@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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/dist/audio/midi-export.d.ts +23 -0
  3. package/dist/audio/midi-export.js +155 -0
  4. package/dist/audio/midi-export.js.map +1 -0
  5. package/dist/chord-sheet/codec.d.ts +14 -0
  6. package/dist/chord-sheet/codec.js +119 -0
  7. package/dist/chord-sheet/codec.js.map +1 -0
  8. package/dist/chord-sheet/defaults.d.ts +13 -0
  9. package/dist/chord-sheet/defaults.js +48 -0
  10. package/dist/chord-sheet/defaults.js.map +1 -0
  11. package/dist/chord-sheet/index.d.ts +3 -0
  12. package/dist/chord-sheet/index.js +4 -0
  13. package/dist/chord-sheet/index.js.map +1 -0
  14. package/dist/chord-sheet/schema.d.ts +6 -0
  15. package/dist/chord-sheet/schema.js +17 -0
  16. package/dist/chord-sheet/schema.js.map +1 -0
  17. package/dist/config.d.ts +2 -0
  18. package/dist/config.js +15 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/engine/auto-fingering.d.ts +36 -0
  21. package/dist/engine/auto-fingering.js +344 -0
  22. package/dist/engine/auto-fingering.js.map +1 -0
  23. package/dist/engine/highlight-mapper.d.ts +3 -0
  24. package/dist/engine/highlight-mapper.js +46 -0
  25. package/dist/engine/highlight-mapper.js.map +1 -0
  26. package/dist/engine/keyboard-layout.d.ts +6 -0
  27. package/dist/engine/keyboard-layout.js +65 -0
  28. package/dist/engine/keyboard-layout.js.map +1 -0
  29. package/dist/engine/note-spelling.d.ts +54 -0
  30. package/dist/engine/note-spelling.js +103 -0
  31. package/dist/engine/note-spelling.js.map +1 -0
  32. package/dist/engine/scale-fingering.d.ts +21 -0
  33. package/dist/engine/scale-fingering.js +130 -0
  34. package/dist/engine/scale-fingering.js.map +1 -0
  35. package/dist/engine/staff-constants.d.ts +16 -0
  36. package/dist/engine/staff-constants.js +20 -0
  37. package/dist/engine/staff-constants.js.map +1 -0
  38. package/dist/engine/staff-glyphs.d.ts +17 -0
  39. package/dist/engine/staff-glyphs.js +59 -0
  40. package/dist/engine/staff-glyphs.js.map +1 -0
  41. package/dist/engine/staff-layout.d.ts +28 -0
  42. package/dist/engine/staff-layout.js +238 -0
  43. package/dist/engine/staff-layout.js.map +1 -0
  44. package/dist/engine/svg-constants.d.ts +19 -0
  45. package/dist/engine/svg-constants.js +40 -0
  46. package/dist/engine/svg-constants.js.map +1 -0
  47. package/dist/index.d.ts +42 -0
  48. package/dist/index.js +37 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/logging.d.ts +70 -0
  51. package/dist/logging.js +24 -0
  52. package/dist/logging.js.map +1 -0
  53. package/dist/parser/natural-language.d.ts +9 -0
  54. package/dist/parser/natural-language.js +406 -0
  55. package/dist/parser/natural-language.js.map +1 -0
  56. package/dist/parser/progression-parser.d.ts +16 -0
  57. package/dist/parser/progression-parser.js +97 -0
  58. package/dist/parser/progression-parser.js.map +1 -0
  59. package/dist/pipeline.d.ts +33 -0
  60. package/dist/pipeline.js +203 -0
  61. package/dist/pipeline.js.map +1 -0
  62. package/dist/progression/form-templates.d.ts +11 -0
  63. package/dist/progression/form-templates.js +105 -0
  64. package/dist/progression/form-templates.js.map +1 -0
  65. package/dist/progression/index.d.ts +5 -0
  66. package/dist/progression/index.js +4 -0
  67. package/dist/progression/index.js.map +1 -0
  68. package/dist/progression/progression-resolver.d.ts +29 -0
  69. package/dist/progression/progression-resolver.js +135 -0
  70. package/dist/progression/progression-resolver.js.map +1 -0
  71. package/dist/progression/roman-numeral.d.ts +14 -0
  72. package/dist/progression/roman-numeral.js +78 -0
  73. package/dist/progression/roman-numeral.js.map +1 -0
  74. package/dist/resolver/auto-layout.d.ts +20 -0
  75. package/dist/resolver/auto-layout.js +195 -0
  76. package/dist/resolver/auto-layout.js.map +1 -0
  77. package/dist/resolver/chord-resolver.d.ts +8 -0
  78. package/dist/resolver/chord-resolver.js +172 -0
  79. package/dist/resolver/chord-resolver.js.map +1 -0
  80. package/dist/resolver/scale-resolver.d.ts +9 -0
  81. package/dist/resolver/scale-resolver.js +45 -0
  82. package/dist/resolver/scale-resolver.js.map +1 -0
  83. package/dist/themes/boomwhacker.d.ts +2 -0
  84. package/dist/themes/boomwhacker.js +35 -0
  85. package/dist/themes/boomwhacker.js.map +1 -0
  86. package/dist/themes/crf.d.ts +19 -0
  87. package/dist/themes/crf.js +34 -0
  88. package/dist/themes/crf.js.map +1 -0
  89. package/dist/themes/index.d.ts +3 -0
  90. package/dist/themes/index.js +25 -0
  91. package/dist/themes/index.js.map +1 -0
  92. package/dist/themes/simple.d.ts +2 -0
  93. package/dist/themes/simple.js +13 -0
  94. package/dist/themes/simple.js.map +1 -0
  95. package/dist/theory/chord-tones.d.ts +56 -0
  96. package/dist/theory/chord-tones.js +215 -0
  97. package/dist/theory/chord-tones.js.map +1 -0
  98. package/dist/theory/degree-labels.d.ts +31 -0
  99. package/dist/theory/degree-labels.js +74 -0
  100. package/dist/theory/degree-labels.js.map +1 -0
  101. package/dist/types.d.ts +123 -0
  102. package/dist/types.js +2 -0
  103. package/dist/types.js.map +1 -0
  104. 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,2 @@
1
+ import type { ColorTheme } from "../types";
2
+ export declare const boomwhackerTheme: ColorTheme;
@@ -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,3 @@
1
+ import type { ColorTheme } from "../types";
2
+ export declare function getTheme(name: string): ColorTheme | undefined;
3
+ export declare function resolveTheme(theme: ColorTheme | string | undefined, highlightColor?: string): ColorTheme | undefined;
@@ -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,2 @@
1
+ import type { ColorTheme } from "../types";
2
+ export declare function createSimpleTheme(highlightColor?: string): ColorTheme;
@@ -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"}