music-jsx-compiler 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 music-jsx-compiler contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # music-jsx-compiler
2
+
3
+ **Write chord charts in JSX. Get MusicXML out.**
4
+
5
+ A tiny component framework for building MusicXML from familiar-looking JSX — loops, sections, and chord bars instead of hundreds of lines of XML. Built for humans *and* the LLMs that help them write music.
6
+
7
+ ```bash
8
+ npm install music-jsx-compiler
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Why this exists
14
+
15
+ MusicXML is precise, but verbose. An eight-bar verse with four chords per bar can balloon into hundreds of lines — painful to write by hand and expensive to generate with AI.
16
+
17
+ With **music-jsx-compiler**, you describe structure once and let the compiler expand it:
18
+
19
+ ```tsx
20
+ <Song tempo={120} key="C" time="4/4">
21
+ <Loop times={8}>
22
+ <ChordBar chords={["Am", "F", "C", "G"]} />
23
+ </Loop>
24
+ <Bridge>
25
+ <Comment text="only guitar for the 2 next bars!" />
26
+ <ChordBar chords={["Dm", "G", "Em", "Am"]} />
27
+ </Bridge>
28
+ </Song>
29
+ ```
30
+
31
+ That becomes **9 measures** (8 looped bars + 1 bridge) of valid **MusicXML 4.0** with harmony symbols, rehearsal marks, and directions — roughly **10× fewer tokens** than writing the XML yourself.
32
+
33
+ ---
34
+
35
+ ## Quick start
36
+
37
+ **1. Install**
38
+
39
+ ```bash
40
+ npm install music-jsx-compiler
41
+ ```
42
+
43
+ **2. Configure TypeScript** (so `.tsx` files use this JSX runtime)
44
+
45
+ ```json
46
+ {
47
+ "compilerOptions": {
48
+ "jsx": "react-jsx",
49
+ "jsxImportSource": "music-jsx-compiler"
50
+ }
51
+ }
52
+ ```
53
+
54
+ **3. Write a song**
55
+
56
+ ```tsx
57
+ import { Song, Loop, ChordBar, Words, compileToMusicXml } from "music-jsx-compiler";
58
+
59
+ const song = (
60
+ <Song tempo={120} key="C" time="4/4" title="My Song">
61
+ <Loop times={4}>
62
+ <Words text="Guitar should start playing here" />
63
+ <ChordBar chords={["C", "G", "Am", "F"]} />
64
+ </Loop>
65
+ </Song>
66
+ );
67
+
68
+ const musicXml = compileToMusicXml(song);
69
+ // → valid MusicXML string, ready to save or import into notation software
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Components
75
+
76
+ | Component | What it does |
77
+ |-----------|--------------|
78
+ | `Song` | Root element — set `tempo`, `key`, `time`, and optional `title` |
79
+ | `Loop` | Repeat its children `times` |
80
+ | `ChordBar` | One measure of chords — array length must match the beat count (e.g. 4 chords in 4/4) |
81
+ | `Bridge` | Section wrapper (adds a rehearsal mark; defaults to "Bridge") |
82
+ | `Section` | Named section wrapper with a custom `name` |
83
+ | `Words` | Lyric or direction text above the next bar |
84
+ | `Comment` | Rehearsal or performance note attached to the next bar |
85
+
86
+ ---
87
+
88
+ ## Chord symbols
89
+
90
+ Common notation just works:
91
+
92
+ `C` · `Am` · `F#m` · `Bb` · `G/B` · `Dm7` · `Cmaj7` · `Gsus4` · `Bdim` · `Caug` · and more
93
+
94
+ ---
95
+
96
+ ## Try the example
97
+
98
+ Clone the repo and run:
99
+
100
+ ```bash
101
+ npm install
102
+ npm run example # writes examples/output.musicxml
103
+ ```
104
+
105
+ ---
106
+
107
+ ## API
108
+
109
+ | Export | Description |
110
+ |--------|-------------|
111
+ | `compileToMusicXml(tree)` | Compile a JSX song tree to a MusicXML string |
112
+ | `Song`, `Loop`, `Bridge`, `Section`, `ChordBar`, `Words`, `Comment` | JSX components |
113
+ | `parseChord(symbol)` | Parse a chord string into structured parts |
114
+ | `flattenSong(tree)` | Expand loops/sections into a flat measure list |
115
+
116
+ ---
117
+
118
+ ## Requirements
119
+
120
+ - **Node.js** 18+
121
+ - **TypeScript** recommended (for `.tsx` and `jsxImportSource`)
122
+
123
+ ---
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1,2 @@
1
+ import type { ParsedChord } from "./types.js";
2
+ export declare function parseChord(symbol: string): ParsedChord;
@@ -0,0 +1,68 @@
1
+ const STEP_PATTERN = /^([A-G])([#b]?)/;
2
+ const SLASH_PATTERN = /\/([A-G])([#b]?)$/;
3
+ const KIND_ALIASES = {
4
+ "": { kind: "major", text: "" },
5
+ m: { kind: "minor", text: "m" },
6
+ min: { kind: "minor", text: "m" },
7
+ maj: { kind: "major", text: "maj" },
8
+ M: { kind: "major", text: "" },
9
+ "7": { kind: "dominant", text: "7" },
10
+ maj7: { kind: "major-seventh", text: "maj7" },
11
+ M7: { kind: "major-seventh", text: "maj7" },
12
+ m7: { kind: "minor-seventh", text: "m7" },
13
+ min7: { kind: "minor-seventh", text: "m7" },
14
+ dim: { kind: "diminished", text: "dim" },
15
+ o: { kind: "diminished", text: "dim" },
16
+ aug: { kind: "augmented", text: "aug" },
17
+ "+": { kind: "augmented", text: "+" },
18
+ sus2: { kind: "suspended-second", text: "sus2" },
19
+ sus4: { kind: "suspended-fourth", text: "sus4" },
20
+ sus: { kind: "suspended-fourth", text: "sus4" },
21
+ add9: { kind: "major", text: "add9" },
22
+ "6": { kind: "major-sixth", text: "6" },
23
+ m6: { kind: "minor-sixth", text: "m6" },
24
+ "9": { kind: "dominant-ninth", text: "9" },
25
+ m9: { kind: "minor-ninth", text: "m9" },
26
+ "11": { kind: "dominant-11th", text: "11" },
27
+ "13": { kind: "dominant-13th", text: "13" },
28
+ };
29
+ export function parseChord(symbol) {
30
+ const trimmed = symbol.trim();
31
+ if (!trimmed) {
32
+ throw new Error("Empty chord symbol");
33
+ }
34
+ const slashMatch = trimmed.match(SLASH_PATTERN);
35
+ const head = slashMatch ? trimmed.slice(0, -slashMatch[0].length) : trimmed;
36
+ const bass = slashMatch ? parsePitch(slashMatch[1], slashMatch[2]) : undefined;
37
+ const rootMatch = head.match(STEP_PATTERN);
38
+ if (!rootMatch) {
39
+ throw new Error(`Invalid chord symbol: ${symbol}`);
40
+ }
41
+ const root = parsePitch(rootMatch[1], rootMatch[2]);
42
+ const qualityText = head.slice(rootMatch[0].length);
43
+ const alias = KIND_ALIASES[qualityText];
44
+ if (!alias) {
45
+ throw new Error(`Unsupported chord quality "${qualityText}" in ${symbol}`);
46
+ }
47
+ const kindText = buildKindText(root.step, root.alter, alias.text, bass?.step, bass?.alter);
48
+ return {
49
+ symbol: trimmed,
50
+ rootStep: root.step,
51
+ rootAlter: root.alter,
52
+ kind: alias.kind,
53
+ kindText,
54
+ bassStep: bass?.step,
55
+ bassAlter: bass?.alter,
56
+ };
57
+ }
58
+ function parsePitch(step, accidental) {
59
+ const alter = accidental === "#" ? 1 : accidental === "b" ? -1 : 0;
60
+ return { step, alter };
61
+ }
62
+ function buildKindText(rootStep, rootAlter, quality, bassStep, bassAlter) {
63
+ const root = rootStep + (rootAlter === 1 ? "#" : rootAlter === -1 ? "b" : "");
64
+ const bass = bassStep === undefined
65
+ ? ""
66
+ : `/${bassStep}${bassAlter === 1 ? "#" : bassAlter === -1 ? "b" : ""}`;
67
+ return `${root}${quality}${bass}`;
68
+ }
@@ -0,0 +1,2 @@
1
+ import type { MusicNode } from "./types.js";
2
+ export declare function compileToMusicXml(root: MusicNode): string;
@@ -0,0 +1,98 @@
1
+ import { chordDuration, escapeXml, flattenSong, renderHarmony, renderWords, validateMeasures, } from "./flatten.js";
2
+ import { keySignatureFifths } from "./key-signature.js";
3
+ const DIVISIONS = 4;
4
+ export function compileToMusicXml(root) {
5
+ const { settings, measures } = flattenSong(root);
6
+ validateMeasures(settings, measures);
7
+ let previousSection;
8
+ const measureXml = measures
9
+ .map((measure, index) => {
10
+ const showSection = measure.section !== undefined && measure.section !== previousSection;
11
+ if (measure.section !== undefined) {
12
+ previousSection = measure.section;
13
+ }
14
+ return renderMeasure(measure, index, settings, showSection);
15
+ })
16
+ .join("\n");
17
+ const title = settings.title ?? "Untitled";
18
+ return `<?xml version="1.0" encoding="UTF-8"?>
19
+ <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
20
+ <score-partwise version="4.0">
21
+ <work>
22
+ <work-title>${escapeXml(title)}</work-title>
23
+ </work>
24
+ <part-list>
25
+ <score-part id="P1">
26
+ <part-name>Chords</part-name>
27
+ </score-part>
28
+ </part-list>
29
+ <part id="P1">
30
+ ${measureXml}
31
+ </part>
32
+ </score-partwise>
33
+ `;
34
+ }
35
+ function renderMeasure(measure, index, settings, showSection) {
36
+ const measureNumber = index + 1;
37
+ const attributes = index === 0
38
+ ? `
39
+ <attributes>
40
+ <divisions>${DIVISIONS}</divisions>
41
+ <key>
42
+ <fifths>${keySignatureFifths(settings.key)}</fifths>
43
+ </key>
44
+ <time>
45
+ <beats>${settings.beatsPerMeasure}</beats>
46
+ <beat-type>${settings.beatType}</beat-type>
47
+ </time>
48
+ </attributes>
49
+ <direction placement="above">
50
+ <direction-type>
51
+ <metronome>
52
+ <beat-unit>quarter</beat-unit>
53
+ <per-minute>${settings.tempo}</per-minute>
54
+ </metronome>
55
+ </direction-type>
56
+ <sound tempo="${settings.tempo}"/>
57
+ </direction>`
58
+ : "";
59
+ const sectionMark = showSection
60
+ ? `
61
+ <direction placement="above">
62
+ <direction-type>
63
+ <rehearsal>${escapeXml(measure.section)}</rehearsal>
64
+ </direction-type>
65
+ </direction>`
66
+ : "";
67
+ const comment = measure.comment ? `\n <!-- ${escapeXml(measure.comment)} -->` : "";
68
+ const words = measure.words ? `\n${renderWords(measure.words)}` : "";
69
+ const duration = chordDuration(settings, DIVISIONS);
70
+ const harmonies = measure.chords
71
+ .map((chord) => `${renderHarmony(chord)}
72
+ <note>
73
+ <rest/>
74
+ <duration>${duration}</duration>
75
+ <voice>1</voice>
76
+ <type>${noteType(settings.beatType)}</type>
77
+ </note>`)
78
+ .join("\n");
79
+ const restOnly = harmonies ||
80
+ ` <note><rest/><duration>${DIVISIONS * settings.beatsPerMeasure}</duration><voice>1</voice><type>whole</type></note>`;
81
+ return ` <measure number="${measureNumber}">${comment}${attributes}${sectionMark}${words}
82
+ ${restOnly}
83
+ </measure>`;
84
+ }
85
+ function noteType(beatType) {
86
+ switch (beatType) {
87
+ case 1:
88
+ return "whole";
89
+ case 2:
90
+ return "half";
91
+ case 4:
92
+ return "quarter";
93
+ case 8:
94
+ return "eighth";
95
+ default:
96
+ return "quarter";
97
+ }
98
+ }
@@ -0,0 +1,29 @@
1
+ import type { SongNode, LoopNode, SectionNode, ChordBarNode, CommentNode, WordsNode } from "./types.js";
2
+ export declare function Song(props: {
3
+ tempo: number;
4
+ key: string;
5
+ time: `${number}/${number}`;
6
+ title?: string;
7
+ children?: SongNode["children"] | SongNode["children"][number];
8
+ }): SongNode;
9
+ export declare function Loop(props: {
10
+ times: number;
11
+ children?: LoopNode["children"] | LoopNode["children"][number];
12
+ }): LoopNode;
13
+ export declare function Bridge(props: {
14
+ name?: string;
15
+ children?: SectionNode["children"] | SectionNode["children"][number];
16
+ }): SectionNode;
17
+ export declare function Section(props: {
18
+ name: string;
19
+ children?: SectionNode["children"] | SectionNode["children"][number];
20
+ }): SectionNode;
21
+ export declare function ChordBar(props: {
22
+ chords: string[];
23
+ }): ChordBarNode;
24
+ export declare function Comment(props: {
25
+ text: string;
26
+ }): CommentNode;
27
+ export declare function Words(props: {
28
+ text: string;
29
+ }): WordsNode;
@@ -0,0 +1,41 @@
1
+ import { collectChildren } from "./jsx-runtime.js";
2
+ export function Song(props) {
3
+ return {
4
+ type: "song",
5
+ tempo: props.tempo,
6
+ key: props.key,
7
+ time: props.time,
8
+ title: props.title,
9
+ children: collectChildren(props.children),
10
+ };
11
+ }
12
+ export function Loop(props) {
13
+ return {
14
+ type: "loop",
15
+ times: props.times,
16
+ children: collectChildren(props.children),
17
+ };
18
+ }
19
+ export function Bridge(props) {
20
+ return {
21
+ type: "section",
22
+ name: props.name ?? "Bridge",
23
+ children: collectChildren(props.children),
24
+ };
25
+ }
26
+ export function Section(props) {
27
+ return {
28
+ type: "section",
29
+ name: props.name,
30
+ children: collectChildren(props.children),
31
+ };
32
+ }
33
+ export function ChordBar(props) {
34
+ return { type: "chordBar", chords: props.chords };
35
+ }
36
+ export function Comment(props) {
37
+ return { type: "comment", text: props.text };
38
+ }
39
+ export function Words(props) {
40
+ return { type: "words", text: props.text };
41
+ }
@@ -0,0 +1,10 @@
1
+ import type { FlatMeasure, MusicNode, ParsedChord, SongSettings } from "./types.js";
2
+ export declare function flattenSong(root: MusicNode): {
3
+ settings: SongSettings;
4
+ measures: FlatMeasure[];
5
+ };
6
+ export declare function validateMeasures(settings: SongSettings, measures: FlatMeasure[]): void;
7
+ export declare function chordDuration(settings: SongSettings, divisions: number): number;
8
+ export declare function escapeXml(text: string): string;
9
+ export declare function renderWords(text: string): string;
10
+ export declare function renderHarmony(chord: ParsedChord): string;
@@ -0,0 +1,91 @@
1
+ import { parseChord } from "./chord-parser.js";
2
+ export function flattenSong(root) {
3
+ if (root.type !== "song") {
4
+ throw new Error("Root element must be <Song>");
5
+ }
6
+ const [beatsPerMeasure, beatType] = root.time.split("/").map(Number);
7
+ const settings = {
8
+ tempo: root.tempo,
9
+ key: root.key,
10
+ beatsPerMeasure,
11
+ beatType,
12
+ title: root.title,
13
+ };
14
+ const measures = [];
15
+ flattenNodes(root.children, measures);
16
+ return { settings, measures };
17
+ }
18
+ function flattenNodes(nodes, measures, section) {
19
+ let pendingComment;
20
+ let pendingWords;
21
+ for (const node of nodes) {
22
+ switch (node.type) {
23
+ case "loop":
24
+ for (let index = 0; index < node.times; index++) {
25
+ flattenNodes(node.children, measures, section);
26
+ }
27
+ break;
28
+ case "section":
29
+ flattenNodes(node.children, measures, node.name);
30
+ break;
31
+ case "chordBar":
32
+ measures.push({
33
+ chords: node.chords.map(parseChord),
34
+ section,
35
+ comment: pendingComment,
36
+ words: pendingWords,
37
+ });
38
+ pendingComment = undefined;
39
+ pendingWords = undefined;
40
+ break;
41
+ case "comment":
42
+ pendingComment = node.text;
43
+ break;
44
+ case "words":
45
+ pendingWords = node.text;
46
+ break;
47
+ default:
48
+ throw new Error(`Unexpected node in flatten: ${node.type}`);
49
+ }
50
+ }
51
+ }
52
+ export function validateMeasures(settings, measures) {
53
+ for (const [index, measure] of measures.entries()) {
54
+ if (measure.chords.length === 0)
55
+ continue;
56
+ if (measure.chords.length !== settings.beatsPerMeasure) {
57
+ throw new Error(`Measure ${index + 1}: expected ${settings.beatsPerMeasure} chords for ${settings.beatsPerMeasure}/${settings.beatType}, got ${measure.chords.length}`);
58
+ }
59
+ }
60
+ }
61
+ export function chordDuration(settings, divisions) {
62
+ return Math.round((divisions * 4) / settings.beatType);
63
+ }
64
+ export function escapeXml(text) {
65
+ return text
66
+ .replaceAll("&", "&amp;")
67
+ .replaceAll("<", "&lt;")
68
+ .replaceAll(">", "&gt;")
69
+ .replaceAll('"', "&quot;");
70
+ }
71
+ export function renderWords(text) {
72
+ return ` <direction placement="above">
73
+ <direction-type>
74
+ <words>${escapeXml(text)}</words>
75
+ </direction-type>
76
+ </direction>`;
77
+ }
78
+ export function renderHarmony(chord) {
79
+ const rootAlter = chord.rootAlter === 0 ? "" : `\n <root-alter>${chord.rootAlter}</root-alter>`;
80
+ const bass = chord.bassStep === undefined
81
+ ? ""
82
+ : `\n <bass>\n <bass-step>${chord.bassStep}</bass-step>${chord.bassAlter && chord.bassAlter !== 0
83
+ ? `\n <bass-alter>${chord.bassAlter}</bass-alter>`
84
+ : ""}\n </bass>`;
85
+ return ` <harmony>
86
+ <root>
87
+ <root-step>${chord.rootStep}</root-step>${rootAlter}
88
+ </root>
89
+ <kind text="${escapeXml(chord.kindText)}">${chord.kind}</kind>${bass}
90
+ </harmony>`;
91
+ }
@@ -0,0 +1,6 @@
1
+ export type * from "./types.js";
2
+ export { jsx, jsxs, Fragment } from "./jsx-runtime.js";
3
+ export { Song, Loop, Bridge, Section, ChordBar, Comment, Words } from "./components.js";
4
+ export { compileToMusicXml } from "./compile.js";
5
+ export { parseChord } from "./chord-parser.js";
6
+ export { flattenSong } from "./flatten.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { jsx, jsxs, Fragment } from "./jsx-runtime.js";
2
+ export { Song, Loop, Bridge, Section, ChordBar, Comment, Words } from "./components.js";
3
+ export { compileToMusicXml } from "./compile.js";
4
+ export { parseChord } from "./chord-parser.js";
5
+ export { flattenSong } from "./flatten.js";
@@ -0,0 +1,11 @@
1
+ import type { MusicNode } from "./types.js";
2
+ type ComponentProps = Record<string, unknown> & {
3
+ children?: MusicNode | MusicNode[];
4
+ };
5
+ export declare function jsx(type: string | ((props: ComponentProps) => MusicNode), props: ComponentProps | null, key?: string): MusicNode;
6
+ export declare function jsxs(type: string | ((props: ComponentProps) => MusicNode), props: ComponentProps | null, key?: string): MusicNode;
7
+ export declare function Fragment(props: {
8
+ children?: MusicNode | MusicNode[];
9
+ }): MusicNode[];
10
+ export declare function collectChildren(children: MusicNode | MusicNode[] | undefined): MusicNode[];
11
+ export {};
@@ -0,0 +1,20 @@
1
+ export function jsx(type, props, key) {
2
+ if (typeof type === "function") {
3
+ return type({ ...props, key });
4
+ }
5
+ throw new Error(`Unknown element: ${String(type)}`);
6
+ }
7
+ export function jsxs(type, props, key) {
8
+ return jsx(type, props, key);
9
+ }
10
+ export function Fragment(props) {
11
+ return normalizeChildren(props.children);
12
+ }
13
+ function normalizeChildren(children) {
14
+ if (children === undefined)
15
+ return [];
16
+ return Array.isArray(children) ? children : [children];
17
+ }
18
+ export function collectChildren(children) {
19
+ return normalizeChildren(children);
20
+ }
@@ -0,0 +1 @@
1
+ export declare function keySignatureFifths(key: string): number;
@@ -0,0 +1,39 @@
1
+ const KEY_FIFTHS = {
2
+ Cb: -7,
3
+ Gb: -6,
4
+ Db: -5,
5
+ Ab: -4,
6
+ Eb: -3,
7
+ Bb: -2,
8
+ F: -1,
9
+ C: 0,
10
+ G: 1,
11
+ D: 2,
12
+ A: 3,
13
+ E: 4,
14
+ B: 5,
15
+ "F#": 6,
16
+ "C#": 7,
17
+ Am: 0,
18
+ Em: 1,
19
+ Bm: 2,
20
+ "F#m": 3,
21
+ "C#m": 4,
22
+ "G#m": 5,
23
+ "D#m": 6,
24
+ "A#m": 7,
25
+ Dm: -1,
26
+ Gm: -2,
27
+ Cm: -3,
28
+ Fm: -4,
29
+ Bbm: -5,
30
+ Ebm: -6,
31
+ Abm: -7,
32
+ };
33
+ export function keySignatureFifths(key) {
34
+ const fifths = KEY_FIFTHS[key];
35
+ if (fifths === undefined) {
36
+ throw new Error(`Unsupported key signature: ${key}`);
37
+ }
38
+ return fifths;
39
+ }
@@ -0,0 +1,54 @@
1
+ export type TimeSignature = `${number}/${number}`;
2
+ export type SongNode = {
3
+ type: "song";
4
+ tempo: number;
5
+ key: string;
6
+ time: TimeSignature;
7
+ title?: string;
8
+ children: MusicNode[];
9
+ };
10
+ export type LoopNode = {
11
+ type: "loop";
12
+ times: number;
13
+ children: MusicNode[];
14
+ };
15
+ export type SectionNode = {
16
+ type: "section";
17
+ name: string;
18
+ children: MusicNode[];
19
+ };
20
+ export type ChordBarNode = {
21
+ type: "chordBar";
22
+ chords: string[];
23
+ };
24
+ export type CommentNode = {
25
+ type: "comment";
26
+ text: string;
27
+ };
28
+ export type WordsNode = {
29
+ type: "words";
30
+ text: string;
31
+ };
32
+ export type MusicNode = SongNode | LoopNode | SectionNode | ChordBarNode | CommentNode | WordsNode;
33
+ export type ParsedChord = {
34
+ symbol: string;
35
+ rootStep: string;
36
+ rootAlter: number;
37
+ kind: string;
38
+ kindText: string;
39
+ bassStep?: string;
40
+ bassAlter?: number;
41
+ };
42
+ export type FlatMeasure = {
43
+ chords: ParsedChord[];
44
+ section?: string;
45
+ comment?: string;
46
+ words?: string;
47
+ };
48
+ export type SongSettings = {
49
+ tempo: number;
50
+ key: string;
51
+ beatsPerMeasure: number;
52
+ beatType: number;
53
+ title?: string;
54
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "music-jsx-compiler",
3
+ "version": "0.1.0",
4
+ "description": "JSX-like component framework for building MusicXML",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "engines": {
9
+ "node": ">=18"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./jsx-runtime": {
24
+ "types": "./dist/jsx-runtime.d.ts",
25
+ "import": "./dist/jsx-runtime.js"
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "prepack": "npm run build",
32
+ "example": "tsx --tsconfig tsconfig.examples.json examples/example.tsx",
33
+ "check": "tsc --noEmit && tsx src/self-check.ts && npm run example"
34
+ },
35
+ "keywords": [
36
+ "musicxml",
37
+ "jsx",
38
+ "music",
39
+ "chords",
40
+ "compiler",
41
+ "llm"
42
+ ],
43
+ "devDependencies": {
44
+ "tsx": "^4.19.0",
45
+ "typescript": "^5.7.0"
46
+ }
47
+ }