@pepperhorn/chordl-core 0.3.0
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/LICENSE +21 -0
- package/dist/audio/midi-export.d.ts +23 -0
- package/dist/audio/midi-export.js +155 -0
- package/dist/audio/midi-export.js.map +1 -0
- package/dist/chord-sheet/codec.d.ts +14 -0
- package/dist/chord-sheet/codec.js +119 -0
- package/dist/chord-sheet/codec.js.map +1 -0
- package/dist/chord-sheet/defaults.d.ts +13 -0
- package/dist/chord-sheet/defaults.js +48 -0
- package/dist/chord-sheet/defaults.js.map +1 -0
- package/dist/chord-sheet/index.d.ts +3 -0
- package/dist/chord-sheet/index.js +4 -0
- package/dist/chord-sheet/index.js.map +1 -0
- package/dist/chord-sheet/schema.d.ts +6 -0
- package/dist/chord-sheet/schema.js +17 -0
- package/dist/chord-sheet/schema.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -0
- package/dist/engine/auto-fingering.d.ts +36 -0
- package/dist/engine/auto-fingering.js +344 -0
- package/dist/engine/auto-fingering.js.map +1 -0
- package/dist/engine/highlight-mapper.d.ts +3 -0
- package/dist/engine/highlight-mapper.js +46 -0
- package/dist/engine/highlight-mapper.js.map +1 -0
- package/dist/engine/keyboard-layout.d.ts +6 -0
- package/dist/engine/keyboard-layout.js +65 -0
- package/dist/engine/keyboard-layout.js.map +1 -0
- package/dist/engine/note-spelling.d.ts +54 -0
- package/dist/engine/note-spelling.js +103 -0
- package/dist/engine/note-spelling.js.map +1 -0
- package/dist/engine/scale-fingering.d.ts +21 -0
- package/dist/engine/scale-fingering.js +130 -0
- package/dist/engine/scale-fingering.js.map +1 -0
- package/dist/engine/staff-constants.d.ts +16 -0
- package/dist/engine/staff-constants.js +20 -0
- package/dist/engine/staff-constants.js.map +1 -0
- package/dist/engine/staff-glyphs.d.ts +17 -0
- package/dist/engine/staff-glyphs.js +59 -0
- package/dist/engine/staff-glyphs.js.map +1 -0
- package/dist/engine/staff-layout.d.ts +28 -0
- package/dist/engine/staff-layout.js +238 -0
- package/dist/engine/staff-layout.js.map +1 -0
- package/dist/engine/svg-constants.d.ts +19 -0
- package/dist/engine/svg-constants.js +40 -0
- package/dist/engine/svg-constants.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +70 -0
- package/dist/logging.js +24 -0
- package/dist/logging.js.map +1 -0
- package/dist/parser/natural-language.d.ts +9 -0
- package/dist/parser/natural-language.js +406 -0
- package/dist/parser/natural-language.js.map +1 -0
- package/dist/parser/progression-parser.d.ts +16 -0
- package/dist/parser/progression-parser.js +97 -0
- package/dist/parser/progression-parser.js.map +1 -0
- package/dist/pipeline.d.ts +33 -0
- package/dist/pipeline.js +203 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/progression/form-templates.d.ts +11 -0
- package/dist/progression/form-templates.js +105 -0
- package/dist/progression/form-templates.js.map +1 -0
- package/dist/progression/index.d.ts +5 -0
- package/dist/progression/index.js +4 -0
- package/dist/progression/index.js.map +1 -0
- package/dist/progression/progression-resolver.d.ts +29 -0
- package/dist/progression/progression-resolver.js +135 -0
- package/dist/progression/progression-resolver.js.map +1 -0
- package/dist/progression/roman-numeral.d.ts +14 -0
- package/dist/progression/roman-numeral.js +78 -0
- package/dist/progression/roman-numeral.js.map +1 -0
- package/dist/resolver/auto-layout.d.ts +20 -0
- package/dist/resolver/auto-layout.js +195 -0
- package/dist/resolver/auto-layout.js.map +1 -0
- package/dist/resolver/chord-resolver.d.ts +8 -0
- package/dist/resolver/chord-resolver.js +172 -0
- package/dist/resolver/chord-resolver.js.map +1 -0
- package/dist/resolver/scale-resolver.d.ts +9 -0
- package/dist/resolver/scale-resolver.js +45 -0
- package/dist/resolver/scale-resolver.js.map +1 -0
- package/dist/themes/boomwhacker.d.ts +2 -0
- package/dist/themes/boomwhacker.js +35 -0
- package/dist/themes/boomwhacker.js.map +1 -0
- package/dist/themes/crf.d.ts +19 -0
- package/dist/themes/crf.js +34 -0
- package/dist/themes/crf.js.map +1 -0
- package/dist/themes/index.d.ts +3 -0
- package/dist/themes/index.js +25 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/simple.d.ts +2 -0
- package/dist/themes/simple.js +13 -0
- package/dist/themes/simple.js.map +1 -0
- package/dist/theory/chord-tones.d.ts +56 -0
- package/dist/theory/chord-tones.js +215 -0
- package/dist/theory/chord-tones.js.map +1 -0
- package/dist/theory/degree-labels.d.ts +31 -0
- package/dist/theory/degree-labels.js +74 -0
- package/dist/theory/degree-labels.js.map +1 -0
- package/dist/types.d.ts +123 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ResolvedScale {
|
|
2
|
+
/** Pitch classes for the full multi-octave display (includes final tonic). */
|
|
3
|
+
notes: string[];
|
|
4
|
+
root: string;
|
|
5
|
+
type: string;
|
|
6
|
+
/** Single-octave Tonal intervals (e.g. ["1P","2M","3M","4P","5P","6M","7M"]). */
|
|
7
|
+
intervals: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveScale(root: string, scaleType: string, direction?: "ascending" | "descending", octaves?: number): ResolvedScale;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scale resolver — resolves scale names to notes via Tonal.
|
|
3
|
+
* Handles melodic minor direction, multi-octave expansion, and
|
|
4
|
+
* generates Tonal interval arrays for degree label computation.
|
|
5
|
+
*/
|
|
6
|
+
import { Scale } from "tonal";
|
|
7
|
+
/** Normalize common scale name variants for Tonal compatibility. */
|
|
8
|
+
const SCALE_NAME_MAP = {
|
|
9
|
+
"natural minor": "minor",
|
|
10
|
+
"minor harmonic": "harmonic minor",
|
|
11
|
+
"minor melodic": "melodic minor",
|
|
12
|
+
"major pentatonic": "major pentatonic",
|
|
13
|
+
"minor pentatonic": "minor pentatonic",
|
|
14
|
+
"whole tone": "whole tone",
|
|
15
|
+
"bebop": "bebop major",
|
|
16
|
+
};
|
|
17
|
+
export function resolveScale(root, scaleType, direction, octaves = 1) {
|
|
18
|
+
// Melodic minor descending = natural minor
|
|
19
|
+
let effectiveType = scaleType;
|
|
20
|
+
if (scaleType === "melodic minor" && direction === "descending") {
|
|
21
|
+
effectiveType = "minor";
|
|
22
|
+
}
|
|
23
|
+
// Apply name normalization
|
|
24
|
+
const tonalName = SCALE_NAME_MAP[effectiveType] ?? effectiveType;
|
|
25
|
+
const scale = Scale.get(`${root} ${tonalName}`);
|
|
26
|
+
if (scale.empty) {
|
|
27
|
+
throw new Error(`Unknown scale: "${root} ${scaleType}"`);
|
|
28
|
+
}
|
|
29
|
+
const oneOctaveNotes = scale.notes;
|
|
30
|
+
const intervals = scale.intervals;
|
|
31
|
+
// Expand across octaves: repeat pitch classes + add final tonic
|
|
32
|
+
const notes = [];
|
|
33
|
+
for (let oct = 0; oct < octaves; oct++) {
|
|
34
|
+
notes.push(...oneOctaveNotes);
|
|
35
|
+
}
|
|
36
|
+
// Add the final tonic to complete the last octave
|
|
37
|
+
notes.push(oneOctaveNotes[0]);
|
|
38
|
+
return {
|
|
39
|
+
notes,
|
|
40
|
+
root,
|
|
41
|
+
type: scaleType,
|
|
42
|
+
intervals,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=scale-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scale-resolver.js","sourceRoot":"","sources":["../../src/resolver/scale-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAW9B,oEAAoE;AACpE,MAAM,cAAc,GAA2B;IAC7C,eAAe,EAAE,OAAO;IACxB,gBAAgB,EAAE,gBAAgB;IAClC,eAAe,EAAE,eAAe;IAChC,kBAAkB,EAAE,kBAAkB;IACtC,kBAAkB,EAAE,kBAAkB;IACtC,YAAY,EAAE,YAAY;IAC1B,OAAO,EAAE,aAAa;CACvB,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,SAAiB,EACjB,SAAsC,EACtC,UAAkB,CAAC;IAEnB,2CAA2C;IAC3C,IAAI,aAAa,GAAG,SAAS,CAAC;IAC9B,IAAI,SAAS,KAAK,eAAe,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QAChE,aAAa,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,2BAA2B;IAC3B,MAAM,SAAS,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC;IAEjE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC;IAChD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,IAAI,SAAS,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC;IACnC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAElC,gEAAgE;IAChE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC;IAChC,CAAC;IACD,kDAAkD;IAClD,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9B,OAAO;QACL,KAAK;QACL,IAAI;QACJ,IAAI,EAAE,SAAS;QACf,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DEFAULT_WHITE_FILL, DEFAULT_BLACK_FILL } from "../engine/svg-constants";
|
|
2
|
+
// Boomwhacker chromatic pitch-class colors
|
|
3
|
+
const PITCH_COLORS = {
|
|
4
|
+
C: "#e81c1c",
|
|
5
|
+
"C#": "#d44a1a",
|
|
6
|
+
D: "#f09819",
|
|
7
|
+
"D#": "#c8b816",
|
|
8
|
+
E: "#f5e616",
|
|
9
|
+
F: "#59b816",
|
|
10
|
+
"F#": "#16a085",
|
|
11
|
+
G: "#16a3b8",
|
|
12
|
+
"G#": "#1670b8",
|
|
13
|
+
A: "#5b16b8",
|
|
14
|
+
"A#": "#8b16b8",
|
|
15
|
+
B: "#b816a0",
|
|
16
|
+
};
|
|
17
|
+
// Muted versions for non-highlighted keys
|
|
18
|
+
const MUTED_ALPHA = "33"; // ~20% opacity in hex
|
|
19
|
+
function mute(hex) {
|
|
20
|
+
return hex + MUTED_ALPHA;
|
|
21
|
+
}
|
|
22
|
+
export const boomwhackerTheme = {
|
|
23
|
+
name: "boomwhacker",
|
|
24
|
+
whiteKey: (note, highlighted) => {
|
|
25
|
+
if (!highlighted)
|
|
26
|
+
return DEFAULT_WHITE_FILL;
|
|
27
|
+
return PITCH_COLORS[note] ?? DEFAULT_WHITE_FILL;
|
|
28
|
+
},
|
|
29
|
+
blackKey: (note, highlighted) => {
|
|
30
|
+
if (!highlighted)
|
|
31
|
+
return DEFAULT_BLACK_FILL;
|
|
32
|
+
return PITCH_COLORS[note] ?? DEFAULT_BLACK_FILL;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=boomwhacker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boomwhacker.js","sourceRoot":"","sources":["../../src/themes/boomwhacker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEjF,2CAA2C;AAC3C,MAAM,YAAY,GAA2B;IAC3C,CAAC,EAAE,SAAS;IACZ,IAAI,EAAE,SAAS;IACf,CAAC,EAAE,SAAS;IACZ,IAAI,EAAE,SAAS;IACf,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,IAAI,EAAE,SAAS;IACf,CAAC,EAAE,SAAS;IACZ,IAAI,EAAE,SAAS;IACf,CAAC,EAAE,SAAS;IACZ,IAAI,EAAE,SAAS;IACf,CAAC,EAAE,SAAS;CACb,CAAC;AAEF,0CAA0C;AAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,sBAAsB;AAEhD,SAAS,IAAI,CAAC,GAAW;IACvB,OAAO,GAAG,GAAG,WAAW,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAe;IAC1C,IAAI,EAAE,aAAa;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE;QAC9B,IAAI,CAAC,WAAW;YAAE,OAAO,kBAAkB,CAAC;QAC5C,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC;IAClD,CAAC;IACD,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE;QAC9B,IAAI,CAAC,WAAW;YAAE,OAAO,kBAAkB,CAAC;QAC5C,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC;IAClD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ColorTheme } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Creative Ranges Foundation piano keyboard colors.
|
|
4
|
+
*
|
|
5
|
+
* Each pitch has a screen color (hex/sRGB) for SVG rendering and a CMYK
|
|
6
|
+
* reference for print pipelines. The hex values are derived from the CMYK
|
|
7
|
+
* spec, so SVGs render true to the print intent — but if a downstream tool
|
|
8
|
+
* needs CMYK directly (Illustrator, InDesign, PDF/X export), use
|
|
9
|
+
* `CRF_PITCH_PALETTE[note].cmyk` rather than re-converting from hex.
|
|
10
|
+
*/
|
|
11
|
+
export interface CrfPitchEntry {
|
|
12
|
+
hex: string;
|
|
13
|
+
/** [C, M, Y, K] in 0–100. */
|
|
14
|
+
cmyk: [number, number, number, number];
|
|
15
|
+
}
|
|
16
|
+
export declare const CRF_PITCH_PALETTE: Record<string, CrfPitchEntry>;
|
|
17
|
+
/** Format a CMYK tuple as a CSS-style string for metadata (`"cmyk(0%, 56%, 56%, 3%)"`). */
|
|
18
|
+
export declare function formatCmyk(cmyk: [number, number, number, number]): string;
|
|
19
|
+
export declare const crfTheme: ColorTheme;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DEFAULT_WHITE_FILL, DEFAULT_BLACK_FILL } from "../engine/svg-constants";
|
|
2
|
+
export const CRF_PITCH_PALETTE = {
|
|
3
|
+
C: { hex: "#f86e6e", cmyk: [0, 56, 56, 3] },
|
|
4
|
+
"C#": { hex: "#f58841", cmyk: [0, 44, 73, 4] },
|
|
5
|
+
D: { hex: "#ffbc57", cmyk: [0, 26, 66, 0] },
|
|
6
|
+
"D#": { hex: "#b8a334", cmyk: [0, 11, 72, 28] },
|
|
7
|
+
E: { hex: "#fff56d", cmyk: [0, 4, 57, 0] },
|
|
8
|
+
F: { hex: "#b3f888", cmyk: [28, 0, 45, 3] },
|
|
9
|
+
"F#": { hex: "#93d154", cmyk: [30, 0, 60, 18] },
|
|
10
|
+
G: { hex: "#6bc6a0", cmyk: [46, 0, 19, 22] },
|
|
11
|
+
"G#": { hex: "#7ee8df", cmyk: [46, 0, 4, 9] },
|
|
12
|
+
A: { hex: "#88a7f8", cmyk: [45, 33, 0, 3] },
|
|
13
|
+
"A#": { hex: "#cc97e8", cmyk: [12, 35, 0, 9] },
|
|
14
|
+
B: { hex: "#e277b1", cmyk: [0, 47, 22, 11] },
|
|
15
|
+
};
|
|
16
|
+
/** Format a CMYK tuple as a CSS-style string for metadata (`"cmyk(0%, 56%, 56%, 3%)"`). */
|
|
17
|
+
export function formatCmyk(cmyk) {
|
|
18
|
+
const [c, m, y, k] = cmyk;
|
|
19
|
+
return `cmyk(${c}%, ${m}%, ${y}%, ${k}%)`;
|
|
20
|
+
}
|
|
21
|
+
export const crfTheme = {
|
|
22
|
+
name: "crf",
|
|
23
|
+
whiteKey: (note, highlighted) => {
|
|
24
|
+
if (!highlighted)
|
|
25
|
+
return DEFAULT_WHITE_FILL;
|
|
26
|
+
return CRF_PITCH_PALETTE[note]?.hex ?? DEFAULT_WHITE_FILL;
|
|
27
|
+
},
|
|
28
|
+
blackKey: (note, highlighted) => {
|
|
29
|
+
if (!highlighted)
|
|
30
|
+
return DEFAULT_BLACK_FILL;
|
|
31
|
+
return CRF_PITCH_PALETTE[note]?.hex ?? DEFAULT_BLACK_FILL;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=crf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crf.js","sourceRoot":"","sources":["../../src/themes/crf.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAiBjF,MAAM,CAAC,MAAM,iBAAiB,GAAkC;IAC9D,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;IAC9C,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;IAC9C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;IAC9C,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;IAC/C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;IAC7C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE;IAC9C,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;IAC/C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;IAC/C,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;IAC7C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;IAC9C,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;IAC9C,CAAC,EAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;CAChD,CAAC;AAEF,2FAA2F;AAC3F,MAAM,UAAU,UAAU,CAAC,IAAsC;IAC/D,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAC1B,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAe;IAClC,IAAI,EAAE,KAAK;IACX,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE;QAC9B,IAAI,CAAC,WAAW;YAAE,OAAO,kBAAkB,CAAC;QAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,kBAAkB,CAAC;IAC5D,CAAC;IACD,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE;QAC9B,IAAI,CAAC,WAAW;YAAE,OAAO,kBAAkB,CAAC;QAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,kBAAkB,CAAC;IAC5D,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { boomwhackerTheme } from "./boomwhacker";
|
|
2
|
+
import { crfTheme } from "./crf";
|
|
3
|
+
import { createSimpleTheme } from "./simple";
|
|
4
|
+
const themeRegistry = {
|
|
5
|
+
boomwhacker: boomwhackerTheme,
|
|
6
|
+
crf: crfTheme,
|
|
7
|
+
simple: createSimpleTheme(),
|
|
8
|
+
};
|
|
9
|
+
export function getTheme(name) {
|
|
10
|
+
return themeRegistry[name];
|
|
11
|
+
}
|
|
12
|
+
export function resolveTheme(theme, highlightColor) {
|
|
13
|
+
if (!theme && !highlightColor)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (!theme && highlightColor)
|
|
16
|
+
return createSimpleTheme(highlightColor);
|
|
17
|
+
if (typeof theme === "string") {
|
|
18
|
+
if (theme === "simple" && highlightColor) {
|
|
19
|
+
return createSimpleTheme(highlightColor);
|
|
20
|
+
}
|
|
21
|
+
return themeRegistry[theme];
|
|
22
|
+
}
|
|
23
|
+
return theme;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/themes/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,aAAa,GAA+B;IAChD,WAAW,EAAE,gBAAgB;IAC7B,GAAG,EAAE,QAAQ;IACb,MAAM,EAAE,iBAAiB,EAAE;CAC5B,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,KAAsC,EACtC,cAAuB;IAEvB,IAAI,CAAC,KAAK,IAAI,CAAC,cAAc;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,CAAC,KAAK,IAAI,cAAc;QAAE,OAAO,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACvE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,KAAK,QAAQ,IAAI,cAAc,EAAE,CAAC;YACzC,OAAO,iBAAiB,CAAC,cAAc,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DEFAULT_WHITE_FILL, DEFAULT_BLACK_FILL } from "../engine/svg-constants";
|
|
2
|
+
const DEFAULT_HIGHLIGHT = "#a0c6e8";
|
|
3
|
+
const DEFAULT_BLACK_HIGHLIGHT = "#5a8ab5";
|
|
4
|
+
export function createSimpleTheme(highlightColor) {
|
|
5
|
+
const whiteHighlight = highlightColor ?? DEFAULT_HIGHLIGHT;
|
|
6
|
+
const blackHighlight = highlightColor ?? DEFAULT_BLACK_HIGHLIGHT;
|
|
7
|
+
return {
|
|
8
|
+
name: "simple",
|
|
9
|
+
whiteKey: (_note, highlighted) => highlighted ? whiteHighlight : DEFAULT_WHITE_FILL,
|
|
10
|
+
blackKey: (_note, highlighted) => highlighted ? blackHighlight : DEFAULT_BLACK_FILL,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=simple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple.js","sourceRoot":"","sources":["../../src/themes/simple.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEjF,MAAM,iBAAiB,GAAG,SAAS,CAAC;AACpC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAE1C,MAAM,UAAU,iBAAiB,CAAC,cAAuB;IACvD,MAAM,cAAc,GAAG,cAAc,IAAI,iBAAiB,CAAC;IAC3D,MAAM,cAAc,GAAG,cAAc,IAAI,uBAAuB,CAAC;IAEjE,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAC/B,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB;QACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAC/B,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB;KACpD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chord tone classification engine.
|
|
3
|
+
*
|
|
4
|
+
* Classifies each note in a chord by its functional role:
|
|
5
|
+
* - identity: defines the chord (3rd, 7th, altered tensions) — never drop
|
|
6
|
+
* - color: adds character (natural 9th, 13th) — drop last
|
|
7
|
+
* - foundation: structural (root) — drop when bass covers it
|
|
8
|
+
* - omittable: standard to omit (perfect 5th, natural 11th) — drop first
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Mark Levine's "The Jazz Piano Book" approach to voicing.
|
|
11
|
+
*/
|
|
12
|
+
export type ChordToneRole = "identity" | "color" | "foundation" | "omittable";
|
|
13
|
+
export interface ChordTone {
|
|
14
|
+
note: string;
|
|
15
|
+
interval: string;
|
|
16
|
+
degree: number;
|
|
17
|
+
quality: string;
|
|
18
|
+
role: ChordToneRole;
|
|
19
|
+
}
|
|
20
|
+
export interface ChordToneAnalysis {
|
|
21
|
+
root: string;
|
|
22
|
+
type: string;
|
|
23
|
+
tones: ChordTone[];
|
|
24
|
+
/** Notes that define the chord — never drop */
|
|
25
|
+
identityTones: string[];
|
|
26
|
+
/** Smallest set that still communicates the chord */
|
|
27
|
+
minVoicing: string[];
|
|
28
|
+
/** Notes ordered safe-to-drop-first → never-drop-last */
|
|
29
|
+
dropOrder: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse a Tonal interval string like "3M", "5d", "9A", "1P".
|
|
33
|
+
* Format: degree number + quality letter(s).
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseInterval(interval: string): {
|
|
36
|
+
degree: number;
|
|
37
|
+
quality: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Classify each tone in a chord by its functional role.
|
|
41
|
+
*
|
|
42
|
+
* @param root - Root note ("C", "Bb", etc.)
|
|
43
|
+
* @param chordType - Tonal chord type string ("major seventh", "dominant sharp ninth")
|
|
44
|
+
* @param intervals - Tonal interval names (["1P", "3M", "5P", "7M"])
|
|
45
|
+
* @param notes - Pitch classes matching intervals (["C", "E", "G", "B"])
|
|
46
|
+
* @param instrument - "piano" (default) or "guitar" — shifts role assignments
|
|
47
|
+
*/
|
|
48
|
+
export declare function classifyTones(root: string, chordType: string, intervals: string[], notes: string[], instrument?: "piano" | "guitar"): ChordToneAnalysis;
|
|
49
|
+
/**
|
|
50
|
+
* Get the minimal voicing — the fewest notes that still communicate the chord.
|
|
51
|
+
*/
|
|
52
|
+
export declare function minimalVoicing(analysis: ChordToneAnalysis): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Get notes in order of safe-to-drop (first = safest to drop).
|
|
55
|
+
*/
|
|
56
|
+
export declare function dropOrder(analysis: ChordToneAnalysis): string[];
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chord tone classification engine.
|
|
3
|
+
*
|
|
4
|
+
* Classifies each note in a chord by its functional role:
|
|
5
|
+
* - identity: defines the chord (3rd, 7th, altered tensions) — never drop
|
|
6
|
+
* - color: adds character (natural 9th, 13th) — drop last
|
|
7
|
+
* - foundation: structural (root) — drop when bass covers it
|
|
8
|
+
* - omittable: standard to omit (perfect 5th, natural 11th) — drop first
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Mark Levine's "The Jazz Piano Book" approach to voicing.
|
|
11
|
+
*/
|
|
12
|
+
// ── Interval parsing ──────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Parse a Tonal interval string like "3M", "5d", "9A", "1P".
|
|
15
|
+
* Format: degree number + quality letter(s).
|
|
16
|
+
*/
|
|
17
|
+
export function parseInterval(interval) {
|
|
18
|
+
const match = interval.match(/^(\d+)(.*)/);
|
|
19
|
+
if (!match)
|
|
20
|
+
return { degree: 0, quality: "" };
|
|
21
|
+
return {
|
|
22
|
+
degree: parseInt(match[1], 10),
|
|
23
|
+
quality: match[2],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if an interval is "altered" — i.e., not the natural/default quality
|
|
28
|
+
* for that scale degree.
|
|
29
|
+
*
|
|
30
|
+
* Natural defaults:
|
|
31
|
+
* 1=P, 2=M, 3=M(maj)/m(min), 4=P, 5=P, 6=M, 7=M(maj)/m(dom/min)
|
|
32
|
+
* 9=M, 11=P, 13=M
|
|
33
|
+
*
|
|
34
|
+
* Anything else (A=augmented, d=diminished, or wrong M/m) is altered.
|
|
35
|
+
*/
|
|
36
|
+
function isAltered(degree, quality) {
|
|
37
|
+
switch (degree) {
|
|
38
|
+
case 1: return quality !== "P";
|
|
39
|
+
case 2:
|
|
40
|
+
case 9: return quality !== "M";
|
|
41
|
+
case 3: return quality !== "M" && quality !== "m"; // both are "natural" depending on chord
|
|
42
|
+
case 4:
|
|
43
|
+
case 11: return quality !== "P";
|
|
44
|
+
case 5: return quality !== "P";
|
|
45
|
+
case 6:
|
|
46
|
+
case 13: return quality !== "M";
|
|
47
|
+
case 7: return quality !== "M" && quality !== "m"; // both natural depending on chord
|
|
48
|
+
default: return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function classifyQualityFamily(type, intervals) {
|
|
52
|
+
const t = type.toLowerCase();
|
|
53
|
+
if (t.includes("alt"))
|
|
54
|
+
return "alt";
|
|
55
|
+
if (t.includes("dim") && t.includes("seventh"))
|
|
56
|
+
return "dim7";
|
|
57
|
+
if (t.includes("half") || t.includes("m7b5"))
|
|
58
|
+
return "half-dim";
|
|
59
|
+
if (t.includes("sus"))
|
|
60
|
+
return "sus";
|
|
61
|
+
if (t.includes("sixth") || t.includes("6"))
|
|
62
|
+
return "sixth";
|
|
63
|
+
if (/seven|7|ninth|9|eleven|11|thirteen|13/.test(t))
|
|
64
|
+
return "seventh";
|
|
65
|
+
// Fallback: infer from intervals when type string is empty/ambiguous
|
|
66
|
+
if (intervals && intervals.length > 0) {
|
|
67
|
+
const hasAugOrDimFifth = intervals.some((i) => {
|
|
68
|
+
const { degree, quality } = parseInterval(i);
|
|
69
|
+
return degree === 5 && (quality === "A" || quality === "d");
|
|
70
|
+
});
|
|
71
|
+
const hasSeventh = intervals.some((i) => parseInterval(i).degree === 7);
|
|
72
|
+
const hasAlteredExtension = intervals.some((i) => {
|
|
73
|
+
const { degree, quality } = parseInterval(i);
|
|
74
|
+
return degree >= 9 && isAltered(degree, quality);
|
|
75
|
+
});
|
|
76
|
+
if (hasAlteredExtension && hasAugOrDimFifth)
|
|
77
|
+
return "alt";
|
|
78
|
+
if (hasSeventh) {
|
|
79
|
+
const dimFifth = intervals.some((i) => {
|
|
80
|
+
const p = parseInterval(i);
|
|
81
|
+
return p.degree === 5 && p.quality === "d";
|
|
82
|
+
});
|
|
83
|
+
const dimSeventh = intervals.some((i) => {
|
|
84
|
+
const p = parseInterval(i);
|
|
85
|
+
return p.degree === 7 && p.quality === "d";
|
|
86
|
+
});
|
|
87
|
+
if (dimFifth && dimSeventh)
|
|
88
|
+
return "dim7";
|
|
89
|
+
if (dimFifth)
|
|
90
|
+
return "half-dim";
|
|
91
|
+
return "seventh";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return "triad";
|
|
95
|
+
}
|
|
96
|
+
// ── Role assignment ───────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Assign a role to a chord tone based on its degree, quality, and the
|
|
99
|
+
* chord's quality family.
|
|
100
|
+
*/
|
|
101
|
+
function assignRole(degree, quality, family, instrument) {
|
|
102
|
+
const altered = isAltered(degree, quality);
|
|
103
|
+
switch (degree) {
|
|
104
|
+
case 1:
|
|
105
|
+
// Root: foundation in most contexts, omittable in rootless jazz piano
|
|
106
|
+
if (family === "alt" && instrument === "piano")
|
|
107
|
+
return "omittable";
|
|
108
|
+
return "foundation";
|
|
109
|
+
case 3:
|
|
110
|
+
// 3rd always defines major/minor quality
|
|
111
|
+
return "identity";
|
|
112
|
+
case 4:
|
|
113
|
+
// sus4: the 4th replaces the 3rd, so it's identity
|
|
114
|
+
if (family === "sus")
|
|
115
|
+
return "identity";
|
|
116
|
+
// #11 is identity (lydian character)
|
|
117
|
+
if (altered)
|
|
118
|
+
return "identity";
|
|
119
|
+
// Natural 11 clashes with major 3rd — omittable
|
|
120
|
+
return "omittable";
|
|
121
|
+
case 5:
|
|
122
|
+
// Perfect 5th is omittable in 7th chords — adds nothing to identity
|
|
123
|
+
// But altered 5th (b5, #5) IS the identity (dim, aug, m7b5)
|
|
124
|
+
if (altered)
|
|
125
|
+
return "identity";
|
|
126
|
+
if (family === "triad")
|
|
127
|
+
return instrument === "guitar" ? "omittable" : "identity";
|
|
128
|
+
return "omittable";
|
|
129
|
+
case 6:
|
|
130
|
+
// 6th in sixth chords is identity
|
|
131
|
+
if (family === "sixth")
|
|
132
|
+
return "identity";
|
|
133
|
+
return "color";
|
|
134
|
+
case 7:
|
|
135
|
+
// 7th defines the chord in 7th-chord families
|
|
136
|
+
if (family === "triad")
|
|
137
|
+
return "color"; // shouldn't normally appear
|
|
138
|
+
return "identity";
|
|
139
|
+
case 2:
|
|
140
|
+
case 9:
|
|
141
|
+
// Altered 9ths (#9, b9) are identity — they define the chord
|
|
142
|
+
if (altered)
|
|
143
|
+
return "identity";
|
|
144
|
+
return "color";
|
|
145
|
+
case 11:
|
|
146
|
+
if (altered)
|
|
147
|
+
return "identity"; // #11 = lydian
|
|
148
|
+
return "omittable"; // natural 11 clashes with 3rd
|
|
149
|
+
case 13:
|
|
150
|
+
if (altered)
|
|
151
|
+
return "identity"; // b13 in alt chords
|
|
152
|
+
return "color";
|
|
153
|
+
default:
|
|
154
|
+
return "color";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ── Main classification ───────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* Classify each tone in a chord by its functional role.
|
|
160
|
+
*
|
|
161
|
+
* @param root - Root note ("C", "Bb", etc.)
|
|
162
|
+
* @param chordType - Tonal chord type string ("major seventh", "dominant sharp ninth")
|
|
163
|
+
* @param intervals - Tonal interval names (["1P", "3M", "5P", "7M"])
|
|
164
|
+
* @param notes - Pitch classes matching intervals (["C", "E", "G", "B"])
|
|
165
|
+
* @param instrument - "piano" (default) or "guitar" — shifts role assignments
|
|
166
|
+
*/
|
|
167
|
+
export function classifyTones(root, chordType, intervals, notes, instrument = "piano") {
|
|
168
|
+
const family = classifyQualityFamily(chordType, intervals);
|
|
169
|
+
const tones = intervals.map((interval, i) => {
|
|
170
|
+
const { degree, quality } = parseInterval(interval);
|
|
171
|
+
const role = assignRole(degree, quality, family, instrument);
|
|
172
|
+
return {
|
|
173
|
+
note: notes[i] ?? "",
|
|
174
|
+
interval,
|
|
175
|
+
degree,
|
|
176
|
+
quality,
|
|
177
|
+
role,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
const identityTones = tones.filter((t) => t.role === "identity").map((t) => t.note);
|
|
181
|
+
// Minimal voicing: identity tones + foundation (root) for grounding
|
|
182
|
+
const foundationTones = tones.filter((t) => t.role === "foundation").map((t) => t.note);
|
|
183
|
+
const minVoicing = [...foundationTones, ...identityTones];
|
|
184
|
+
// Drop order: omittable first, then color, then foundation, identity last
|
|
185
|
+
const ROLE_PRIORITY = {
|
|
186
|
+
omittable: 0,
|
|
187
|
+
color: 1,
|
|
188
|
+
foundation: 2,
|
|
189
|
+
identity: 3,
|
|
190
|
+
};
|
|
191
|
+
const dropOrder = [...tones]
|
|
192
|
+
.sort((a, b) => ROLE_PRIORITY[a.role] - ROLE_PRIORITY[b.role])
|
|
193
|
+
.map((t) => t.note);
|
|
194
|
+
return {
|
|
195
|
+
root,
|
|
196
|
+
type: chordType,
|
|
197
|
+
tones,
|
|
198
|
+
identityTones,
|
|
199
|
+
minVoicing,
|
|
200
|
+
dropOrder,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get the minimal voicing — the fewest notes that still communicate the chord.
|
|
205
|
+
*/
|
|
206
|
+
export function minimalVoicing(analysis) {
|
|
207
|
+
return analysis.minVoicing;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get notes in order of safe-to-drop (first = safest to drop).
|
|
211
|
+
*/
|
|
212
|
+
export function dropOrder(analysis) {
|
|
213
|
+
return analysis.dropOrder;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=chord-tones.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chord-tones.js","sourceRoot":"","sources":["../../src/theory/chord-tones.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jazz roman numeral degree labels.
|
|
3
|
+
*
|
|
4
|
+
* Maps Tonal interval strings (1P, 3M, 5A, 7m, etc.) to jazz-standard
|
|
5
|
+
* roman numeral notation with accidentals (I, bIII, #V, bVII, etc.).
|
|
6
|
+
*
|
|
7
|
+
* The accidental reflects the interval alteration from the major scale:
|
|
8
|
+
* - Perfect/Major = natural (no accidental)
|
|
9
|
+
* - Minor/Diminished = flat (b)
|
|
10
|
+
* - Augmented = sharp (#)
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Convert a Tonal interval string to a jazz roman numeral degree label.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* intervalToDegreeLabel("1P") → "I"
|
|
17
|
+
* intervalToDegreeLabel("3M") → "III"
|
|
18
|
+
* intervalToDegreeLabel("3m") → "bIII"
|
|
19
|
+
* intervalToDegreeLabel("5A") → "#V"
|
|
20
|
+
* intervalToDegreeLabel("7m") → "bVII"
|
|
21
|
+
* intervalToDegreeLabel("9A") → "#IX"
|
|
22
|
+
* intervalToDegreeLabel("5d") → "bV"
|
|
23
|
+
*/
|
|
24
|
+
export declare function intervalToDegreeLabel(interval: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Convert an array of Tonal intervals to jazz degree labels.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* degreesForIntervals(["1P", "3M", "5A", "7m"]) → ["I", "III", "#V", "bVII"]
|
|
30
|
+
*/
|
|
31
|
+
export declare function degreesForIntervals(intervals: string[]): string[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jazz roman numeral degree labels.
|
|
3
|
+
*
|
|
4
|
+
* Maps Tonal interval strings (1P, 3M, 5A, 7m, etc.) to jazz-standard
|
|
5
|
+
* roman numeral notation with accidentals (I, bIII, #V, bVII, etc.).
|
|
6
|
+
*
|
|
7
|
+
* The accidental reflects the interval alteration from the major scale:
|
|
8
|
+
* - Perfect/Major = natural (no accidental)
|
|
9
|
+
* - Minor/Diminished = flat (b)
|
|
10
|
+
* - Augmented = sharp (#)
|
|
11
|
+
*/
|
|
12
|
+
import { parseInterval } from "./chord-tones";
|
|
13
|
+
const ROMAN = ["", "I", "II", "III", "IV", "V", "VI", "VII"];
|
|
14
|
+
const ROMAN_EXT = {
|
|
15
|
+
9: "IX", 10: "X", 11: "XI", 12: "XII", 13: "XIII",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The "natural" quality for each scale degree in the major scale.
|
|
19
|
+
* 1, 4, 5 are perfect (P). 2, 3, 6, 7 are major (M).
|
|
20
|
+
* Extensions follow the same pattern: 9=M, 11=P, 13=M.
|
|
21
|
+
*/
|
|
22
|
+
function naturalQuality(degree) {
|
|
23
|
+
const simple = degree > 7 ? degree - 7 : degree;
|
|
24
|
+
if (simple === 1 || simple === 4 || simple === 5)
|
|
25
|
+
return "P";
|
|
26
|
+
return "M";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert a Tonal interval string to a jazz roman numeral degree label.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* intervalToDegreeLabel("1P") → "I"
|
|
33
|
+
* intervalToDegreeLabel("3M") → "III"
|
|
34
|
+
* intervalToDegreeLabel("3m") → "bIII"
|
|
35
|
+
* intervalToDegreeLabel("5A") → "#V"
|
|
36
|
+
* intervalToDegreeLabel("7m") → "bVII"
|
|
37
|
+
* intervalToDegreeLabel("9A") → "#IX"
|
|
38
|
+
* intervalToDegreeLabel("5d") → "bV"
|
|
39
|
+
*/
|
|
40
|
+
export function intervalToDegreeLabel(interval) {
|
|
41
|
+
const { degree, quality } = parseInterval(interval);
|
|
42
|
+
if (degree === 0)
|
|
43
|
+
return "?";
|
|
44
|
+
const numeral = degree <= 7
|
|
45
|
+
? (ROMAN[degree] ?? String(degree))
|
|
46
|
+
: (ROMAN_EXT[degree] ?? String(degree));
|
|
47
|
+
const nat = naturalQuality(degree);
|
|
48
|
+
// Determine accidental
|
|
49
|
+
if (quality === nat) {
|
|
50
|
+
// Natural quality for this degree — no accidental
|
|
51
|
+
return numeral;
|
|
52
|
+
}
|
|
53
|
+
if (quality === "m" || quality === "d") {
|
|
54
|
+
return `b${numeral}`;
|
|
55
|
+
}
|
|
56
|
+
if (quality === "A") {
|
|
57
|
+
return `#${numeral}`;
|
|
58
|
+
}
|
|
59
|
+
// Edge case: double diminished (bb7 in dim7) → still flat in jazz notation
|
|
60
|
+
if (quality === "dd") {
|
|
61
|
+
return `bb${numeral}`;
|
|
62
|
+
}
|
|
63
|
+
return numeral;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert an array of Tonal intervals to jazz degree labels.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* degreesForIntervals(["1P", "3M", "5A", "7m"]) → ["I", "III", "#V", "bVII"]
|
|
70
|
+
*/
|
|
71
|
+
export function degreesForIntervals(intervals) {
|
|
72
|
+
return intervals.map(intervalToDegreeLabel);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=degree-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degree-labels.js","sourceRoot":"","sources":["../../src/theory/degree-labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7D,MAAM,SAAS,GAA2B;IACxC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM;CAClD,CAAC;AAEF;;;;GAIG;AACH,SAAS,cAAc,CAAC,MAAc;IACpC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAChD,IAAI,MAAM,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAC7D,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAE7B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC;QACzB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAE1C,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEnC,uBAAuB;IACvB,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,kDAAkD;QAClD,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACvC,OAAO,IAAI,OAAO,EAAE,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,OAAO,IAAI,OAAO,EAAE,CAAC;IACvB,CAAC;IACD,2EAA2E;IAC3E,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,KAAK,OAAO,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAmB;IACrD,OAAO,SAAS,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AAC9C,CAAC"}
|