@pepperhorn/chordl-voicing 0.0.0-stage → 0.2.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/dist/canonical.d.ts +22 -0
  4. package/dist/canonical.js +90 -0
  5. package/dist/canonical.js.map +1 -0
  6. package/dist/catalogue.d.ts +14 -0
  7. package/dist/catalogue.js +164 -0
  8. package/dist/catalogue.js.map +1 -0
  9. package/dist/checks.d.ts +8 -0
  10. package/dist/checks.js +150 -0
  11. package/dist/checks.js.map +1 -0
  12. package/dist/chordId.d.ts +16 -0
  13. package/dist/chordId.js +106 -0
  14. package/dist/chordId.js.map +1 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +8 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/cli/run.d.ts +6 -0
  19. package/dist/cli/run.js +438 -0
  20. package/dist/cli/run.js.map +1 -0
  21. package/dist/generated/chord.schema.json +29 -0
  22. package/dist/generated/voicing.schema.json +164 -0
  23. package/dist/hash.d.ts +4 -0
  24. package/dist/hash.js +34 -0
  25. package/dist/hash.js.map +1 -0
  26. package/dist/index.d.ts +16 -0
  27. package/dist/index.js +10 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/instruments.d.ts +3 -0
  30. package/dist/instruments.js +7 -0
  31. package/dist/instruments.js.map +1 -0
  32. package/dist/pitch.d.ts +16 -0
  33. package/dist/pitch.js +63 -0
  34. package/dist/pitch.js.map +1 -0
  35. package/dist/realization.d.ts +9 -0
  36. package/dist/realization.js +78 -0
  37. package/dist/realization.js.map +1 -0
  38. package/dist/schema.d.ts +18 -0
  39. package/dist/schema.js +35 -0
  40. package/dist/schema.js.map +1 -0
  41. package/dist/types.d.ts +87 -0
  42. package/dist/types.js +2 -0
  43. package/dist/types.js.map +1 -0
  44. package/docs/CANONICAL.md +36 -0
  45. package/package.json +57 -3
  46. package/schema/chord.schema.json +29 -0
  47. package/schema/voicing.schema.json +164 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PepperHorn Music / Creative Ranges Foundation
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 CHANGED
@@ -1,3 +1,155 @@
1
- # Temporary Holding Version
1
+ # @pepperhorn/chordl-voicing
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An open JSON format for chord voicings (fretted, keyboard, bowed), with TypeScript
4
+ types, JSON Schemas, a canonical formatter, content hashes, a catalogue validator
5
+ and a `chordl-voicing` command line tool.
6
+
7
+ A **chord file** holds one chord and all of its voicings. A **voicing** records which
8
+ notes sound, how they are realized on an instrument, who wrote it, and how it relates
9
+ to other voicings (revisions, supersession, duplicates). Files are plain JSON, one per
10
+ chord, designed to live in a git repository and be reviewed as diffs.
11
+
12
+ ## Schemas
13
+
14
+ | Schema id | Describes |
15
+ | --- | --- |
16
+ | `chordl.chord/1` | A chord file: the chord (`id`, `symbol`, `root`, `quality`, `bass`, `aliases`) and its `voicings` |
17
+ | `chordl.voicing/1` | One voicing: `notes`, a `realization`, `provenance`, `relations` and `hashes` |
18
+
19
+ Both are JSON Schema draft 2020-12, shipped as `schema/chord.schema.json` and
20
+ `schema/voicing.schema.json` (also importable as
21
+ `@pepperhorn/chordl-voicing/schema/chord.schema.json`).
22
+
23
+ Chord ids look like `C:maj7` or `C:7/E`: `<root>:<quality>[/<bass>]`. Pitches are
24
+ ASCII scientific pitch (`Eb4`, never `E♭4`). Each note carries a jazz degree such as
25
+ `I`, `bIII`, `#IV`.
26
+
27
+ ## Realizations
28
+
29
+ The `realization` of a voicing has a `kind` of `fretted`, `keyboard` or `bowed`.
30
+
31
+ Fretted. `strings` holds absolute frets (`null` is muted), in chordl-guitar string order:
32
+
33
+ ```json
34
+ {
35
+ "kind": "fretted",
36
+ "instrument": "guitar",
37
+ "tuning": ["E2", "A2", "D3", "G3", "B3", "E4"],
38
+ "strings": [null, 3, 2, 0, 0, 0],
39
+ "fingers": [null, 3, 2, null, null, null],
40
+ "baseFret": 1,
41
+ "barres": [],
42
+ "capo": null
43
+ }
44
+ ```
45
+
46
+ Keyboard. Pitches per hand; `fingering` is optional:
47
+
48
+ ```json
49
+ { "kind": "keyboard", "hands": { "left": ["C3", "G3"], "right": ["E4", "B4"] } }
50
+ ```
51
+
52
+ Bowed. One stop per sounding string:
53
+
54
+ ```json
55
+ {
56
+ "kind": "bowed",
57
+ "instrument": "violin",
58
+ "tuning": ["G3", "D4", "A4", "E5"],
59
+ "stops": [{ "string": 2, "pitch": "C5", "finger": 2 }],
60
+ "position": 1
61
+ }
62
+ ```
63
+
64
+ ## Library
65
+
66
+ ```ts
67
+ import {
68
+ validateChordFileSchema, checkVoicing, formatChordFile, isCanonical, withHashes,
69
+ } from "@pepperhorn/chordl-voicing";
70
+
71
+ const result = validateChordFileSchema(json);
72
+ if (!result.ok) console.error(result.errors); // [{ path, message }]
73
+ else {
74
+ for (const voicing of result.value.voicings) {
75
+ const { hard, flags } = checkVoicing(voicing); // hard = must fix, flags = review
76
+ }
77
+ const text = formatChordFile(result.value); // canonical JSON text
78
+ }
79
+ ```
80
+
81
+ Other exports: `parseChordId`, `formatChordId`, `chordIntervals`, `labelNotes`,
82
+ `soundingMidi`, `notesMatchRealization`, `shapeHash`, `pitchHash`, `withHashes`,
83
+ `chordFilePath`, `validateCatalogue` and the types (`ChordFile`, `Voicing`,
84
+ `Realization`, ...).
85
+
86
+ Hashes (`hashes.shape`, `hashes.pitch`) cover musical content only, so tier, status
87
+ and provenance edits never change them. The exact file form and hash rules are in
88
+ [docs/CANONICAL.md](./docs/CANONICAL.md).
89
+
90
+ ## CLI
91
+
92
+ Run it inside a catalogue checkout (files live under `chords/`):
93
+
94
+ ```sh
95
+ npx @pepperhorn/chordl-voicing <command>
96
+ ```
97
+
98
+ ### new
99
+
100
+ Create a voicing and add it to the chord's file (creating the file if needed):
101
+
102
+ ```sh
103
+ # Fretted: one character per string, or comma-separated; x = muted
104
+ chordl-voicing new --chord C:maj7 --instrument guitar --frets x32000 --author gh:alice
105
+
106
+ # Barre chords: --barre <fret>:<fromString>-<toString>, repeatable.
107
+ # Strings are numbered from 0 (the first string in the tuning, thickest on guitar).
108
+ # A barre counts as one finger, so a full F shape passes the four-finger check.
109
+ chordl-voicing new --chord F:maj --instrument guitar --frets 133211 \
110
+ --barre 1:0-5 --author gh:alice
111
+
112
+ # Keyboard: --notes "<left> | <right>", pitches space-separated.
113
+ # Without a "|", every note goes to the right hand.
114
+ chordl-voicing new --chord C:maj7 --instrument piano \
115
+ --notes "C3 G3 | E4 B4" --author gh:alice
116
+ ```
117
+
118
+ `--author` is a prefixed id such as `gh:alice`. `--tier` is `community` (default) or
119
+ `curated`. Note names and degrees are worked out from the chord, hashes are filled
120
+ in, and the voicing is checked before it is written. Bowed voicings are written by
121
+ hand.
122
+
123
+ ### format
124
+
125
+ ```sh
126
+ chordl-voicing format # rewrite every file under chords/ to canonical form
127
+ chordl-voicing format --check # print a diff and exit 1 if any file is not canonical
128
+ chordl-voicing format chords/C/C__maj7.json
129
+ ```
130
+
131
+ ### validate
132
+
133
+ ```sh
134
+ chordl-voicing validate [--base <dir>] [--licences <file>]
135
+ ```
136
+
137
+ Checks schema, canonical form, hashes, notes against realization, and catalogue rules.
138
+ With `--base`, also checks history against an earlier checkout (revisions must be
139
+ appended, not edited). Exits 1 on any error.
140
+
141
+ ### bundle
142
+
143
+ ```sh
144
+ chordl-voicing bundle --out dist --version v2026.10.1
145
+ ```
146
+
147
+ Writes `manifest.json`, `catalogue.json` and `by-instrument/<instrument>.json`.
148
+ `catalogue.json` holds every revision of every voicing, including retired ones;
149
+ `by-instrument/<instrument>.json` and the manifest counts cover live voicings only.
150
+
151
+ Exit codes: 0 success, 1 failure, 2 usage error.
152
+
153
+ ## Licence
154
+
155
+ MIT
@@ -0,0 +1,22 @@
1
+ import type { ChordFile } from "./types.js";
2
+ export declare const KEY_ORDER: {
3
+ readonly file: readonly ["schema", "chord", "voicings"];
4
+ readonly chord: readonly ["id", "symbol", "root", "quality", "bass", "aliases"];
5
+ readonly voicing: readonly ["schema", "id", "rev", "previous", "chordId", "status", "tier", "licence", "notes", "realization", "level", "provenance", "relations", "externalIds", "hashes"];
6
+ readonly note: readonly ["pitch", "degree"];
7
+ readonly fretted: readonly ["kind", "instrument", "tuning", "strings", "fingers", "baseFret", "barres", "capo"];
8
+ readonly barre: readonly ["fret", "fromString", "toString"];
9
+ readonly keyboard: readonly ["kind", "hands", "fingering"];
10
+ readonly hands: readonly ["left", "right"];
11
+ readonly fingering: readonly ["left", "right"];
12
+ readonly bowed: readonly ["kind", "instrument", "tuning", "stops", "position"];
13
+ readonly stop: readonly ["string", "pitch", "finger"];
14
+ readonly provenance: readonly ["source", "authorId", "createdAt", "changeNote", "basedOn", "alternativeTo"];
15
+ readonly relations: readonly ["supersedes", "supersededBy", "duplicateOf"];
16
+ readonly hashes: readonly ["shape", "pitch"];
17
+ };
18
+ export declare function formatChordFile(file: ChordFile): string;
19
+ export declare function isCanonical(text: string): {
20
+ ok: boolean;
21
+ expected: string;
22
+ };
@@ -0,0 +1,90 @@
1
+ // Key order for every object in a chord file, defined once. Mirrors the property order in types.ts.
2
+ export const KEY_ORDER = {
3
+ file: ["schema", "chord", "voicings"],
4
+ chord: ["id", "symbol", "root", "quality", "bass", "aliases"],
5
+ voicing: [
6
+ "schema", "id", "rev", "previous", "chordId", "status", "tier", "licence", "notes",
7
+ "realization", "level", "provenance", "relations", "externalIds", "hashes",
8
+ ],
9
+ note: ["pitch", "degree"],
10
+ fretted: ["kind", "instrument", "tuning", "strings", "fingers", "baseFret", "barres", "capo"],
11
+ barre: ["fret", "fromString", "toString"],
12
+ keyboard: ["kind", "hands", "fingering"],
13
+ hands: ["left", "right"],
14
+ fingering: ["left", "right"],
15
+ bowed: ["kind", "instrument", "tuning", "stops", "position"],
16
+ stop: ["string", "pitch", "finger"],
17
+ provenance: ["source", "authorId", "createdAt", "changeNote", "basedOn", "alternativeTo"],
18
+ relations: ["supersedes", "supersededBy", "duplicateOf"],
19
+ hashes: ["shape", "pitch"],
20
+ };
21
+ // Present keys in the given order; absent keys stay absent. Unknown keys are kept at the end
22
+ // (in their original order) so the formatter never drops data; the schema rejects them.
23
+ function order(o, keys, children = {}) {
24
+ if (o === null || typeof o !== "object" || Array.isArray(o))
25
+ return o;
26
+ const src = o;
27
+ // Null-prototype so a `__proto__` key from JSON.parse is plain data, and own-property checks
28
+ // so inherited names (`toString`, `constructor`) are never mistaken for keys.
29
+ const out = Object.create(null);
30
+ const has = (x, k) => Object.prototype.hasOwnProperty.call(x, k);
31
+ const emit = (k) => { out[k] = has(children, k) ? children[k](src[k]) : src[k]; };
32
+ for (const k of keys)
33
+ if (has(src, k) && src[k] !== undefined)
34
+ emit(k);
35
+ for (const k of Object.keys(src))
36
+ if (!has(out, k) && src[k] !== undefined)
37
+ emit(k);
38
+ return out;
39
+ }
40
+ const list = (f) => (v) => (Array.isArray(v) ? v.map(f) : v);
41
+ const K = KEY_ORDER;
42
+ function realization(v) {
43
+ const kind = v?.kind;
44
+ if (kind === "fretted")
45
+ return order(v, K.fretted, { barres: list((b) => order(b, K.barre)) });
46
+ if (kind === "keyboard") {
47
+ return order(v, K.keyboard, {
48
+ hands: (h) => order(h, K.hands),
49
+ fingering: (f) => order(f, K.fingering),
50
+ });
51
+ }
52
+ if (kind === "bowed")
53
+ return order(v, K.bowed, { stops: list((s) => order(s, K.stop)) });
54
+ return v;
55
+ }
56
+ const voicing = (v) => order(v, K.voicing, {
57
+ notes: list((n) => order(n, K.note)),
58
+ realization,
59
+ provenance: (p) => order(p, K.provenance),
60
+ relations: (r) => order(r, K.relations),
61
+ hashes: (h) => order(h, K.hashes),
62
+ });
63
+ const cmp = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : a.rev - b.rev);
64
+ export function formatChordFile(file) {
65
+ const sorted = Array.isArray(file.voicings) ? [...file.voicings].sort(cmp) : file.voicings;
66
+ const out = order({ ...file, voicings: sorted }, K.file, {
67
+ chord: (c) => order(c, K.chord),
68
+ voicings: list(voicing),
69
+ });
70
+ return JSON.stringify(out, null, 2) + "\n";
71
+ }
72
+ export function isCanonical(text) {
73
+ const normalised = text.replace(/^/, "").replace(/\r\n?/g, "\n");
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(normalised);
77
+ }
78
+ catch {
79
+ return { ok: false, expected: "" };
80
+ }
81
+ let expected;
82
+ try {
83
+ expected = formatChordFile(parsed);
84
+ }
85
+ catch {
86
+ return { ok: false, expected: "" };
87
+ }
88
+ return { ok: text === expected, expected };
89
+ }
90
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface CatalogueIssue {
2
+ path: string;
3
+ code: string;
4
+ message: string;
5
+ }
6
+ export interface CatalogueReport {
7
+ ok: boolean;
8
+ errors: CatalogueIssue[];
9
+ warnings: CatalogueIssue[];
10
+ }
11
+ export declare function chordFilePath(chordId: string): string;
12
+ export declare function validateCatalogue(head: Map<string, string>, base?: Map<string, string>, opts?: {
13
+ licences: string[];
14
+ }): CatalogueReport;
@@ -0,0 +1,164 @@
1
+ import { isCanonical } from "./canonical.js";
2
+ import { checkVoicing } from "./checks.js";
3
+ import { parseChordId } from "./chordId.js";
4
+ import { withHashes } from "./hash.js";
5
+ import { validateChordFileSchema } from "./schema.js";
6
+ // chords/<root dir>/<encoded id>.json ; `#`->`s`, `/`->`_over_`, `:`->`__` (raw `#` breaks raw/CDN URLs).
7
+ export function chordFilePath(chordId) {
8
+ const encode = (s) => s.replace(/#/g, "s").replace(/\//g, "_over_").replace(/:/g, "__");
9
+ const root = encode(chordId.split(":")[0]);
10
+ return `chords/${root}/${encode(chordId)}.json`;
11
+ }
12
+ const isChordPath = (p) => p.startsWith("chords/") && p.endsWith(".json");
13
+ const stripBom = (s) => s.replace(/^/, "");
14
+ // Key-sorted JSON, so history comparison ignores key order.
15
+ function stable(v) {
16
+ const sort = (x) => {
17
+ if (Array.isArray(x))
18
+ return x.map(sort);
19
+ if (x && typeof x === "object") {
20
+ return Object.fromEntries(Object.keys(x).sort().map((k) => [k, sort(x[k])]));
21
+ }
22
+ return x;
23
+ };
24
+ return JSON.stringify(sort(v));
25
+ }
26
+ // Voicing entries of a file keyed `id@rev`; tolerant of files that do not pass the schema.
27
+ function historyOf(files) {
28
+ const out = new Map();
29
+ for (const [path, text] of files) {
30
+ if (!isChordPath(path))
31
+ continue;
32
+ let data;
33
+ try {
34
+ data = JSON.parse(stripBom(text));
35
+ }
36
+ catch {
37
+ continue;
38
+ }
39
+ const voicings = data?.voicings;
40
+ if (!Array.isArray(voicings))
41
+ continue;
42
+ for (const v of voicings) {
43
+ if (v && typeof v === "object" && typeof v.id === "string")
44
+ out.set(`${v.id}@${v.rev}`, { path, json: stable(v) });
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ export function validateCatalogue(head, base, opts = { licences: ["CC0-1.0"] }) {
50
+ const errors = [];
51
+ const warnings = [];
52
+ const err = (path, code, message) => errors.push({ path, code, message });
53
+ const warn = (path, code, message) => warnings.push({ path, code, message });
54
+ const parsed = [];
55
+ for (const path of [...head.keys()].filter(isChordPath).sort()) {
56
+ const text = head.get(path);
57
+ let data;
58
+ try {
59
+ data = JSON.parse(stripBom(text));
60
+ }
61
+ catch (e) {
62
+ err(path, "schema", `Not valid JSON: ${e.message}`);
63
+ continue;
64
+ }
65
+ const res = validateChordFileSchema(data);
66
+ if (!res.ok) {
67
+ err(path, "schema", res.errors.map((e) => `${e.path || "/"}: ${e.message}`).join("; "));
68
+ continue;
69
+ }
70
+ parsed.push({ path, file: res.value });
71
+ }
72
+ const idFiles = new Map(); // voicing id -> first file
73
+ for (const { path, file } of parsed) {
74
+ if (!isCanonical(head.get(path)).ok)
75
+ err(path, "not-canonical", "File is not in canonical form; run the formatter");
76
+ const chordOk = parseChordId(file.chord.id) !== null;
77
+ if (!chordOk)
78
+ err(path, "chord-id", `Invalid chord id "${file.chord.id}"`);
79
+ else {
80
+ const p = parseChordId(file.chord.id);
81
+ if (file.chord.root !== p.root || file.chord.quality !== p.quality || file.chord.bass !== p.bass) {
82
+ err(path, "chord-id", `chord root/quality/bass (${file.chord.root}, ${file.chord.quality}, ${file.chord.bass}) disagree with id "${file.chord.id}"`);
83
+ }
84
+ }
85
+ if (chordOk && chordFilePath(file.chord.id) !== path)
86
+ err(path, "path-mismatch", `Expected path ${chordFilePath(file.chord.id)}`);
87
+ const revs = new Map();
88
+ for (const v of file.voicings) {
89
+ if (v.chordId !== file.chord.id)
90
+ err(path, "chord-id", `${v.id}@${v.rev}: chordId "${v.chordId}" differs from chord.id "${file.chord.id}"`);
91
+ if (!opts.licences.includes(v.licence))
92
+ err(path, "licence", `${v.id}@${v.rev}: licence "${v.licence}" is not allowed`);
93
+ try {
94
+ const h = withHashes(v).hashes;
95
+ if (h.shape !== v.hashes.shape || h.pitch !== v.hashes.pitch)
96
+ err(path, "hash-mismatch", `${v.id}@${v.rev}: stored hashes do not match the content`);
97
+ }
98
+ catch (e) {
99
+ err(path, "hash-mismatch", `${v.id}@${v.rev}: cannot compute hashes: ${e.message}`);
100
+ }
101
+ const res = checkVoicing(v);
102
+ for (const f of res.hard)
103
+ err(path, `check:${f.code}`, `${v.id}@${v.rev}: ${f.message}`);
104
+ for (const f of res.flags)
105
+ warn(path, `check:${f.code}`, `${v.id}@${v.rev}: ${f.message}`);
106
+ revs.set(v.id, [...(revs.get(v.id) ?? []), v]);
107
+ }
108
+ for (const [id, list] of revs) {
109
+ const sorted = [...list].sort((a, b) => a.rev - b.rev);
110
+ const bad = sorted.some((v, i) => v.rev !== i + 1 || v.previous !== (i === 0 ? null : i));
111
+ if (bad)
112
+ err(path, "rev-chain", `${id}: revisions must be 1..n with previous = rev-1 (rev 1 previous null)`);
113
+ const first = idFiles.get(id);
114
+ if (first === undefined)
115
+ idFiles.set(id, path);
116
+ else if (first !== path)
117
+ err(path, "id-reused", `${id} is already used in ${first}`);
118
+ }
119
+ }
120
+ // Latest rev per id across files; only live ones are compared for duplicates.
121
+ const latest = new Map();
122
+ for (const { path, file } of parsed) {
123
+ for (const v of file.voicings) {
124
+ const cur = latest.get(v.id);
125
+ if (!cur || v.rev > cur.v.rev)
126
+ latest.set(v.id, { path, v });
127
+ }
128
+ }
129
+ const live = [...latest.values()].filter((e) => e.v.status === "live").sort((a, b) => (a.v.id < b.v.id ? -1 : 1));
130
+ const group = (key, report) => {
131
+ const seen = new Map();
132
+ for (const e of live) {
133
+ const k = key(e.v);
134
+ const first = seen.get(k);
135
+ if (first)
136
+ report(e, first);
137
+ else
138
+ seen.set(k, e);
139
+ }
140
+ };
141
+ group((v) => `${v.chordId}\u0000${v.hashes.shape}`, (e, f) => err(e.path, "duplicate-shape", `${e.v.id} has the same shape as ${f.v.id} for ${e.v.chordId}`));
142
+ group((v) => v.hashes.shape, (e, f) => {
143
+ if (e.v.chordId !== f.v.chordId)
144
+ warn(e.path, "same-shape-other-chord", `${e.v.id} (${e.v.chordId}) has the same shape as ${f.v.id} (${f.v.chordId})`);
145
+ });
146
+ group((v) => v.hashes.pitch, (e, f) => {
147
+ if (e.v.hashes.shape !== f.v.hashes.shape) {
148
+ warn(e.path, "same-pitch", `${e.v.id} sounds the same pitches as ${f.v.id}`);
149
+ }
150
+ });
151
+ if (base) {
152
+ const before = historyOf(base);
153
+ const after = historyOf(new Map([...head].filter(([p]) => isChordPath(p))));
154
+ for (const [key, b] of before) {
155
+ const h = after.get(key);
156
+ if (!h)
157
+ err(b.path, "removed-history", `${key} was removed`);
158
+ else if (h.json !== b.json)
159
+ err(h.path, "modified-history", `${key} was modified; history is append-only`);
160
+ }
161
+ }
162
+ return { ok: errors.length === 0, errors, warnings };
163
+ }
164
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","sourceRoot":"","sources":["../src/catalogue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Finding } from "./realization.js";
2
+ import type { Voicing } from "./types.js";
3
+ export interface CheckResult {
4
+ hard: Finding[];
5
+ flags: Finding[];
6
+ facts: Finding[];
7
+ }
8
+ export declare function checkVoicing(v: Voicing): CheckResult;