@libraz/libcantus 0.9.4 → 1.0.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/README.md +61 -261
- package/README_ja.md +115 -0
- package/dist/analyze/index.cjs +54 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +213 -456
- package/dist/analyze/index.d.ts +213 -456
- package/dist/analyze/index.js +56 -9
- package/dist/chunk-3JXCW6DX.cjs +3220 -0
- package/dist/chunk-3JXCW6DX.cjs.map +1 -0
- package/dist/chunk-4A2RMUCC.js +7647 -0
- package/dist/chunk-4A2RMUCC.js.map +1 -0
- package/dist/chunk-6XPTBE7Y.cjs +49 -0
- package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
- package/dist/chunk-AFM2ULJH.cjs +5121 -0
- package/dist/chunk-AFM2ULJH.cjs.map +1 -0
- package/dist/chunk-E3F4AHAH.cjs +690 -0
- package/dist/chunk-E3F4AHAH.cjs.map +1 -0
- package/dist/chunk-ELCTZUJJ.js +379 -0
- package/dist/chunk-ELCTZUJJ.js.map +1 -0
- package/dist/chunk-FVSI36UV.js +690 -0
- package/dist/chunk-FVSI36UV.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -0
- package/dist/chunk-GOZG57ER.js +3220 -0
- package/dist/chunk-GOZG57ER.js.map +1 -0
- package/dist/chunk-H3VORETI.js +2243 -0
- package/dist/chunk-H3VORETI.js.map +1 -0
- package/dist/chunk-H42UHPYW.js +1577 -0
- package/dist/chunk-H42UHPYW.js.map +1 -0
- package/dist/chunk-HFPS7JFD.js +1 -0
- package/dist/chunk-INHVXP6Z.js +3053 -0
- package/dist/chunk-INHVXP6Z.js.map +1 -0
- package/dist/chunk-K4ZJUGF4.cjs +379 -0
- package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
- package/dist/chunk-KNJEKLKV.js +49 -0
- package/dist/chunk-KNJEKLKV.js.map +1 -0
- package/dist/chunk-M4CWTJWJ.cjs +7647 -0
- package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
- package/dist/chunk-MXO72PLY.cjs +211 -0
- package/dist/chunk-MXO72PLY.cjs.map +1 -0
- package/dist/chunk-NLEU4QSE.cjs +360 -0
- package/dist/chunk-NLEU4QSE.cjs.map +1 -0
- package/dist/chunk-OE6WK2Y2.cjs +1577 -0
- package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
- package/dist/chunk-Q2WGKJNN.cjs +1064 -0
- package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
- package/dist/chunk-QYQQ5J6E.js +1 -0
- package/dist/chunk-QYQQ5J6E.js.map +1 -0
- package/dist/chunk-RHIJ3S4I.cjs +1 -0
- package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
- package/dist/chunk-TTCLJX6D.js +211 -0
- package/dist/chunk-TTCLJX6D.js.map +1 -0
- package/dist/chunk-UADYAYAT.cjs +3053 -0
- package/dist/chunk-UADYAYAT.cjs.map +1 -0
- package/dist/chunk-VOHZB45S.js +5121 -0
- package/dist/chunk-VOHZB45S.js.map +1 -0
- package/dist/chunk-VTI6FOLB.js +1064 -0
- package/dist/chunk-VTI6FOLB.js.map +1 -0
- package/dist/chunk-WWURE4XT.cjs +2243 -0
- package/dist/chunk-WWURE4XT.cjs.map +1 -0
- package/dist/chunk-YH3BIKHH.js +360 -0
- package/dist/chunk-YH3BIKHH.js.map +1 -0
- package/dist/core/index.cjs +103 -6
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +96 -345
- package/dist/core/index.d.ts +96 -345
- package/dist/core/index.js +104 -7
- package/dist/generate/index.cjs +89 -10
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +437 -1095
- package/dist/generate/index.d.ts +437 -1095
- package/dist/generate/index.js +94 -15
- package/dist/index-9wcMhG73.d.ts +1095 -0
- package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
- package/dist/index-B9MNZboQ.d.cts +770 -0
- package/dist/index-B9MNZboQ.d.ts +770 -0
- package/dist/index-BGFW-8P2.d.cts +2307 -0
- package/dist/index-BO6xE9YU.d.cts +1804 -0
- package/dist/index-BkGmlyI3.d.cts +470 -0
- package/dist/index-CCNJRG-P.d.cts +1095 -0
- package/dist/index-CGpRv5sl.d.cts +643 -0
- package/dist/index-C_8o--VE.d.cts +1242 -0
- package/dist/index-CgHqxdps.d.ts +2307 -0
- package/dist/index-Chbst3Jx.d.ts +1343 -0
- package/dist/index-Cj49F892.d.ts +470 -0
- package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
- package/dist/index-D57QRbVn.d.ts +1804 -0
- package/dist/index-DInQqT8Q.d.cts +66 -0
- package/dist/index-DInQqT8Q.d.ts +66 -0
- package/dist/index-Dzr13q3d.d.cts +1343 -0
- package/dist/index-G1uwGGWQ.d.ts +643 -0
- package/dist/index-szBv3496.d.ts +1242 -0
- package/dist/index.cjs +358 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -15
- package/dist/index.d.ts +17 -15
- package/dist/index.js +407 -64
- package/dist/masks-DYbE485J.d.cts +445 -0
- package/dist/masks-DYbE485J.d.ts +445 -0
- package/dist/model/index.cjs +39 -7
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +4685 -243
- package/dist/model/index.d.ts +4685 -243
- package/dist/model/index.js +40 -8
- package/dist/positional-Cptlj-xs.d.ts +287 -0
- package/dist/positional-DvH_sd8u.d.cts +287 -0
- package/dist/theory/index.cjs +101 -7
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +513 -173
- package/dist/theory/index.d.ts +513 -173
- package/dist/theory/index.js +118 -24
- package/package.json +16 -11
- package/dist/chunk-36N73KZ3.cjs +0 -246
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs +0 -3077
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs +0 -498
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js +0 -3077
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs +0 -679
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js +0 -479
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js +0 -1449
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs +0 -30
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs +0 -479
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js +0 -173
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-HKGYCQ34.js +0 -53
- package/dist/chunk-HKGYCQ34.js.map +0 -1
- package/dist/chunk-I24MV65U.js +0 -106
- package/dist/chunk-I24MV65U.js.map +0 -1
- package/dist/chunk-KMHOICIY.js +0 -679
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs +0 -1117
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-N6IHQUOX.js +0 -1
- package/dist/chunk-NKIBQ6PT.js +0 -30
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs +0 -106
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs +0 -1449
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js +0 -327
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js +0 -845
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs +0 -327
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs +0 -845
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js +0 -1117
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-RUMU3UA6.cjs +0 -53
- package/dist/chunk-RUMU3UA6.cjs.map +0 -1
- package/dist/chunk-T64C7ZD7.cjs +0 -1
- package/dist/chunk-T64C7ZD7.cjs.map +0 -1
- package/dist/chunk-XEBG3G53.js +0 -498
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js +0 -246
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs +0 -173
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- package/dist/function-BtiZb9i9.d.ts +0 -208
- package/dist/function-DNugAOrt.d.cts +0 -208
- package/dist/index-6VR8-kit.d.ts +0 -143
- package/dist/index-70p168VM.d.ts +0 -192
- package/dist/index-Be74mcon.d.cts +0 -152
- package/dist/index-BtXT6UQ0.d.cts +0 -192
- package/dist/index-C5UBbQ4G.d.ts +0 -152
- package/dist/index-C83mZI1P.d.ts +0 -242
- package/dist/index-DXd9JikN.d.cts +0 -246
- package/dist/index-DXd9JikN.d.ts +0 -246
- package/dist/index-OG65ocOr.d.cts +0 -272
- package/dist/index-OG65ocOr.d.ts +0 -272
- package/dist/index-tJhkmQgE.d.cts +0 -143
- package/dist/index-y_ak9_Hi.d.cts +0 -242
- package/dist/styled-DIRkk9Uz.d.ts +0 -506
- package/dist/styled-DJbkJQfj.d.cts +0 -506
- package/dist/types-BN6_Q9Kl.d.cts +0 -39
- package/dist/types-BN6_Q9Kl.d.ts +0 -39
- /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every playing technique a note can carry, in declaration order.
|
|
3
|
+
*
|
|
4
|
+
* These name intent, not a rendering: how `flam` becomes a grace stroke or
|
|
5
|
+
* `slide` becomes pitch bend is the caller's decision, because it depends on the
|
|
6
|
+
* sampler, key switches, and controller layout in use. Carrying the intent
|
|
7
|
+
* instead of a baked note pair is what lets any phrase be flammed, ghosted, or
|
|
8
|
+
* muted without the generator having to know how it will be voiced.
|
|
9
|
+
*
|
|
10
|
+
* @category Core
|
|
11
|
+
*/
|
|
12
|
+
declare const ARTICULATIONS: readonly ["accent", "ghost", "staccato", "legato", "slide", "hammer", "mute", "flam", "drag", "roll", "choke", "open"];
|
|
13
|
+
/**
|
|
14
|
+
* How a note is played, independent of its pitch and length.
|
|
15
|
+
*
|
|
16
|
+
* @category Core
|
|
17
|
+
*/
|
|
18
|
+
type Articulation = (typeof ARTICULATIONS)[number];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A key/scale definition anchored on a root pitch class.
|
|
22
|
+
*
|
|
23
|
+
* `modeMask12` is a 12-bit mask where bit `n` set means the pitch class
|
|
24
|
+
* `(rootPc + n) % 12` belongs to the scale. Bit 0 (the root) is always set;
|
|
25
|
+
* {@link maskFromOffsets} enforces this so the root is always a scale tone.
|
|
26
|
+
*
|
|
27
|
+
* @category Core
|
|
28
|
+
*/
|
|
29
|
+
type KeyScale = {
|
|
30
|
+
rootPc: number;
|
|
31
|
+
modeMask12: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A single sounding note on a beat-indexed timeline: an absolute MIDI pitch, an
|
|
35
|
+
* onset and duration measured in quarter-note beats, and an optional MIDI
|
|
36
|
+
* velocity. This is the library-wide interchange shape for note events read
|
|
37
|
+
* from or written to a DAW/MIDI track; the harmonizer's `MelodyNote` and the
|
|
38
|
+
* analysis `VoiceNote` are specializations of it.
|
|
39
|
+
*
|
|
40
|
+
* @category Core
|
|
41
|
+
*/
|
|
42
|
+
type NoteEvent = {
|
|
43
|
+
/** MIDI pitch in [0, 127] (middle C = 60). */
|
|
44
|
+
pitch: number;
|
|
45
|
+
/**
|
|
46
|
+
* Onset in quarter-note beats, absolute from the start of the timeline, and
|
|
47
|
+
* unbounded below: beat 0 is the first downbeat, so a pickup sounds before it
|
|
48
|
+
* and is written at a negative onset, in the bar numbered -1. Shifting the
|
|
49
|
+
* whole timeline a bar later instead would move every strong beat with it,
|
|
50
|
+
* and the upbeat would then be analyzed as a downbeat. A caller that knows
|
|
51
|
+
* how long its pickup is narrows the bound with
|
|
52
|
+
* {@link NoteEventAssertOptions.minStartBeat}.
|
|
53
|
+
*/
|
|
54
|
+
startBeat: number;
|
|
55
|
+
/** Duration in quarter-note beats. */
|
|
56
|
+
durationBeat: number;
|
|
57
|
+
/** MIDI velocity in [0, 127], when known. */
|
|
58
|
+
velocity?: number;
|
|
59
|
+
/**
|
|
60
|
+
* How the note is played, when the writer had an intent to record. The
|
|
61
|
+
* library carries the intent only: turning `slide` into pitch bend or `ghost`
|
|
62
|
+
* into a velocity floor depends on the instrument and controller layout at
|
|
63
|
+
* the far end, so it is the reader's decision. Optional, so a reader that
|
|
64
|
+
* knows nothing of articulation reads the note exactly as before.
|
|
65
|
+
*/
|
|
66
|
+
articulation?: Articulation;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The library's error taxonomy.
|
|
71
|
+
*
|
|
72
|
+
* Every failure this library raises falls into one of three kinds, and the kind
|
|
73
|
+
* decides what a caller can do about it. Batch-voicing a lead sheet, for
|
|
74
|
+
* instance, has to tell "fix the input" apart from "loosen the constraints",
|
|
75
|
+
* and a message string is not a contract.
|
|
76
|
+
*
|
|
77
|
+
* The classes extend the built-in error types a caller would otherwise catch —
|
|
78
|
+
* a rejected argument is still a `RangeError` — so the `code` narrows an
|
|
79
|
+
* existing category rather than replacing it.
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* What kind of failure an error reports.
|
|
83
|
+
*
|
|
84
|
+
* - `INVALID_INPUT`: an argument is malformed or out of range. The caller's
|
|
85
|
+
* input must change.
|
|
86
|
+
* - `NO_SOLUTION`: the input is valid but nothing satisfies the constraints.
|
|
87
|
+
* The constraints must change, or the failure must be accepted.
|
|
88
|
+
* - `BUDGET_EXCEEDED`: the requested work is larger than the synchronous
|
|
89
|
+
* generation budget. The request must be smaller, or the budget larger.
|
|
90
|
+
*
|
|
91
|
+
* @category Core
|
|
92
|
+
*/
|
|
93
|
+
type LibcantusErrorCode = 'INVALID_INPUT' | 'NO_SOLUTION' | 'BUDGET_EXCEEDED';
|
|
94
|
+
/**
|
|
95
|
+
* Any error this library raises, as a union of its three classes.
|
|
96
|
+
*
|
|
97
|
+
* The type a non-throwing entry point reports its failure as: a caller that
|
|
98
|
+
* holds one of these already knows it carries a {@link LibcantusErrorCode} and
|
|
99
|
+
* a message worth showing.
|
|
100
|
+
*
|
|
101
|
+
* @category Core
|
|
102
|
+
*/
|
|
103
|
+
type LibcantusError = InvalidInputError | NoSolutionError | BudgetExceededError;
|
|
104
|
+
/**
|
|
105
|
+
* The outcome of a parse that reports failure instead of throwing it.
|
|
106
|
+
*
|
|
107
|
+
* A text field is parsed on every keystroke, and most of those keystrokes are
|
|
108
|
+
* halfway through a valid symbol, so failure is the normal case rather than an
|
|
109
|
+
* exceptional one. The error travels in the result — not as `null` — because an
|
|
110
|
+
* input field has to say what is wrong with what was typed.
|
|
111
|
+
*
|
|
112
|
+
* @typeParam T The value a successful parse produces.
|
|
113
|
+
* @category Core
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { tryParseChordSymbol } from '@libraz/libcantus';
|
|
117
|
+
* const result = tryParseChordSymbol('Cmaj');
|
|
118
|
+
* const label = result.ok ? result.value.quality : result.error.message;
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
type ParseResult<T> = {
|
|
122
|
+
ok: true;
|
|
123
|
+
value: T;
|
|
124
|
+
} | {
|
|
125
|
+
ok: false;
|
|
126
|
+
error: LibcantusError;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* A rejected argument: malformed, out of range, or naming something unknown.
|
|
130
|
+
*
|
|
131
|
+
* @category Core
|
|
132
|
+
*/
|
|
133
|
+
declare class InvalidInputError extends RangeError {
|
|
134
|
+
readonly code: "INVALID_INPUT";
|
|
135
|
+
readonly name = "InvalidInputError";
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A valid request with no answer: no voicing fits the given ranges, no chord
|
|
139
|
+
* matches the pitches, no preset claims the style.
|
|
140
|
+
*
|
|
141
|
+
* @category Core
|
|
142
|
+
*/
|
|
143
|
+
declare class NoSolutionError extends Error {
|
|
144
|
+
readonly code: "NO_SOLUTION";
|
|
145
|
+
readonly name = "NoSolutionError";
|
|
146
|
+
/**
|
|
147
|
+
* Where in a sequence the failure occurred, when the operation was working
|
|
148
|
+
* through one — the chord index of a progression, say.
|
|
149
|
+
*/
|
|
150
|
+
readonly at: number | undefined;
|
|
151
|
+
constructor(message: string, options?: {
|
|
152
|
+
at?: number;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Work that would exceed the synchronous generation budget.
|
|
157
|
+
*
|
|
158
|
+
* @category Core
|
|
159
|
+
*/
|
|
160
|
+
declare class BudgetExceededError extends RangeError {
|
|
161
|
+
readonly code: "BUDGET_EXCEEDED";
|
|
162
|
+
readonly name = "BudgetExceededError";
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Whether a caught value is one of this library's coded errors.
|
|
166
|
+
*
|
|
167
|
+
* @param value The caught value.
|
|
168
|
+
* @returns True when it carries a {@link LibcantusErrorCode}.
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import { isLibcantusError, voiceChord, parseChordSymbol } from '@libraz/libcantus';
|
|
172
|
+
* try {
|
|
173
|
+
* voiceChord(parseChordSymbol('C'), { ranges: [] });
|
|
174
|
+
* } catch (error) {
|
|
175
|
+
* if (isLibcantusError(error) && error.code === 'NO_SOLUTION') {
|
|
176
|
+
* // loosen the constraints rather than blaming the input
|
|
177
|
+
* }
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
* @category Core
|
|
181
|
+
*/
|
|
182
|
+
declare function isLibcantusError(value: unknown): value is InvalidInputError | NoSolutionError | BudgetExceededError;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Note-name notation systems.
|
|
186
|
+
*
|
|
187
|
+
* A pitch has one identity but many written names: the note an English score
|
|
188
|
+
* calls `G#` is `gis` in German, `嬰ト` in Japanese and `sol diesis` in Italian.
|
|
189
|
+
* This module holds each system's table — letters, accidental affixes and mode
|
|
190
|
+
* words — so a name can be read and written in the system a source actually
|
|
191
|
+
* uses, and so a name of unknown origin can be attributed to one.
|
|
192
|
+
*
|
|
193
|
+
* The tables live together because attribution needs them together: `B` is a B
|
|
194
|
+
* natural in English but a B flat in German, and only a table that knows both
|
|
195
|
+
* can say so.
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A system of written note names.
|
|
200
|
+
*
|
|
201
|
+
* - `english`: C D E F G A B, `#` sharp and `b` flat (`C#`, `Bb`, `F##`).
|
|
202
|
+
* - `german`: C D E F G A H, `-is` sharp and `-es` flat (`cis`, `des`), with B
|
|
203
|
+
* for B flat and H for B natural.
|
|
204
|
+
* - `japanese`: the katakana 音名 ハ ニ ホ ヘ ト イ ロ, prefixed 嬰 sharp and
|
|
205
|
+
* 変 flat.
|
|
206
|
+
* - `italian`: the solfège syllables do re mi fa sol la si, followed by
|
|
207
|
+
* `diesis` or `bemolle`.
|
|
208
|
+
* - `fixedDo`: the same syllables read as fixed-do note names; identical to
|
|
209
|
+
* `italian`, and named separately because callers ask for it by that name.
|
|
210
|
+
*
|
|
211
|
+
* @category Pitch & Intervals
|
|
212
|
+
*/
|
|
213
|
+
type NoteNameSystem = 'english' | 'german' | 'japanese' | 'italian' | 'fixedDo';
|
|
214
|
+
/**
|
|
215
|
+
* Which notation system to read or write a name in.
|
|
216
|
+
*
|
|
217
|
+
* @category Pitch & Intervals
|
|
218
|
+
*/
|
|
219
|
+
type NoteNameOptions = {
|
|
220
|
+
/**
|
|
221
|
+
* The system. Omitted on parsing, the system is detected from the name
|
|
222
|
+
* itself; omitted on formatting, the name is written in English.
|
|
223
|
+
*/
|
|
224
|
+
system?: NoteNameSystem;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* A key named the way a score names it: a spelled tonic and a mode.
|
|
228
|
+
*
|
|
229
|
+
* This is what a key *name* carries. It is deliberately not a key/scale: a name
|
|
230
|
+
* fixes the tonic's spelling and says major or minor, and nothing more.
|
|
231
|
+
*
|
|
232
|
+
* @category Scales
|
|
233
|
+
*/
|
|
234
|
+
type KeyName = {
|
|
235
|
+
/** The spelled tonic, without an octave. */
|
|
236
|
+
tonic: Note;
|
|
237
|
+
/** Which of the two modes the name gives. */
|
|
238
|
+
mode: 'major' | 'minor';
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* The notation system a note name or key name is written in.
|
|
242
|
+
*
|
|
243
|
+
* A name several systems could read is attributed to the first of English,
|
|
244
|
+
* German, Japanese, Italian that reads it — so `B` is the English B natural,
|
|
245
|
+
* and German is chosen only for a name carrying a German-only cue (`H`, an
|
|
246
|
+
* `-is`/`-es` ending, or `dur`/`moll`). Because the fixed-do syllables are the
|
|
247
|
+
* Italian ones, detection reports `'italian'` for them; `'fixedDo'` is only ever
|
|
248
|
+
* a system a caller asks for by name.
|
|
249
|
+
*
|
|
250
|
+
* @param text The note name or key name.
|
|
251
|
+
* @returns The system that reads it.
|
|
252
|
+
* @throws If no system reads the name, or the name mixes two systems — a German
|
|
253
|
+
* tonic under an English mode word, say.
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* import { detectNoteNameSystem } from '@libraz/libcantus';
|
|
257
|
+
* detectNoteNameSystem('B'); // 'english' — never the German B flat
|
|
258
|
+
* detectNoteNameSystem('gis moll'); // 'german'
|
|
259
|
+
* detectNoteNameSystem('嬰ト短調'); // 'japanese'
|
|
260
|
+
* ```
|
|
261
|
+
* @category Pitch & Intervals
|
|
262
|
+
*/
|
|
263
|
+
declare function detectNoteNameSystem(text: string): NoteNameSystem;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
|
|
267
|
+
* intervals.
|
|
268
|
+
*
|
|
269
|
+
* The rest of the library works in pitch classes (0..11), which cannot tell a
|
|
270
|
+
* G# from an Ab or an augmented fourth from a diminished fifth. This module adds
|
|
271
|
+
* an explicit spelling layer — a note is a diatonic letter plus a chromatic
|
|
272
|
+
* alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
|
|
273
|
+
* preserved.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* A spelled note: a diatonic letter, a chromatic alteration, and an optional
|
|
278
|
+
* octave.
|
|
279
|
+
*
|
|
280
|
+
* - `letter` is 0..6 for C..B.
|
|
281
|
+
* - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
|
|
282
|
+
* 0 natural, +1 sharp, +2 double-sharp.
|
|
283
|
+
* - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
|
|
284
|
+
* absent, the note denotes a bare pitch class.
|
|
285
|
+
*
|
|
286
|
+
* @category Pitch & Intervals
|
|
287
|
+
*/
|
|
288
|
+
type Note = {
|
|
289
|
+
letter: number;
|
|
290
|
+
alter: number;
|
|
291
|
+
octave?: number;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* The quality label of a spelled interval: perfect, major, minor, or one or
|
|
295
|
+
* more augmentations or diminutions.
|
|
296
|
+
*
|
|
297
|
+
* This names how an interval is *spelled*. It is a different notion from
|
|
298
|
+
* {@link ConsonanceClass}, which classifies how an interval *sounds* against
|
|
299
|
+
* another voice.
|
|
300
|
+
*
|
|
301
|
+
* @see {@link ConsonanceClass}
|
|
302
|
+
* @category Pitch & Intervals
|
|
303
|
+
*/
|
|
304
|
+
type IntervalQualityLabel = 'P' | 'M' | 'm' | `A${string}` | `d${string}`;
|
|
305
|
+
/**
|
|
306
|
+
* A spelled interval: a diatonic number, a quality label, and its semitone span.
|
|
307
|
+
*
|
|
308
|
+
* @category Pitch & Intervals
|
|
309
|
+
*/
|
|
310
|
+
type SpelledInterval = {
|
|
311
|
+
/** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
|
|
312
|
+
number: number;
|
|
313
|
+
/** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
|
|
314
|
+
quality: IntervalQualityLabel;
|
|
315
|
+
/** Signed semitone distance from the first note to the second. */
|
|
316
|
+
semitones: number;
|
|
317
|
+
/**
|
|
318
|
+
* True when the diatonic letters move down, and absent otherwise — an
|
|
319
|
+
* ascending interval leaves the key out rather than carrying `false`, so
|
|
320
|
+
* every producer of this type returns the same object for the same interval.
|
|
321
|
+
* The flag is not redundant with the sign of the span: a descending unison
|
|
322
|
+
* spans zero semitones, and a letter can rise while the pitch falls.
|
|
323
|
+
*/
|
|
324
|
+
descending?: boolean;
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Reduce a MIDI pitch, a bare pitch class, or any signed offset to a pitch
|
|
328
|
+
* class in [0, 11].
|
|
329
|
+
*
|
|
330
|
+
* This is the library's single definition of pitch-class arithmetic: every
|
|
331
|
+
* layer routes through it so a fractional pitch reduces the same way
|
|
332
|
+
* everywhere. A non-integral input is rounded to the nearest semitone first, so
|
|
333
|
+
* a microtonal 60.6 reads as C# rather than C.
|
|
334
|
+
*
|
|
335
|
+
* @param value A MIDI pitch, pitch class, or signed semitone offset.
|
|
336
|
+
* @returns The pitch class in [0, 11].
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* import { pitchClassOf } from '@libraz/libcantus';
|
|
340
|
+
* pitchClassOf(61); // 1
|
|
341
|
+
* pitchClassOf(-1); // 11
|
|
342
|
+
* pitchClassOf(60.6); // 1
|
|
343
|
+
* ```
|
|
344
|
+
* @category Pitch & Intervals
|
|
345
|
+
*/
|
|
346
|
+
declare function pitchClassOf(value: number): number;
|
|
347
|
+
/**
|
|
348
|
+
* Reduce a letter number or letter offset to a diatonic letter in [0, 6]
|
|
349
|
+
* (0 = C .. 6 = B).
|
|
350
|
+
*
|
|
351
|
+
* @param value A letter number or signed letter offset.
|
|
352
|
+
* @returns The diatonic letter in [0, 6].
|
|
353
|
+
* @category Pitch & Intervals
|
|
354
|
+
*/
|
|
355
|
+
declare function diatonicLetterOf(value: number): number;
|
|
356
|
+
/**
|
|
357
|
+
* The pitch class of a diatonic letter with no accidental (C = 0, D = 2, ...).
|
|
358
|
+
*
|
|
359
|
+
* @param letter A letter number or signed letter offset; reduced to [0, 6].
|
|
360
|
+
* @returns The natural pitch class of that letter.
|
|
361
|
+
* @category Pitch & Intervals
|
|
362
|
+
*/
|
|
363
|
+
declare function naturalPitchClassOf(letter: number): number;
|
|
364
|
+
/**
|
|
365
|
+
* Parse a note name into a {@link Note}, in any supported notation system.
|
|
366
|
+
*
|
|
367
|
+
* English (the default when a name could be read in more than one system)
|
|
368
|
+
* accepts a letter (case-insensitive) followed by any number of same-direction
|
|
369
|
+
* accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
|
|
370
|
+
* `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
|
|
371
|
+
* flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
|
|
372
|
+
*
|
|
373
|
+
* Without a `system` the name is attributed by {@link detectNoteNameSystem},
|
|
374
|
+
* which reads a name any two systems could claim as English: `"B"` is the B
|
|
375
|
+
* natural, never the German B flat. German is chosen only for a German-only
|
|
376
|
+
* name such as `"H"`, `"fis"` or `"es"`.
|
|
377
|
+
*
|
|
378
|
+
* A scientific octave may follow the name in every system (`"gis4"`,
|
|
379
|
+
* `"嬰ト4"`), which is what makes formatting and parsing exact inverses for an
|
|
380
|
+
* octave-bearing note.
|
|
381
|
+
*
|
|
382
|
+
* @param text The note name.
|
|
383
|
+
* @param opts `system` reads the name in that notation system instead of
|
|
384
|
+
* detecting it.
|
|
385
|
+
* @returns The parsed note.
|
|
386
|
+
* @throws If the text is not a valid note in the given (or detected) system.
|
|
387
|
+
* Use {@link tryParseNote} where failure is ordinary, such as a note field
|
|
388
|
+
* read on every keystroke.
|
|
389
|
+
* @example
|
|
390
|
+
* ```ts
|
|
391
|
+
* import { parseNote, noteToMidi, noteToPitchClass } from '@libraz/libcantus';
|
|
392
|
+
* noteToMidi(parseNote('C#4')); // 61
|
|
393
|
+
* noteToPitchClass(parseNote('gis')); // 8 — the German G sharp
|
|
394
|
+
* noteToPitchClass(parseNote('B', { system: 'german' })); // 10 — the German B flat
|
|
395
|
+
* ```
|
|
396
|
+
* @category Pitch & Intervals
|
|
397
|
+
*/
|
|
398
|
+
declare function parseNote(text: string, opts?: NoteNameOptions): Note;
|
|
399
|
+
/**
|
|
400
|
+
* Parse a note name, reporting failure instead of throwing it.
|
|
401
|
+
*
|
|
402
|
+
* The same reading as {@link parseNote} — that function is this one with its
|
|
403
|
+
* error thrown — for the callers where a name that does not parse yet is the
|
|
404
|
+
* normal state of the input rather than a fault.
|
|
405
|
+
*
|
|
406
|
+
* @param text The note name.
|
|
407
|
+
* @param opts `system` reads the name in that notation system instead of
|
|
408
|
+
* detecting it.
|
|
409
|
+
* @returns The note, or the error explaining why the text is not one.
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* import { tryParseNote } from '@libraz/libcantus';
|
|
413
|
+
* const result = tryParseNote('C#4');
|
|
414
|
+
* result.ok ? result.value.letter : result.error.message;
|
|
415
|
+
* ```
|
|
416
|
+
* @category Pitch & Intervals
|
|
417
|
+
*/
|
|
418
|
+
declare function tryParseNote(text: string, opts?: NoteNameOptions): ParseResult<Note>;
|
|
419
|
+
/**
|
|
420
|
+
* Render a {@link Note} as a note name, in any supported notation system.
|
|
421
|
+
*
|
|
422
|
+
* The inverse of {@link parseNote} when both are given the same `system`,
|
|
423
|
+
* including double accidentals and octaves. Without one, {@link parseNote}
|
|
424
|
+
* detects the system and reads a bare `B` as the English B natural, so a German
|
|
425
|
+
* B flat written as `'b'` does not survive a round trip that names no system.
|
|
426
|
+
*
|
|
427
|
+
* @param note The note to format.
|
|
428
|
+
* @param opts `system` writes the name in that notation system instead of
|
|
429
|
+
* English.
|
|
430
|
+
* @returns The note name, including the octave when present.
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* import { formatNote, parseNote } from '@libraz/libcantus';
|
|
434
|
+
* formatNote(parseNote('C#4')); // 'C#4'
|
|
435
|
+
* formatNote(parseNote('G#'), { system: 'german' }); // 'gis'
|
|
436
|
+
* formatNote(parseNote('G#'), { system: 'japanese' }); // '嬰ト'
|
|
437
|
+
* ```
|
|
438
|
+
* @category Pitch & Intervals
|
|
439
|
+
*/
|
|
440
|
+
declare function formatNote(note: Note, opts?: NoteNameOptions): string;
|
|
441
|
+
/**
|
|
442
|
+
* Parse a key name such as `'C major'`, `'gis moll'` or `'嬰ト短調'`.
|
|
443
|
+
*
|
|
444
|
+
* The name is a tonic followed by a mode word, in any of the supported
|
|
445
|
+
* notation systems; German also accepts its hyphenated form (`'gis-Moll'`).
|
|
446
|
+
* Without a `system` the name is attributed by {@link detectNoteNameSystem},
|
|
447
|
+
* so a mode word is itself enough to pick one: `'B dur'` is B flat major while
|
|
448
|
+
* `'B major'` is B major.
|
|
449
|
+
*
|
|
450
|
+
* German carries the mode twice, in the word and in the case of the tonic
|
|
451
|
+
* (`C dur`, `c moll`). When the two disagree the word wins — `'C moll'` is C
|
|
452
|
+
* minor — because the word is what the writer said and the case only what they
|
|
453
|
+
* typed. A German name with no mode word is decided by its case alone
|
|
454
|
+
* (`'gis'` is G sharp minor, `'Gis'` G sharp major); a bare tonic in any other
|
|
455
|
+
* system names a major key.
|
|
456
|
+
*
|
|
457
|
+
* @param text The key name.
|
|
458
|
+
* @param opts `system` reads the name in that notation system instead of
|
|
459
|
+
* detecting it.
|
|
460
|
+
* @returns The spelled tonic and the mode.
|
|
461
|
+
* @throws If the text is not a key name in the given (or detected) system, if
|
|
462
|
+
* it mixes two systems, or if the tonic carries an octave. Use
|
|
463
|
+
* {@link tryParseKeyName} where failure is ordinary, such as a key field read
|
|
464
|
+
* on every keystroke.
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* import { formatNote, parseKeyName } from '@libraz/libcantus';
|
|
468
|
+
* const key = parseKeyName('gis moll');
|
|
469
|
+
* formatNote(key.tonic); // 'G#'
|
|
470
|
+
* key.mode; // 'minor'
|
|
471
|
+
* ```
|
|
472
|
+
* @category Scales
|
|
473
|
+
*/
|
|
474
|
+
declare function parseKeyName(text: string, opts?: NoteNameOptions): KeyName;
|
|
475
|
+
/**
|
|
476
|
+
* Parse a key name, reporting failure instead of throwing it.
|
|
477
|
+
*
|
|
478
|
+
* The same reading as {@link parseKeyName} — that function is this one with its
|
|
479
|
+
* error thrown — for the callers where a name that does not parse yet is the
|
|
480
|
+
* normal state of the input rather than a fault.
|
|
481
|
+
*
|
|
482
|
+
* @param text The key name.
|
|
483
|
+
* @param opts `system` reads the name in that notation system instead of
|
|
484
|
+
* detecting it.
|
|
485
|
+
* @returns The key, or the error explaining why the text is not one.
|
|
486
|
+
* @example
|
|
487
|
+
* ```ts
|
|
488
|
+
* import { tryParseKeyName } from '@libraz/libcantus';
|
|
489
|
+
* const result = tryParseKeyName('gis moll');
|
|
490
|
+
* result.ok ? result.value.mode : result.error.message; // 'minor'
|
|
491
|
+
* ```
|
|
492
|
+
* @category Scales
|
|
493
|
+
*/
|
|
494
|
+
declare function tryParseKeyName(text: string, opts?: NoteNameOptions): ParseResult<KeyName>;
|
|
495
|
+
/**
|
|
496
|
+
* Render a spelled tonic and a mode as a key name.
|
|
497
|
+
*
|
|
498
|
+
* The inverse of {@link parseKeyName}. German is written the way it is read,
|
|
499
|
+
* with the mode in the case of the tonic as well as in the word.
|
|
500
|
+
*
|
|
501
|
+
* @param key The spelled tonic and mode; any octave on the tonic is dropped,
|
|
502
|
+
* since a key has no register.
|
|
503
|
+
* @param opts `system` writes the name in that notation system instead of
|
|
504
|
+
* English.
|
|
505
|
+
* @returns The key name.
|
|
506
|
+
* @throws If the tonic or the mode is not valid.
|
|
507
|
+
* @example
|
|
508
|
+
* ```ts
|
|
509
|
+
* import { formatKeyName, parseNote } from '@libraz/libcantus';
|
|
510
|
+
* formatKeyName({ tonic: parseNote('G#'), mode: 'minor' }); // 'G# minor'
|
|
511
|
+
* formatKeyName({ tonic: parseNote('G#'), mode: 'minor' }, { system: 'german' }); // 'gis moll'
|
|
512
|
+
* ```
|
|
513
|
+
* @category Scales
|
|
514
|
+
*/
|
|
515
|
+
declare function formatKeyName(key: KeyName, opts?: NoteNameOptions): string;
|
|
516
|
+
/**
|
|
517
|
+
* Pitch class (0..11) of a note, ignoring octave.
|
|
518
|
+
*
|
|
519
|
+
* @param note The note.
|
|
520
|
+
* @returns The pitch class.
|
|
521
|
+
* @example
|
|
522
|
+
* ```ts
|
|
523
|
+
* import { noteToPitchClass, parseNote } from '@libraz/libcantus';
|
|
524
|
+
* noteToPitchClass(parseNote('Db')); // 1
|
|
525
|
+
* ```
|
|
526
|
+
* @category Pitch & Intervals
|
|
527
|
+
*/
|
|
528
|
+
declare function noteToPitchClass(note: Note): number;
|
|
529
|
+
/**
|
|
530
|
+
* MIDI number of a note (middle C = C4 = 60).
|
|
531
|
+
*
|
|
532
|
+
* @param note The note; must carry an octave.
|
|
533
|
+
* @returns The MIDI number.
|
|
534
|
+
* @throws If the note has no octave.
|
|
535
|
+
* @example
|
|
536
|
+
* ```ts
|
|
537
|
+
* import { noteToMidi, parseNote } from '@libraz/libcantus';
|
|
538
|
+
* noteToMidi(parseNote('A4')); // 69
|
|
539
|
+
* ```
|
|
540
|
+
* @category Pitch & Intervals
|
|
541
|
+
*/
|
|
542
|
+
declare function noteToMidi(note: Note): number;
|
|
543
|
+
/**
|
|
544
|
+
* Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
|
|
545
|
+
*
|
|
546
|
+
* The input is rounded to the nearest integer but is not clamped to the MIDI
|
|
547
|
+
* range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
|
|
548
|
+
* `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
|
|
549
|
+
*
|
|
550
|
+
* @param midi The MIDI number.
|
|
551
|
+
* @param spelling Whether to prefer sharps or flats for black keys.
|
|
552
|
+
* @returns The spelled note, with octave.
|
|
553
|
+
* @throws If `spelling` is neither `'sharp'` nor `'flat'`; a name the table does
|
|
554
|
+
* not carry would otherwise be spelled as one of them without saying which.
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* import { midiToNote, formatNote } from '@libraz/libcantus';
|
|
558
|
+
* formatNote(midiToNote(61)); // 'C#4'
|
|
559
|
+
* formatNote(midiToNote(61, 'flat')); // 'Db4'
|
|
560
|
+
* ```
|
|
561
|
+
* @category Pitch & Intervals
|
|
562
|
+
*/
|
|
563
|
+
declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
|
|
564
|
+
/**
|
|
565
|
+
* Transpose a note by a signed number of semitones, keeping its spelling.
|
|
566
|
+
*
|
|
567
|
+
* The letter moves by the diatonic distance of the conventional interval for
|
|
568
|
+
* that many semitones and the accidental is recomputed for the new letter, so
|
|
569
|
+
* enharmonic identity survives: `Ab4` up a major second is `Bb4`, not `A#4`, and
|
|
570
|
+
* the operation is an exact inverse of itself. An octave-less note stays
|
|
571
|
+
* octave-less.
|
|
572
|
+
*
|
|
573
|
+
* A bare semitone count cannot distinguish an augmented fourth from a diminished
|
|
574
|
+
* fifth; pass `spelling` to override the letter distance when the enharmonic
|
|
575
|
+
* choice matters.
|
|
576
|
+
*
|
|
577
|
+
* @param note The note to transpose.
|
|
578
|
+
* @param semitones The signed semitone offset.
|
|
579
|
+
* @param opts `spelling` forces the result onto the sharp or flat side instead
|
|
580
|
+
* of following the source note's letter.
|
|
581
|
+
* @returns The transposed note.
|
|
582
|
+
* @example
|
|
583
|
+
* ```ts
|
|
584
|
+
* import { transposeNote, formatNote, parseNote } from '@libraz/libcantus';
|
|
585
|
+
* formatNote(transposeNote(parseNote('Ab4'), 2)); // 'Bb4'
|
|
586
|
+
* formatNote(transposeNote(parseNote('Ab4'), 2, { spelling: 'sharp' })); // 'A#4'
|
|
587
|
+
* ```
|
|
588
|
+
* @category Pitch & Intervals
|
|
589
|
+
*/
|
|
590
|
+
declare function transposeNote(note: Note, semitones: number, opts?: {
|
|
591
|
+
spelling?: 'sharp' | 'flat';
|
|
592
|
+
}): Note;
|
|
593
|
+
/**
|
|
594
|
+
* The semitone span of the interval named by a diatonic number and a quality.
|
|
595
|
+
*
|
|
596
|
+
* The inverse of the quality derivation in {@link spelledInterval}: it turns a
|
|
597
|
+
* name back into a distance, which is what transposing by a named interval or
|
|
598
|
+
* parsing one from text needs.
|
|
599
|
+
*
|
|
600
|
+
* @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.
|
|
601
|
+
* @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.
|
|
602
|
+
* @returns The unsigned semitone span.
|
|
603
|
+
* @throws If the quality cannot apply to the number, such as a major fifth or a
|
|
604
|
+
* diminished unison.
|
|
605
|
+
* @example
|
|
606
|
+
* ```ts
|
|
607
|
+
* import { intervalSemitones } from '@libraz/libcantus';
|
|
608
|
+
* intervalSemitones(5, 'P'); // 7
|
|
609
|
+
* ```
|
|
610
|
+
* @category Pitch & Intervals
|
|
611
|
+
*/
|
|
612
|
+
declare function intervalSemitones(numberValue: number, quality: IntervalQualityLabel): number;
|
|
613
|
+
/**
|
|
614
|
+
* Parse an interval name such as `'P5'`, `'m3'`, `'AA4'`, or `'-m3'`.
|
|
615
|
+
*
|
|
616
|
+
* A leading `'-'` names the same interval taken downward: the span is negated
|
|
617
|
+
* and `descending` is set. The flag is not redundant with the sign — a
|
|
618
|
+
* descending unison spans zero semitones, so only the flag records that the
|
|
619
|
+
* letters move down.
|
|
620
|
+
*
|
|
621
|
+
* @param name The interval name: an optional `'-'`, a quality label, and a
|
|
622
|
+
* diatonic number. Surrounding whitespace is ignored.
|
|
623
|
+
* @returns The spelled interval. An ascending name carries a non-negative span
|
|
624
|
+
* and no `descending` flag.
|
|
625
|
+
* @throws If the name is not a quality label followed by a number, or the two
|
|
626
|
+
* cannot describe the same interval. Use {@link tryParseInterval} where
|
|
627
|
+
* failure is ordinary, such as an interval field read on every keystroke.
|
|
628
|
+
* @example
|
|
629
|
+
* ```ts
|
|
630
|
+
* import { parseInterval } from '@libraz/libcantus';
|
|
631
|
+
* parseInterval('m3'); // { number: 3, quality: 'm', semitones: 3 }
|
|
632
|
+
* parseInterval('-m3'); // { number: 3, quality: 'm', semitones: -3, descending: true }
|
|
633
|
+
* ```
|
|
634
|
+
* @category Pitch & Intervals
|
|
635
|
+
*/
|
|
636
|
+
declare function parseInterval(name: string): SpelledInterval;
|
|
637
|
+
/**
|
|
638
|
+
* Parse an interval name, reporting failure instead of throwing it.
|
|
639
|
+
*
|
|
640
|
+
* The same reading as {@link parseInterval} — that function is this one with
|
|
641
|
+
* its error thrown — for the callers where a name that does not parse yet is
|
|
642
|
+
* the normal state of the input rather than a fault.
|
|
643
|
+
*
|
|
644
|
+
* @param name The interval name.
|
|
645
|
+
* @returns The interval, or the error explaining why the text is not one.
|
|
646
|
+
* @example
|
|
647
|
+
* ```ts
|
|
648
|
+
* import { tryParseInterval } from '@libraz/libcantus';
|
|
649
|
+
* const result = tryParseInterval('M5');
|
|
650
|
+
* result.ok ? result.value.semitones : result.error.message; // a fifth cannot be major
|
|
651
|
+
* ```
|
|
652
|
+
* @category Pitch & Intervals
|
|
653
|
+
*/
|
|
654
|
+
declare function tryParseInterval(name: string): ParseResult<SpelledInterval>;
|
|
655
|
+
/**
|
|
656
|
+
* Anything that names a spelled interval: an interval name, plain interval
|
|
657
|
+
* data, or a value that serializes to interval data such as the `Interval`
|
|
658
|
+
* class.
|
|
659
|
+
*
|
|
660
|
+
* @category Pitch & Intervals
|
|
661
|
+
*/
|
|
662
|
+
type IntervalLike = string | SpelledInterval | {
|
|
663
|
+
/** The interval data this value stands for. */
|
|
664
|
+
toJSON(): SpelledInterval;
|
|
665
|
+
};
|
|
666
|
+
/**
|
|
667
|
+
* Resolve any interval-shaped value to plain {@link SpelledInterval} data.
|
|
668
|
+
*
|
|
669
|
+
* This is what lets an entry point accept an interval name, the plain data the
|
|
670
|
+
* pitch module returns, or an `Interval` instance without every caller
|
|
671
|
+
* branching on the form. An instance is accepted through its `toJSON` method
|
|
672
|
+
* rather than by its type, because the core layer cannot import the model layer
|
|
673
|
+
* that defines the class.
|
|
674
|
+
*
|
|
675
|
+
* @param value An interval name, plain interval data, or a value whose `toJSON`
|
|
676
|
+
* returns interval data.
|
|
677
|
+
* @returns The validated interval data, carrying `descending` only when the
|
|
678
|
+
* interval descends.
|
|
679
|
+
* @throws If the value is not interval-shaped, or its number, quality, and span
|
|
680
|
+
* do not describe the same interval.
|
|
681
|
+
* @example
|
|
682
|
+
* ```ts
|
|
683
|
+
* import { toSpelledInterval } from '@libraz/libcantus';
|
|
684
|
+
* toSpelledInterval('P5'); // { number: 5, quality: 'P', semitones: 7 }
|
|
685
|
+
* toSpelledInterval({ number: 5, quality: 'P', semitones: -7 });
|
|
686
|
+
* // { number: 5, quality: 'P', semitones: -7, descending: true }
|
|
687
|
+
* ```
|
|
688
|
+
* @category Pitch & Intervals
|
|
689
|
+
*/
|
|
690
|
+
declare function toSpelledInterval(value: IntervalLike): SpelledInterval;
|
|
691
|
+
/**
|
|
692
|
+
* Anything that names a spelled note: a note name, plain note data, a MIDI
|
|
693
|
+
* number, or a value that serializes to note data such as the `Note` class.
|
|
694
|
+
*
|
|
695
|
+
* @category Pitch & Intervals
|
|
696
|
+
*/
|
|
697
|
+
type NoteLike = string | number | Note | {
|
|
698
|
+
/** The note data this value stands for. */
|
|
699
|
+
toJSON(): Note;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* Resolve any note-shaped value to plain {@link Note} data.
|
|
703
|
+
*
|
|
704
|
+
* The counterpart of {@link toSpelledInterval} for notes: an entry point takes
|
|
705
|
+
* whatever form the caller has — the name a text field holds, the MIDI number a
|
|
706
|
+
* device sends, the data the pitch module returns, or a `Note` instance — and
|
|
707
|
+
* gets one shape back. An instance is accepted through its `toJSON` method
|
|
708
|
+
* rather than by its type, because the core layer cannot import the model layer
|
|
709
|
+
* that defines the class.
|
|
710
|
+
*
|
|
711
|
+
* A number is read as MIDI and spelled with sharps, the same reading
|
|
712
|
+
* {@link midiToNote} takes by default. Where a key is known the caller spells
|
|
713
|
+
* it there instead — `spellPitch` reads the key — because no single default
|
|
714
|
+
* suits both a G# in E major and an Ab in Eb major.
|
|
715
|
+
*
|
|
716
|
+
* @param value A note name, a MIDI number, plain note data, or a value whose
|
|
717
|
+
* `toJSON` returns note data.
|
|
718
|
+
* @returns The validated note data, carrying `octave` only when the value has
|
|
719
|
+
* one.
|
|
720
|
+
* @throws If the value is not note-shaped, or its letter, alteration, or octave
|
|
721
|
+
* fall outside the range a spelled note holds.
|
|
722
|
+
* @example
|
|
723
|
+
* ```ts
|
|
724
|
+
* import { toNoteData } from '@libraz/libcantus';
|
|
725
|
+
* toNoteData('Eb4'); // { letter: 2, alter: -1, octave: 4 }
|
|
726
|
+
* toNoteData(60); // { letter: 0, alter: 0, octave: 4 }
|
|
727
|
+
* ```
|
|
728
|
+
* @category Pitch & Intervals
|
|
729
|
+
*/
|
|
730
|
+
declare function toNoteData(value: NoteLike): Note;
|
|
731
|
+
/**
|
|
732
|
+
* Transpose a note by a spelled interval, keeping the spelling the interval
|
|
733
|
+
* names.
|
|
734
|
+
*
|
|
735
|
+
* Unlike {@link transposeNote}, which picks a letter from the semitone count,
|
|
736
|
+
* the diatonic number decides the letter here: transposing C by an augmented
|
|
737
|
+
* second gives D#, not Eb.
|
|
738
|
+
*
|
|
739
|
+
* @param note The note to transpose.
|
|
740
|
+
* @param interval The interval to apply; a negative span transposes downward.
|
|
741
|
+
* @returns The transposed note, octave-less if the source was.
|
|
742
|
+
* @example
|
|
743
|
+
* ```ts
|
|
744
|
+
* import { formatNote, parseInterval, parseNote, transposeByInterval } from '@libraz/libcantus';
|
|
745
|
+
* formatNote(transposeByInterval(parseNote('C4'), parseInterval('A2'))); // 'D#4'
|
|
746
|
+
* ```
|
|
747
|
+
* @category Pitch & Intervals
|
|
748
|
+
*/
|
|
749
|
+
declare function transposeByInterval(note: Note, interval: SpelledInterval): Note;
|
|
750
|
+
/**
|
|
751
|
+
* The spelled interval from one note to another.
|
|
752
|
+
*
|
|
753
|
+
* When both notes carry octaves, the interval reflects their full signed
|
|
754
|
+
* distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
|
|
755
|
+
* interval is measured within a single ascending octave from `a` to `b`.
|
|
756
|
+
*
|
|
757
|
+
* @param a The lower/first note.
|
|
758
|
+
* @param b The upper/second note.
|
|
759
|
+
* @returns The diatonic number, quality, and semitone span.
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* import { spelledInterval, parseNote } from '@libraz/libcantus';
|
|
763
|
+
* spelledInterval(parseNote('C4'), parseNote('G4'));
|
|
764
|
+
* // { number: 5, quality: 'P', semitones: 7 }
|
|
765
|
+
* ```
|
|
766
|
+
* @category Pitch & Intervals
|
|
767
|
+
*/
|
|
768
|
+
declare function spelledInterval(a: Note, b: Note): SpelledInterval;
|
|
769
|
+
|
|
770
|
+
export { ARTICULATIONS as A, BudgetExceededError as B, toSpelledInterval as C, transposeByInterval as D, transposeNote as E, tryParseInterval as F, tryParseKeyName as G, tryParseNote as H, type IntervalLike as I, type KeyName as K, type LibcantusError as L, NoSolutionError as N, type ParseResult as P, type SpelledInterval as S, type Articulation as a, type IntervalQualityLabel as b, InvalidInputError as c, type KeyScale as d, type LibcantusErrorCode as e, type Note as f, type NoteEvent as g, type NoteLike as h, type NoteNameOptions as i, type NoteNameSystem as j, detectNoteNameSystem as k, diatonicLetterOf as l, formatKeyName as m, formatNote as n, intervalSemitones as o, isLibcantusError as p, midiToNote as q, naturalPitchClassOf as r, noteToMidi as s, noteToPitchClass as t, parseInterval as u, parseKeyName as v, parseNote as w, pitchClassOf as x, spelledInterval as y, toNoteData as z };
|