@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,16 @@
|
|
|
1
|
+
import type { Format } from "../types";
|
|
2
|
+
export interface ParsedProgressionRequest {
|
|
3
|
+
progression: string;
|
|
4
|
+
key: string;
|
|
5
|
+
numExamples: number;
|
|
6
|
+
styleHint?: string;
|
|
7
|
+
format?: Format;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Detect whether an input string is a progression request rather than a single chord.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isProgressionRequest(input: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Parse a progression request from natural language.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseProgressionRequest(input: string): ParsedProgressionRequest;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MAX_EXAMPLES } from "../config";
|
|
2
|
+
// Roman numeral pattern — must not be followed by a letter (prevents "in", "it", "is" etc.)
|
|
3
|
+
const ROMAN_NUMERAL_SRC = `[#b]?(?:iv|vi{1,2}|i{1,3}|IV|VI{1,2}|I{1,3}|V)(?:[°ø]?\\d*(?:maj|m)?[0-9#b]*)(?![a-z])`;
|
|
4
|
+
// A progression: 2+ roman numerals separated by delimiters
|
|
5
|
+
const PROGRESSION_PATTERN = new RegExp(`(?:^|\\s)(${ROMAN_NUMERAL_SRC}(?:[\\s\\-–—→>]+${ROMAN_NUMERAL_SRC})+)`, "i");
|
|
6
|
+
// Example count: "3 examples", "show me 2", "give me 5 examples"
|
|
7
|
+
const EXAMPLE_COUNT_RE = /(\d+)\s*(?:examples?|variations?|versions?|ways?|options?)/i;
|
|
8
|
+
// Style hints
|
|
9
|
+
const STYLE_RE = /(?:(?:in\s+)?(?:the\s+)?style\s+of\s+|like\s+|a\s+la\s+)([\w\s]+?)(?:\s*$|\s*,|\s+(?:in\s|examples?|variations?))/i;
|
|
10
|
+
const STYLE_KEYWORD_RE = /\b(bebop|basie|nestico|ellington|modal|comping|rootless|quartal|block\s*chords?|locked\s*hands|drop\s*2\s*\+?\s*4|drop\s*2|upper\s*structure|shell|stride|spread|4[- ]?note\s*closed|bill\s*evans|mccoy\s*tyner|herbie\s*hancock|bud\s*powell|monk)\b/i;
|
|
11
|
+
// Form template names — must come before roman numeral detection
|
|
12
|
+
const FORM_NAMES = [
|
|
13
|
+
{ pattern: /\bjazz\s*blues\b/i, id: "jazz blues" },
|
|
14
|
+
{ pattern: /\bblues\b/i, id: "blues" },
|
|
15
|
+
{ pattern: /\brhythm\s*changes?\b/i, id: "rhythm changes" },
|
|
16
|
+
{ pattern: /\bturnaround\b/i, id: "I-vi-ii-V" },
|
|
17
|
+
{ pattern: /\bmodal\s*vamp\b/i, id: "modal vamp" },
|
|
18
|
+
{ pattern: /\bbackdoor\b/i, id: "backdoor" },
|
|
19
|
+
{ pattern: /\btritone\s*sub(?:stitution)?\b/i, id: "tritone sub" },
|
|
20
|
+
{ pattern: /\bminor\s*(?:ii[\s\-–—]*v[\s\-–—]*i|2[\s\-–—]*5[\s\-–—]*1)\b/i, id: "minor ii-V-i" },
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Detect whether an input string is a progression request rather than a single chord.
|
|
24
|
+
*/
|
|
25
|
+
export function isProgressionRequest(input) {
|
|
26
|
+
// Check for form template names first
|
|
27
|
+
for (const { pattern } of FORM_NAMES) {
|
|
28
|
+
if (pattern.test(input))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Check for roman numeral progression pattern (2+ numerals separated by delimiters)
|
|
32
|
+
if (PROGRESSION_PATTERN.test(input))
|
|
33
|
+
return true;
|
|
34
|
+
// Check for "progression" keyword
|
|
35
|
+
if (/\bprogression\b/i.test(input))
|
|
36
|
+
return true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a progression request from natural language.
|
|
41
|
+
*/
|
|
42
|
+
export function parseProgressionRequest(input) {
|
|
43
|
+
// Extract example count
|
|
44
|
+
let numExamples = MAX_EXAMPLES;
|
|
45
|
+
const countMatch = input.match(EXAMPLE_COUNT_RE);
|
|
46
|
+
if (countMatch) {
|
|
47
|
+
numExamples = Math.min(parseInt(countMatch[1], 10), MAX_EXAMPLES);
|
|
48
|
+
}
|
|
49
|
+
// Extract style
|
|
50
|
+
let styleHint;
|
|
51
|
+
const styleMatch = input.match(STYLE_RE);
|
|
52
|
+
const styleKeywordMatch = input.match(STYLE_KEYWORD_RE);
|
|
53
|
+
if (styleMatch) {
|
|
54
|
+
styleHint = styleMatch[1].trim();
|
|
55
|
+
}
|
|
56
|
+
else if (styleKeywordMatch) {
|
|
57
|
+
styleHint = styleKeywordMatch[1].trim();
|
|
58
|
+
}
|
|
59
|
+
// Extract format
|
|
60
|
+
let format;
|
|
61
|
+
const formatMatch = input.match(/\b(compact|exact)\b/i);
|
|
62
|
+
if (formatMatch) {
|
|
63
|
+
format = formatMatch[1].toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
// Extract key — "in G", "in the key of Db", "in F#"
|
|
66
|
+
let key = "C";
|
|
67
|
+
const keyMatch = input.match(/\bin\s+(?:the\s+)?(?:key\s+(?:of\s+)?)?([A-Ga-g][#b]?)(?:\s|$)/i);
|
|
68
|
+
if (keyMatch) {
|
|
69
|
+
key = keyMatch[1].charAt(0).toUpperCase() + keyMatch[1].slice(1);
|
|
70
|
+
}
|
|
71
|
+
// Extract progression — check form names first, then roman numerals
|
|
72
|
+
let progression = "";
|
|
73
|
+
for (const { pattern, id } of FORM_NAMES) {
|
|
74
|
+
if (pattern.test(input)) {
|
|
75
|
+
progression = id;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// If no form name matched, try roman numeral pattern
|
|
80
|
+
if (!progression) {
|
|
81
|
+
const progMatch = input.match(PROGRESSION_PATTERN);
|
|
82
|
+
if (progMatch) {
|
|
83
|
+
progression = progMatch[1].trim();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!progression) {
|
|
87
|
+
progression = "ii-V-I";
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
progression,
|
|
91
|
+
key,
|
|
92
|
+
numExamples,
|
|
93
|
+
styleHint,
|
|
94
|
+
format,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=progression-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progression-parser.js","sourceRoot":"","sources":["../../src/parser/progression-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LogConfig } from "./logging";
|
|
2
|
+
export interface ChordRequest {
|
|
3
|
+
input: string;
|
|
4
|
+
format?: "json" | "svg" | "midi";
|
|
5
|
+
logConfig?: LogConfig;
|
|
6
|
+
}
|
|
7
|
+
export interface ChordResultKeyboard {
|
|
8
|
+
startFrom: string;
|
|
9
|
+
size: number;
|
|
10
|
+
highlightKeys: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ChordResultTelemetry {
|
|
13
|
+
parser: "chord" | "progression";
|
|
14
|
+
resolver: {
|
|
15
|
+
root: string;
|
|
16
|
+
type: string;
|
|
17
|
+
pitchClasses: string[];
|
|
18
|
+
};
|
|
19
|
+
durationMs: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ChordResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
chordName: string;
|
|
24
|
+
root: string;
|
|
25
|
+
notes: string[];
|
|
26
|
+
inversion?: number;
|
|
27
|
+
keyboard: ChordResultKeyboard;
|
|
28
|
+
telemetry: ChordResultTelemetry;
|
|
29
|
+
svg?: string;
|
|
30
|
+
midi?: Uint8Array;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function processChordRequest(request: ChordRequest): ChordResult;
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { parseChordDescription } from "./parser/natural-language";
|
|
2
|
+
import { resolveChord } from "./resolver/chord-resolver";
|
|
3
|
+
import { calculateLayout } from "./resolver/auto-layout";
|
|
4
|
+
import { computeKeyboard, computeSvgDimensions } from "./engine/keyboard-layout";
|
|
5
|
+
import { mapHighlights, normalizeNote } from "./engine/highlight-mapper";
|
|
6
|
+
import { generateMidiFile } from "./audio/midi-export";
|
|
7
|
+
import { logChordRequest } from "./logging";
|
|
8
|
+
import { WHITE_KEY_RY, BLACK_KEY_RY, DEFAULT_STROKE, DEFAULT_STROKE_WIDTH, } from "./engine/svg-constants";
|
|
9
|
+
// ── SVG builder ───────────────────────────────────────────────────
|
|
10
|
+
function buildSvgString(keys, fills, svgWidth, svgHeight) {
|
|
11
|
+
const whiteRects = [];
|
|
12
|
+
const blackRects = [];
|
|
13
|
+
for (let i = 0; i < keys.length; i++) {
|
|
14
|
+
const k = keys[i];
|
|
15
|
+
const fill = fills[i];
|
|
16
|
+
const ry = k.isBlack ? BLACK_KEY_RY : WHITE_KEY_RY;
|
|
17
|
+
const rect = `<rect x="${k.x}" y="${k.y}" width="${k.width}" height="${k.height}" rx="${ry}" ry="${ry}" fill="${fill}" stroke="${DEFAULT_STROKE}" stroke-width="${DEFAULT_STROKE_WIDTH}"/>`;
|
|
18
|
+
if (k.isBlack) {
|
|
19
|
+
blackRects.push(rect);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
whiteRects.push(rect);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [
|
|
26
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}">`,
|
|
27
|
+
...whiteRects,
|
|
28
|
+
...blackRects,
|
|
29
|
+
`</svg>`,
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
32
|
+
import { PC_SEMITONES as NOTE_SEMITONES } from "./engine/note-spelling";
|
|
33
|
+
// ── Main pipeline ─────────────────────────────────────────────────
|
|
34
|
+
export function processChordRequest(request) {
|
|
35
|
+
const start = performance.now();
|
|
36
|
+
// Default error result
|
|
37
|
+
const errorResult = (msg) => ({
|
|
38
|
+
success: false,
|
|
39
|
+
chordName: "",
|
|
40
|
+
root: "",
|
|
41
|
+
notes: [],
|
|
42
|
+
keyboard: { startFrom: "C", size: 8, highlightKeys: [] },
|
|
43
|
+
telemetry: {
|
|
44
|
+
parser: "chord",
|
|
45
|
+
resolver: { root: "", type: "", pitchClasses: [] },
|
|
46
|
+
durationMs: performance.now() - start,
|
|
47
|
+
},
|
|
48
|
+
errorMessage: msg,
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const { input, format: outputFormat, logConfig } = request;
|
|
52
|
+
if (!input || !input.trim()) {
|
|
53
|
+
return errorResult("No chord input provided");
|
|
54
|
+
}
|
|
55
|
+
// 1. Parse
|
|
56
|
+
const parsed = parseChordDescription(input);
|
|
57
|
+
if (!parsed.chordName) {
|
|
58
|
+
return errorResult("Could not extract a chord name from the input");
|
|
59
|
+
}
|
|
60
|
+
// 2. Resolve
|
|
61
|
+
const resolved = resolveChord(parsed.chordName, parsed.inversion);
|
|
62
|
+
const pitchClasses = resolved.notes; // display names (Bb, Eb, etc.)
|
|
63
|
+
const keyboardPitchClasses = pitchClasses.map(normalizeNote); // sharps for keyboard internals
|
|
64
|
+
// 3. Layout
|
|
65
|
+
const layoutFormat = parsed.format ?? "compact";
|
|
66
|
+
const layout = calculateLayout(keyboardPitchClasses, {
|
|
67
|
+
padding: parsed.padding,
|
|
68
|
+
startingNote: parsed.startingNote,
|
|
69
|
+
spanFrom: parsed.spanFrom,
|
|
70
|
+
spanTo: parsed.spanTo,
|
|
71
|
+
});
|
|
72
|
+
// 4. Compute keyboard keys
|
|
73
|
+
const keys = computeKeyboard(layout.startFrom, layout.size, layoutFormat);
|
|
74
|
+
// 5. Build octave-qualified highlight keys
|
|
75
|
+
const baseOctave = 4;
|
|
76
|
+
const highlightKeys = [];
|
|
77
|
+
const midiNoteNames = [];
|
|
78
|
+
// Max playable hand span: octave + fifth (~19 semitones)
|
|
79
|
+
const MAX_SPAN = 28;
|
|
80
|
+
// Build ascending semitone sequence for the chord notes
|
|
81
|
+
let prevSemitone = -1;
|
|
82
|
+
let currentOctave = baseOctave + layout.chordOctave;
|
|
83
|
+
// Step 1: naive ascending octave assignment
|
|
84
|
+
const assigned = [];
|
|
85
|
+
for (let i = 0; i < pitchClasses.length; i++) {
|
|
86
|
+
const pc = pitchClasses[i];
|
|
87
|
+
const kpc = keyboardPitchClasses[i];
|
|
88
|
+
const semitone = NOTE_SEMITONES[pc];
|
|
89
|
+
if (semitone === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
if (prevSemitone !== -1 && semitone <= prevSemitone) {
|
|
92
|
+
currentOctave++;
|
|
93
|
+
}
|
|
94
|
+
prevSemitone = semitone;
|
|
95
|
+
assigned.push({ pc, kpc, semitone, octave: currentOctave });
|
|
96
|
+
}
|
|
97
|
+
// Step 2: compact — fold notes down if span exceeds playable range
|
|
98
|
+
if (assigned.length > 1) {
|
|
99
|
+
const baseSemi = assigned[0].semitone + assigned[0].octave * 12;
|
|
100
|
+
for (let i = 1; i < assigned.length; i++) {
|
|
101
|
+
const noteSemi = assigned[i].semitone + assigned[i].octave * 12;
|
|
102
|
+
if (noteSemi - baseSemi > MAX_SPAN && assigned[i].octave > assigned[0].octave) {
|
|
103
|
+
assigned[i].octave--;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const a of assigned) {
|
|
108
|
+
midiNoteNames.push(`${a.pc}${a.octave}`);
|
|
109
|
+
const relOctave = a.octave - baseOctave;
|
|
110
|
+
highlightKeys.push(`${a.kpc}:${relOctave}`);
|
|
111
|
+
}
|
|
112
|
+
// 6. Map highlight fills
|
|
113
|
+
const fills = mapHighlights(keys, highlightKeys);
|
|
114
|
+
// 7. Build result
|
|
115
|
+
const result = {
|
|
116
|
+
success: true,
|
|
117
|
+
chordName: parsed.chordName,
|
|
118
|
+
root: resolved.root,
|
|
119
|
+
notes: midiNoteNames,
|
|
120
|
+
inversion: parsed.inversion,
|
|
121
|
+
keyboard: {
|
|
122
|
+
startFrom: layout.startFrom,
|
|
123
|
+
size: layout.size,
|
|
124
|
+
highlightKeys,
|
|
125
|
+
},
|
|
126
|
+
telemetry: {
|
|
127
|
+
parser: "chord",
|
|
128
|
+
resolver: {
|
|
129
|
+
root: resolved.root,
|
|
130
|
+
type: resolved.type,
|
|
131
|
+
pitchClasses,
|
|
132
|
+
},
|
|
133
|
+
durationMs: performance.now() - start,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
// 8. SVG output
|
|
137
|
+
if (outputFormat === "svg") {
|
|
138
|
+
const dims = computeSvgDimensions(layout.size, layoutFormat);
|
|
139
|
+
result.svg = buildSvgString(keys, fills, dims.width, dims.height);
|
|
140
|
+
}
|
|
141
|
+
// 9. MIDI output
|
|
142
|
+
if (outputFormat === "midi") {
|
|
143
|
+
result.midi = generateMidiFile({ notes: pitchClasses });
|
|
144
|
+
}
|
|
145
|
+
// 10. Optional logging
|
|
146
|
+
if (logConfig) {
|
|
147
|
+
const logEntry = {
|
|
148
|
+
input,
|
|
149
|
+
pipeline: {
|
|
150
|
+
parser: "chord",
|
|
151
|
+
parsed: {
|
|
152
|
+
chordName: parsed.chordName,
|
|
153
|
+
inversion: parsed.inversion,
|
|
154
|
+
allInversions: parsed.allInversions,
|
|
155
|
+
startingNote: parsed.startingNote,
|
|
156
|
+
startingDegree: parsed.startingDegree,
|
|
157
|
+
bassNote: parsed.bassNote,
|
|
158
|
+
bassDegree: parsed.bassDegree,
|
|
159
|
+
spanFrom: parsed.spanFrom,
|
|
160
|
+
spanTo: parsed.spanTo,
|
|
161
|
+
padding: parsed.padding,
|
|
162
|
+
chordOctaveShift: parsed.chordOctaveShift,
|
|
163
|
+
bassOctaveShift: parsed.bassOctaveShift,
|
|
164
|
+
styleHint: parsed.styleHint,
|
|
165
|
+
format: parsed.format,
|
|
166
|
+
showNoteNames: parsed.showNoteNames,
|
|
167
|
+
fingering: parsed.fingering,
|
|
168
|
+
autoFingering: parsed.autoFingering,
|
|
169
|
+
},
|
|
170
|
+
resolver: {
|
|
171
|
+
method: "tonal",
|
|
172
|
+
type: resolved.type,
|
|
173
|
+
root: resolved.root,
|
|
174
|
+
pitchClasses,
|
|
175
|
+
},
|
|
176
|
+
voicing: {
|
|
177
|
+
attempted: false,
|
|
178
|
+
found: false,
|
|
179
|
+
},
|
|
180
|
+
layout: {
|
|
181
|
+
startFrom: layout.startFrom,
|
|
182
|
+
size: layout.size,
|
|
183
|
+
chordOctave: layout.chordOctave,
|
|
184
|
+
},
|
|
185
|
+
output: {
|
|
186
|
+
notes: midiNoteNames,
|
|
187
|
+
highlightKeys,
|
|
188
|
+
format: outputFormat,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
success: true,
|
|
192
|
+
durationMs: performance.now() - start,
|
|
193
|
+
};
|
|
194
|
+
logChordRequest(logEntry, logConfig);
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
+
return errorResult(msg);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FormTemplate {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
numerals: string[];
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const FORM_TEMPLATES: FormTemplate[];
|
|
8
|
+
/**
|
|
9
|
+
* Find a form template by ID, name, or keyword alias.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findTemplate(query: string): FormTemplate | undefined;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const FORM_TEMPLATES = [
|
|
2
|
+
{
|
|
3
|
+
id: "ii-V-I",
|
|
4
|
+
name: "ii-V-I",
|
|
5
|
+
numerals: ["ii7", "V7", "Imaj7"],
|
|
6
|
+
description: "The most common jazz cadence",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: "minor-ii-V-i",
|
|
10
|
+
name: "Minor ii-V-i",
|
|
11
|
+
numerals: ["iiø7", "V7", "i7"],
|
|
12
|
+
description: "Minor key ii-V-i cadence",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "I-vi-ii-V",
|
|
16
|
+
name: "I-vi-ii-V",
|
|
17
|
+
numerals: ["Imaj7", "vi7", "ii7", "V7"],
|
|
18
|
+
description: "Classic turnaround (rhythm changes A section)",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "iii-vi-ii-V",
|
|
22
|
+
name: "iii-vi-ii-V",
|
|
23
|
+
numerals: ["iii7", "vi7", "ii7", "V7"],
|
|
24
|
+
description: "Extended turnaround from the iii chord",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "blues",
|
|
28
|
+
name: "12-Bar Blues",
|
|
29
|
+
numerals: [
|
|
30
|
+
"I7", "IV7", "I7", "I7",
|
|
31
|
+
"IV7", "IV7", "I7", "I7",
|
|
32
|
+
"V7", "IV7", "I7", "V7",
|
|
33
|
+
],
|
|
34
|
+
description: "Standard 12-bar blues progression",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "jazz-blues",
|
|
38
|
+
name: "Jazz Blues",
|
|
39
|
+
numerals: [
|
|
40
|
+
"I7", "IV7", "I7", "vi7",
|
|
41
|
+
"ii7", "V7", "I7", "vi7",
|
|
42
|
+
"ii7", "V7", "I7", "ii7", "V7",
|
|
43
|
+
],
|
|
44
|
+
description: "Jazz blues with ii-V turnarounds",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "rhythm-changes",
|
|
48
|
+
name: "Rhythm Changes (A)",
|
|
49
|
+
numerals: ["Imaj7", "vi7", "ii7", "V7", "iii7", "vi7", "ii7", "V7"],
|
|
50
|
+
description: "A section of rhythm changes (I Got Rhythm)",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "modal-vamp",
|
|
54
|
+
name: "Modal Vamp",
|
|
55
|
+
numerals: ["i7", "bVII7"],
|
|
56
|
+
description: "Two-chord modal vamp (So What / Impressions style)",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "backdoor-ii-V",
|
|
60
|
+
name: "Backdoor ii-V",
|
|
61
|
+
numerals: ["iv7", "bVII7", "Imaj7"],
|
|
62
|
+
description: "Backdoor resolution via bVII to I",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "tritone-sub",
|
|
66
|
+
name: "Tritone Sub ii-V-I",
|
|
67
|
+
numerals: ["ii7", "bII7", "Imaj7"],
|
|
68
|
+
description: "ii-V-I with tritone substitution on the V",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
// Keyword aliases for template lookup
|
|
72
|
+
const TEMPLATE_ALIASES = {
|
|
73
|
+
"blues": "blues",
|
|
74
|
+
"jazzblues": "jazz-blues",
|
|
75
|
+
"rhythmchanges": "rhythm-changes",
|
|
76
|
+
"rhythmchange": "rhythm-changes",
|
|
77
|
+
"turnaround": "I-vi-ii-V",
|
|
78
|
+
"modalvamp": "modal-vamp",
|
|
79
|
+
"backdoor": "backdoor-ii-V",
|
|
80
|
+
"tritonesub": "tritone-sub",
|
|
81
|
+
"tritonesubstitution": "tritone-sub",
|
|
82
|
+
"minoriivi": "minor-ii-V-i",
|
|
83
|
+
"minorii-v-i": "minor-ii-V-i",
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Find a form template by ID, name, or keyword alias.
|
|
87
|
+
*/
|
|
88
|
+
export function findTemplate(query) {
|
|
89
|
+
const lower = query.toLowerCase().replace(/[\s\-–—]+/g, "");
|
|
90
|
+
// Exact ID match
|
|
91
|
+
const byId = FORM_TEMPLATES.find((t) => t.id.replace(/-/g, "").toLowerCase() === lower);
|
|
92
|
+
if (byId)
|
|
93
|
+
return byId;
|
|
94
|
+
// Alias match
|
|
95
|
+
const aliasId = TEMPLATE_ALIASES[lower];
|
|
96
|
+
if (aliasId) {
|
|
97
|
+
return FORM_TEMPLATES.find((t) => t.id === aliasId);
|
|
98
|
+
}
|
|
99
|
+
// Fuzzy name match
|
|
100
|
+
return FORM_TEMPLATES.find((t) => {
|
|
101
|
+
const name = t.name.toLowerCase().replace(/[\s\-–—]+/g, "");
|
|
102
|
+
return name.includes(lower) || lower.includes(name);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=form-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-templates.js","sourceRoot":"","sources":["../../src/progression/form-templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { resolveProgression, tokenizeProgression } from "./roman-numeral";
|
|
2
|
+
export { FORM_TEMPLATES, findTemplate } from "./form-templates";
|
|
3
|
+
export type { FormTemplate } from "./form-templates";
|
|
4
|
+
export { resolveProgressionRequest, } from "./progression-resolver";
|
|
5
|
+
export type { ProgressionRequest, ProgressionResult, ProgressionExample, ProgressionChord, } from "./progression-resolver";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/progression/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EACL,yBAAyB,GAC1B,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ProgressionChord {
|
|
2
|
+
symbol: string;
|
|
3
|
+
root: string;
|
|
4
|
+
notes: string[];
|
|
5
|
+
voicingStyle?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProgressionExample {
|
|
8
|
+
label: string;
|
|
9
|
+
chords: ProgressionChord[];
|
|
10
|
+
}
|
|
11
|
+
export interface ProgressionResult {
|
|
12
|
+
progressionName: string;
|
|
13
|
+
key: string;
|
|
14
|
+
examples: ProgressionExample[];
|
|
15
|
+
}
|
|
16
|
+
export interface ProgressionRequest {
|
|
17
|
+
/** Roman numeral tokens or a form template name */
|
|
18
|
+
progression: string;
|
|
19
|
+
/** Musical key, e.g. "G", "Db" */
|
|
20
|
+
key: string;
|
|
21
|
+
/** Number of examples to generate */
|
|
22
|
+
numExamples?: number;
|
|
23
|
+
/** Style hint: artist name or voicing style keyword */
|
|
24
|
+
styleHint?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a progression request into multiple voicing examples.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveProgressionRequest(req: ProgressionRequest): ProgressionResult;
|