@miadi/ava8-measure 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 +21 -0
- package/README.md +174 -0
- package/dist/audio.d.ts +343 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +821 -0
- package/dist/audio.js.map +1 -0
- package/dist/fft.d.ts +51 -0
- package/dist/fft.d.ts.map +1 -0
- package/dist/fft.js +246 -0
- package/dist/fft.js.map +1 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/midi.d.ts +333 -0
- package/dist/midi.d.ts.map +1 -0
- package/dist/midi.js +651 -0
- package/dist/midi.js.map +1 -0
- package/dist/movement.d.ts +431 -0
- package/dist/movement.d.ts.map +1 -0
- package/dist/movement.js +701 -0
- package/dist/movement.js.map +1 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@miadi/ava8-measure` — read a rendered artefact and hold it to a claim.
|
|
3
|
+
*
|
|
4
|
+
* Everything else in the family writes. This reads back, and the difference is
|
|
5
|
+
* the whole point: a generator's header states an intention, and at least three
|
|
6
|
+
* tools sit between that intention and the bytes on disk. Each of them has
|
|
7
|
+
* silently changed something at least once.
|
|
8
|
+
*
|
|
9
|
+
* The three measurement surfaces stay behind namespaces, because they are
|
|
10
|
+
* three different subjects and their vocabularies collide on purpose — `read`
|
|
11
|
+
* means one thing for a MIDI file and another for a movement capture, and
|
|
12
|
+
* flattening them into one namespace would force one of them to be renamed to
|
|
13
|
+
* something less honest.
|
|
14
|
+
*
|
|
15
|
+
* ```js
|
|
16
|
+
* import { midi, verify } from "@miadi/ava8-measure"
|
|
17
|
+
*
|
|
18
|
+
* const m = midi.read(bytes)
|
|
19
|
+
* const verdict = verify(bytes, { voidBand: {lo: 45, hi: 53}, mode: "ddorian" })
|
|
20
|
+
* if (!verdict.ok) for (const f of verdict.failed) console.error(f.detail)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export * as midi from "./midi.js";
|
|
24
|
+
export * as movement from "./movement.js";
|
|
25
|
+
export * as fft from "./fft.js";
|
|
26
|
+
export * as audio from "./audio.js";
|
|
27
|
+
import * as midiNS from "./midi.js";
|
|
28
|
+
/**
|
|
29
|
+
* The wire value, counted from zero. ABC writes `%%MIDI channel 10` and every
|
|
30
|
+
* manual says ten; the four bits in the file say nine. A constant of 10 here
|
|
31
|
+
* would exclude channel 11 and let every drum hit through as an intruder in a
|
|
32
|
+
* void band — an open hi-hat is note 46, which sits inside 45-53.
|
|
33
|
+
*/
|
|
34
|
+
const DRUM_CHANNEL = 9;
|
|
35
|
+
/**
|
|
36
|
+
* A pitch with its name, or the fact that there is no pitch there.
|
|
37
|
+
*
|
|
38
|
+
* `firstDivergence` carries `null` on the side that ran out of notes, and
|
|
39
|
+
* printing `null (undefined)` at the exact moment a person is trying to find
|
|
40
|
+
* out what changed is worse than saying nothing.
|
|
41
|
+
*/
|
|
42
|
+
const named = (pitch) => pitch === null ? "no note (the file ended)" : `${pitch} (${midiNS.pitchName(pitch)})`;
|
|
43
|
+
/**
|
|
44
|
+
* Hold a rendered MIDI to what its author says about it.
|
|
45
|
+
*
|
|
46
|
+
* Nothing here trusts a generator's header. Every number comes out of the
|
|
47
|
+
* bytes.
|
|
48
|
+
*
|
|
49
|
+
* @param bytes the rendered file — not the ABC, not the score, the artefact
|
|
50
|
+
* @param claim what the piece says about itself
|
|
51
|
+
* @param parseMode a mode parser, normally `parseMode` from `@miadi/ava8-atelier`.
|
|
52
|
+
* It is injected rather than imported so that this function stays usable
|
|
53
|
+
* when a caller has their own vocabulary, and so a missing mode name is the
|
|
54
|
+
* caller's error rather than a silent pass.
|
|
55
|
+
*/
|
|
56
|
+
export function verify(bytes, claim, parseMode) {
|
|
57
|
+
const read = midiNS.read(bytes);
|
|
58
|
+
const findings = [];
|
|
59
|
+
const note = (name, held, claimed, found, detail) => findings.push({ claim: name, held, claimed, found, detail });
|
|
60
|
+
// -- the register left empty on purpose ----------------------------------
|
|
61
|
+
if (claim.voidBand) {
|
|
62
|
+
const { lo, hi, name = `${claim.voidBand.lo}-${claim.voidBand.hi}` } = claim.voidBand;
|
|
63
|
+
const intruders = [
|
|
64
|
+
...new Set(read.notes
|
|
65
|
+
.filter((n) => n.channel !== DRUM_CHANNEL)
|
|
66
|
+
.filter((n) => n.pitch >= lo && n.pitch <= hi)
|
|
67
|
+
.map((n) => n.pitch)),
|
|
68
|
+
].sort((a, b) => a - b);
|
|
69
|
+
note("voidBand", intruders.length === 0, `${lo}-${hi} empty`, intruders, intruders.length === 0
|
|
70
|
+
? `band ${name} (${lo}-${hi}) is empty of melodic notes, as claimed`
|
|
71
|
+
: `band ${name} (${lo}-${hi}) claims to be empty and holds ${intruders.length} ` +
|
|
72
|
+
`pitch(es): ${intruders.map((p) => `${p} (${midiNS.pitchName(p)})`).join(", ")}. ` +
|
|
73
|
+
`Channel ${DRUM_CHANNEL} was excluded, so these are not drums.`);
|
|
74
|
+
}
|
|
75
|
+
// -- the mode -------------------------------------------------------------
|
|
76
|
+
if (claim.mode) {
|
|
77
|
+
if (!parseMode) {
|
|
78
|
+
note("mode", false, claim.mode, null, `a mode was claimed but no parser was passed; import parseMode from ` +
|
|
79
|
+
`@miadi/ava8-atelier and hand it in — guessing the vocabulary here ` +
|
|
80
|
+
`would turn an unreadable spelling into a pass`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const floor = claim.minPurity ?? 1;
|
|
84
|
+
const purity = midiNS.modePurity(read.notes, parseMode(claim.mode));
|
|
85
|
+
const outside = purity.outside
|
|
86
|
+
.map((o) => `${midiNS.PC_NAMES[o.pc]} ${(o.share * 100).toFixed(2)} %`)
|
|
87
|
+
.join(" · ");
|
|
88
|
+
note("mode", purity.purity >= floor - 1e-12, `${claim.mode} at ${(floor * 100).toFixed(2)} % or better`, purity.purity, purity.purity >= floor - 1e-12
|
|
89
|
+
? `${(purity.purity * 100).toFixed(2)} % of duration is inside ${claim.mode}`
|
|
90
|
+
: `${claim.mode} claims ${(floor * 100).toFixed(2)} % purity and the file ` +
|
|
91
|
+
`measures ${(purity.purity * 100).toFixed(2)} %. Outside: ${outside || "nothing"}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// -- tempo, read as events, not as a header -------------------------------
|
|
95
|
+
if (claim.tempos) {
|
|
96
|
+
const tol = claim.tempoTolerance ?? 0.5;
|
|
97
|
+
const found = read.tempos.map((t) => t.bpm);
|
|
98
|
+
const held = found.length === claim.tempos.length &&
|
|
99
|
+
claim.tempos.every((want, i) => Math.abs((found[i] ?? Number.NaN) - want) <= tol);
|
|
100
|
+
note("tempos", held, claim.tempos, found, held
|
|
101
|
+
? `${found.length} tempo event(s), matching the claim within ${tol} bpm`
|
|
102
|
+
: `${claim.tempos.length} tempo event(s) claimed, ${found.length} in the file: ` +
|
|
103
|
+
`${found.map((b) => b.toFixed(2)).join(" → ") || "none"}. ` +
|
|
104
|
+
`A bare Q: line in the body of a multi-voice tune is dropped in silence — ` +
|
|
105
|
+
`write it inline as [Q:1/4=…] and it survives.`);
|
|
106
|
+
}
|
|
107
|
+
// -- the drums, by position and not by count ------------------------------
|
|
108
|
+
if (claim.kickPositions) {
|
|
109
|
+
const dp = midiNS.drumPositions(read.notes, read.division, read.timeSignatures);
|
|
110
|
+
// 36 is the kick. `byPitch` maps pitch -> (eighth slot -> hit count), so the
|
|
111
|
+
// slots a kick actually occupied are that inner map's keys — the count is
|
|
112
|
+
// deliberately not what is compared here, because the right number of hits
|
|
113
|
+
// in the wrong places is exactly the failure this claim exists to catch.
|
|
114
|
+
const found = [...(dp.byPitch.get(36)?.keys() ?? [])].sort((a, b) => a - b);
|
|
115
|
+
const want = [...new Set(claim.kickPositions)].sort((a, b) => a - b);
|
|
116
|
+
const held = found.length === want.length && found.every((p, i) => p === want[i]);
|
|
117
|
+
note("kickPositions", held, want, found, held
|
|
118
|
+
? `the kick lands on ${found.join(", ")} and nowhere else`
|
|
119
|
+
: `the kick claims ${want.join(", ")} and lands on ${found.join(", ") || "nothing"}. ` +
|
|
120
|
+
`The right number of hits in the wrong places is the failure a count cannot see.`);
|
|
121
|
+
}
|
|
122
|
+
// -- length ----------------------------------------------------------------
|
|
123
|
+
if (claim.seconds) {
|
|
124
|
+
const last = read.notes.reduce((m, n) => Math.max(m, n.endTick), 0);
|
|
125
|
+
const found = midiNS.tickToSeconds(last, read.division, read.tempos);
|
|
126
|
+
const held = Math.abs(found - claim.seconds.value) <= claim.seconds.tolerance;
|
|
127
|
+
note("seconds", held, claim.seconds.value, found, held
|
|
128
|
+
? `${found.toFixed(2)} s, within ${claim.seconds.tolerance} s of the claim`
|
|
129
|
+
: `${claim.seconds.value} s claimed, ${found.toFixed(2)} s measured`);
|
|
130
|
+
}
|
|
131
|
+
// -- the same material as another rendering -------------------------------
|
|
132
|
+
if (claim.source) {
|
|
133
|
+
const how = claim.sourceMatch ?? "notes";
|
|
134
|
+
const same = midiNS.samePitches(claim.source, bytes);
|
|
135
|
+
const held = how === "notes" ? same.noteForNote : same.multiset;
|
|
136
|
+
note("source", held, how === "notes" ? "note for note" : "the same notes in any order", { noteForNote: same.noteForNote, multiset: same.multiset, countA: same.countA, countB: same.countB }, held
|
|
137
|
+
? how === "notes"
|
|
138
|
+
? `${same.countB} notes, identical to the source in order and pitch`
|
|
139
|
+
: `the same ${same.countB} pitches as the source, reordered`
|
|
140
|
+
: same.firstDivergence
|
|
141
|
+
? `first divergence at note ${same.firstDivergence.index}: ` +
|
|
142
|
+
`source ${named(same.firstDivergence.a)}, this file ${named(same.firstDivergence.b)}`
|
|
143
|
+
: `${same.countA} notes in the source, ${same.countB} here`);
|
|
144
|
+
}
|
|
145
|
+
// -- how many notes there are ---------------------------------------------
|
|
146
|
+
if (claim.noteCount !== undefined) {
|
|
147
|
+
note("noteCount", read.notes.length === claim.noteCount, claim.noteCount, read.notes.length, read.notes.length === claim.noteCount
|
|
148
|
+
? `${read.notes.length} notes, as claimed`
|
|
149
|
+
: `${claim.noteCount} notes claimed, ${read.notes.length} found`);
|
|
150
|
+
}
|
|
151
|
+
// -- a note that never ends is a defect nobody claimed --------------------
|
|
152
|
+
if (read.unclosed > 0) {
|
|
153
|
+
note("unclosed", false, "every note ends", read.unclosed, `${read.unclosed} note-on(s) never received a note-off. Nothing claimed this; ` +
|
|
154
|
+
`it is reported because a hanging note plays until the file does.`);
|
|
155
|
+
}
|
|
156
|
+
const failed = findings.filter((f) => !f.held);
|
|
157
|
+
return {
|
|
158
|
+
ok: failed.length === 0,
|
|
159
|
+
findings,
|
|
160
|
+
failed,
|
|
161
|
+
checked: findings.map((f) => f.claim),
|
|
162
|
+
read,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One line per finding, for a terminal.
|
|
167
|
+
*
|
|
168
|
+
* A verdict with nothing checked prints as such rather than as a pass — the
|
|
169
|
+
* shape of the output has to make "I verified nothing" look different from
|
|
170
|
+
* "I verified everything and it held".
|
|
171
|
+
*/
|
|
172
|
+
export function formatVerdict(verdict) {
|
|
173
|
+
if (verdict.findings.length === 0) {
|
|
174
|
+
return "nothing was claimed, so nothing was checked — this is not a pass";
|
|
175
|
+
}
|
|
176
|
+
const lines = verdict.findings.map((f) => `${f.held ? " ok " : " FAIL "} ${f.claim} ${f.detail}`);
|
|
177
|
+
const failed = verdict.failed.length;
|
|
178
|
+
lines.push(failed === 0
|
|
179
|
+
? `all ${verdict.findings.length} claim(s) held`
|
|
180
|
+
: `${failed} of ${verdict.findings.length} claim(s) failed`);
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/midi.d.ts
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* midi.ts — read a *rendered* Standard MIDI File and prove what is inside it.
|
|
3
|
+
*
|
|
4
|
+
* Hand-written, and it must stay hand-written. The Python this is ported from
|
|
5
|
+
* (`atelier_midi.py`) refuses to import `mido`, `pretty_midi` or `music21`
|
|
6
|
+
* because none of them exist on the host the atelier runs on, and a plugin
|
|
7
|
+
* that assumes them fails on the one machine it was built for. The same
|
|
8
|
+
* discipline here costs nothing and buys everything: a decoder you can audit
|
|
9
|
+
* in one sitting, with no dependency that can rot underneath it.
|
|
10
|
+
*
|
|
11
|
+
* The port keeps that promise and adds one of its own — **bytes in, never a
|
|
12
|
+
* path**. `read` takes a `Uint8Array` and uses `DataView`, so the whole
|
|
13
|
+
* measuring surface runs unchanged in a browser. Nothing here touches
|
|
14
|
+
* `node:fs`, and nothing here touches `Buffer`.
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS MODULE IS FOR
|
|
17
|
+
* Verification re-reads the rendered artefact, never the source. A note
|
|
18
|
+
* count that comes out right in the generator is not proof; the same count
|
|
19
|
+
* read back out of the .mid that `abc2midi` produced is. Every function
|
|
20
|
+
* answers one question that decided something in the atelier:
|
|
21
|
+
*
|
|
22
|
+
* registers() did a voice land in a register it was told to avoid
|
|
23
|
+
* bandOccupancy() is the singer's band empty (the 45-53 rule)
|
|
24
|
+
* pitchClasses() what mode does the rendered piece actually sit in
|
|
25
|
+
* modePurity() how much of it stays inside the field
|
|
26
|
+
* samePitches() were his notes changed between source and render
|
|
27
|
+
* drumPositions() is the kick really on the floor, eighth by eighth
|
|
28
|
+
* tempos did the mid-tune tempo change survive (a bare Q: does not)
|
|
29
|
+
*
|
|
30
|
+
* UNITS
|
|
31
|
+
* Tick durations are musical durations. Pitch-class and purity shares are
|
|
32
|
+
* weighted in ticks on purpose: they describe the written field, and a tempo
|
|
33
|
+
* change must not reweight it. Anything reported in seconds says so.
|
|
34
|
+
*
|
|
35
|
+
* @packageDocumentation
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Channel index 9 — "channel 10" in ABC, in General MIDI, and in every manual.
|
|
39
|
+
*
|
|
40
|
+
* Note the deliberate divergence from `@miadi/ava8-atelier`, whose
|
|
41
|
+
* `DRUM_CHANNEL` is `10` because it names the channel the way a musician
|
|
42
|
+
* writes it. A decoder reads the wire, and on the wire the nibble is 9.
|
|
43
|
+
*/
|
|
44
|
+
export declare const DRUM_CHANNEL = 9;
|
|
45
|
+
/** The General MIDI drum names the atelier actually uses, by note number. */
|
|
46
|
+
export declare const GM_DRUM_NAMES: Readonly<Record<number, string>>;
|
|
47
|
+
/** Sharp spellings, because a decoder has no key signature to spell against. */
|
|
48
|
+
export declare const PC_NAMES: readonly string[];
|
|
49
|
+
/** One paired note-on/note-off. Ticks are absolute within its own track. */
|
|
50
|
+
export interface Note {
|
|
51
|
+
readonly startTick: number;
|
|
52
|
+
readonly endTick: number;
|
|
53
|
+
readonly pitch: number;
|
|
54
|
+
readonly velocity: number;
|
|
55
|
+
readonly channel: number;
|
|
56
|
+
readonly track: number;
|
|
57
|
+
}
|
|
58
|
+
/** Sounding length in ticks. A musical duration, never seconds. */
|
|
59
|
+
export declare function noteDuration(note: Note): number;
|
|
60
|
+
/** One `set tempo` meta event, with the bpm already worked out. */
|
|
61
|
+
export interface Tempo {
|
|
62
|
+
readonly tick: number;
|
|
63
|
+
readonly usecPerQuarter: number;
|
|
64
|
+
readonly bpm: number;
|
|
65
|
+
readonly track: number;
|
|
66
|
+
}
|
|
67
|
+
/** One `time signature` meta event, denominator already un-log2'd. */
|
|
68
|
+
export interface TimeSignature {
|
|
69
|
+
readonly tick: number;
|
|
70
|
+
readonly numerator: number;
|
|
71
|
+
readonly denominator: number;
|
|
72
|
+
readonly clocksPerClick: number;
|
|
73
|
+
readonly notated32ndPerQuarter: number;
|
|
74
|
+
readonly track: number;
|
|
75
|
+
}
|
|
76
|
+
/** Everything read out of one file. */
|
|
77
|
+
export interface MidiRead {
|
|
78
|
+
readonly notes: Note[];
|
|
79
|
+
readonly division: number;
|
|
80
|
+
readonly tempos: Tempo[];
|
|
81
|
+
readonly timeSignatures: TimeSignature[];
|
|
82
|
+
readonly format: number;
|
|
83
|
+
readonly nTracks: number;
|
|
84
|
+
readonly trackNames: Map<number, string>;
|
|
85
|
+
readonly unclosed: number;
|
|
86
|
+
/** Whatever label the caller passed in. The reader never opened anything. */
|
|
87
|
+
readonly path: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read a Standard MIDI File into paired notes. This is the only door in.
|
|
91
|
+
*
|
|
92
|
+
* Handles: format 0 and 1 (and 2, read as independent tracks), running status,
|
|
93
|
+
* meta events, sysex (`0xF0` and the `0xF7` escape form), tempo and
|
|
94
|
+
* time-signature maps, unknown chunk types (skipped by their declared length,
|
|
95
|
+
* as the spec instructs), and note-on-with-velocity-0 as note-off.
|
|
96
|
+
*
|
|
97
|
+
* Note-offs are paired FIFO per `(track, channel, pitch)` — pairing on pitch
|
|
98
|
+
* alone silently merges two voices that happen to share a pitch on different
|
|
99
|
+
* channels, and the merge is invisible in the output, which is the worst kind
|
|
100
|
+
* of wrong. Notes left hanging at end of track are closed at the last tick
|
|
101
|
+
* seen and counted in `unclosed`; a non-zero count means the file is malformed
|
|
102
|
+
* and every duration-weighted measure below it is approximate.
|
|
103
|
+
*
|
|
104
|
+
* The sort order is load-bearing, not cosmetic: `(startTick, track, channel,
|
|
105
|
+
* pitch)`. {@link samePitches} re-sorts on top of it and relies on a stable
|
|
106
|
+
* sort to keep the track/channel order under equal pitches, so two readers
|
|
107
|
+
* that disagree here will disagree about whether a file was altered.
|
|
108
|
+
*
|
|
109
|
+
* @param bytes the whole file. Never a path — the library must run in a browser.
|
|
110
|
+
* @param path a label to carry through for reporting. Nothing is opened.
|
|
111
|
+
*/
|
|
112
|
+
export declare function read(bytes: Uint8Array, path?: string): MidiRead;
|
|
113
|
+
/**
|
|
114
|
+
* The canonical note order: `(startTick, track, channel, pitch)`.
|
|
115
|
+
*
|
|
116
|
+
* Exported because it is a contract, not an implementation detail. Reproduce
|
|
117
|
+
* it wrong and {@link samePitches} reports a file altered that was not, or
|
|
118
|
+
* misses one that was. Ties on `startTick` are broken by track first, which is
|
|
119
|
+
* why a two-voice render reads voice-by-voice inside each chord.
|
|
120
|
+
*/
|
|
121
|
+
export declare function compareNotes(x: Note, y: Note): number;
|
|
122
|
+
/**
|
|
123
|
+
* Ticks per quarter note, or `null` when the file uses SMPTE timing.
|
|
124
|
+
*
|
|
125
|
+
* A SMPTE division (the 0x8000 bit) carries frames and subframes, not a beat
|
|
126
|
+
* grid, so every bar-relative measure — {@link drumPositions} above all — must
|
|
127
|
+
* refuse rather than guess. Returning `null` is how the refusal is made
|
|
128
|
+
* unmissable at the type level.
|
|
129
|
+
*/
|
|
130
|
+
export declare function ticksPerBeat(division: number): number | null;
|
|
131
|
+
/**
|
|
132
|
+
* Absolute seconds for a tick, walking the tempo map. Reporting only.
|
|
133
|
+
*
|
|
134
|
+
* Used to say *when* something happened out loud, to a human who is listening
|
|
135
|
+
* rather than counting. No musical decision is taken on seconds: they drift
|
|
136
|
+
* with every tempo event, tick durations do not. With no tempo written, the
|
|
137
|
+
* SMF default of 120 bpm applies — which is exactly the trap a bare `Q:` in an
|
|
138
|
+
* ABC body falls into.
|
|
139
|
+
*/
|
|
140
|
+
export declare function tickToSeconds(tick: number, division: number, tempos?: readonly Tempo[]): number;
|
|
141
|
+
/** Pitch extent, note count and channel set for one track. */
|
|
142
|
+
export interface TrackRegister {
|
|
143
|
+
min: number;
|
|
144
|
+
max: number;
|
|
145
|
+
count: number;
|
|
146
|
+
span: number;
|
|
147
|
+
channels: number[];
|
|
148
|
+
pitches: number[];
|
|
149
|
+
}
|
|
150
|
+
/** Where two tracks share a pitch region, and which pitches exactly. */
|
|
151
|
+
export interface RegisterOverlap {
|
|
152
|
+
a: number;
|
|
153
|
+
b: number;
|
|
154
|
+
low: number;
|
|
155
|
+
high: number;
|
|
156
|
+
semitones: number;
|
|
157
|
+
sharedPitches: number[];
|
|
158
|
+
}
|
|
159
|
+
export interface Registers {
|
|
160
|
+
tracks: Map<number, TrackRegister>;
|
|
161
|
+
overlaps: RegisterOverlap[];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Per-track pitch extent and count, plus the overlap between every pair.
|
|
165
|
+
*
|
|
166
|
+
* Decides: whether a voice went where it was told not to go, and whether two
|
|
167
|
+
* voices meant to stay out of each other's way in fact cross. The atelier
|
|
168
|
+
* reads this before publishing, on the *rendered* file — an ABC window that
|
|
169
|
+
* looks disjoint on paper can still collide once `abc2midi` has chosen an
|
|
170
|
+
* octave, because `clef=treble-8` sounds an octave below what is written.
|
|
171
|
+
*
|
|
172
|
+
* Overlaps come back as an array ordered by `(a, b)` rather than a
|
|
173
|
+
* tuple-keyed map, because JavaScript has no tuple key that survives a round
|
|
174
|
+
* trip through JSON.
|
|
175
|
+
*/
|
|
176
|
+
export declare function registers(notes: Iterable<Note>): Registers;
|
|
177
|
+
export interface BandOccupancy {
|
|
178
|
+
low: number;
|
|
179
|
+
high: number;
|
|
180
|
+
count: number;
|
|
181
|
+
total: number;
|
|
182
|
+
share: number;
|
|
183
|
+
empty: boolean;
|
|
184
|
+
byTrack: Map<number, number>;
|
|
185
|
+
notes: Note[];
|
|
186
|
+
pitches: number[];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* How many notes fall inside a MIDI range, inclusive.
|
|
190
|
+
*
|
|
191
|
+
* This is how "the singer's band is empty" is proven, and it is the only check
|
|
192
|
+
* in the atelier that is pass/fail rather than a number to read. His band was
|
|
193
|
+
* measured on the day: 94.1 % of the park drone lives in MIDI 45-53, so 45-53
|
|
194
|
+
* stays empty in every piece written for him to sing over. Every other measure
|
|
195
|
+
* here reports and lets the human judge; this one has a verdict because the
|
|
196
|
+
* band belongs to a person, not to a threshold someone picked.
|
|
197
|
+
*
|
|
198
|
+
* Returns the count, the share of all notes, the offending notes, and which
|
|
199
|
+
* tracks they came from — naming the track is what makes the correction one
|
|
200
|
+
* edit instead of a hunt.
|
|
201
|
+
*/
|
|
202
|
+
export declare function bandOccupancy(notes: Iterable<Note>, lo: number, hi: number): BandOccupancy;
|
|
203
|
+
export interface PitchClassWeight {
|
|
204
|
+
ticks: number;
|
|
205
|
+
share: number;
|
|
206
|
+
events: number;
|
|
207
|
+
}
|
|
208
|
+
export interface PitchClasses {
|
|
209
|
+
totalTicks: number;
|
|
210
|
+
/** Twelve entries, indexed by pitch class. */
|
|
211
|
+
byPc: PitchClassWeight[];
|
|
212
|
+
/** Pitch classes ordered by descending ticks; ties stay in ascending pc order. */
|
|
213
|
+
ranked: number[];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Pitch-class histogram weighted by sounding duration, in ticks.
|
|
217
|
+
*
|
|
218
|
+
* Decides the mode. Counting note *events* lies: a drone struck once and held
|
|
219
|
+
* thirty seconds counts as one, and the piece then reads as whatever the busy
|
|
220
|
+
* voice happens to be doing. Weighting by duration is what found MI PHRYGIEN
|
|
221
|
+
* in his Songbird take — the mi2/re#2 beat held thirty seconds *was* the piece.
|
|
222
|
+
*
|
|
223
|
+
* The drum channel is excluded, because a kick is not a pitch class.
|
|
224
|
+
*/
|
|
225
|
+
export declare function pitchClasses(notes: Iterable<Note>): PitchClasses;
|
|
226
|
+
export interface ModePurity {
|
|
227
|
+
allowed: number[];
|
|
228
|
+
purity: number;
|
|
229
|
+
insideTicks: number;
|
|
230
|
+
totalTicks: number;
|
|
231
|
+
/** What left the field, heaviest first. */
|
|
232
|
+
outside: Array<{
|
|
233
|
+
pc: number;
|
|
234
|
+
share: number;
|
|
235
|
+
}>;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Share of sounding duration that sits inside a given pitch-class set.
|
|
239
|
+
*
|
|
240
|
+
* Decides whether a take stays in the field, and names what leaves it. The
|
|
241
|
+
* number is not a verdict on its own: 21 % outside the white field of the bed
|
|
242
|
+
* was not an error in his Songbird — it was the material the intruder in
|
|
243
|
+
* opus 019 is made of. Report the share, name the strays, let the human read.
|
|
244
|
+
*
|
|
245
|
+
* A mode given as text (`"ddorian"`, `"e phrygian"`, `"Bb-major"`, or a bare
|
|
246
|
+
* `"0,2,4,5,7,9,11"`) is handed to `parseMode` from `@miadi/ava8-atelier`.
|
|
247
|
+
* The atelier owns what a mode *is*; this package only owns what a rendered
|
|
248
|
+
* file *did*. Two copies of that table would eventually disagree, and the
|
|
249
|
+
* disagreement would surface as a purity number nobody could explain.
|
|
250
|
+
*/
|
|
251
|
+
export declare function modePurity(notes: Iterable<Note>, allowedPcs: string | Iterable<number>): ModePurity;
|
|
252
|
+
export interface SamePitches {
|
|
253
|
+
countA: number;
|
|
254
|
+
countB: number;
|
|
255
|
+
noteForNote: boolean;
|
|
256
|
+
multiset: boolean;
|
|
257
|
+
firstDivergence: {
|
|
258
|
+
index: number;
|
|
259
|
+
a: number | null;
|
|
260
|
+
b: number | null;
|
|
261
|
+
} | null;
|
|
262
|
+
/** Pitches whose count changed, ascending. `delta` is B minus A. */
|
|
263
|
+
multisetDelta: Array<{
|
|
264
|
+
pitch: number;
|
|
265
|
+
delta: number;
|
|
266
|
+
}>;
|
|
267
|
+
}
|
|
268
|
+
/** Either a whole file's bytes, or a note list already read. */
|
|
269
|
+
export type NoteSource = Uint8Array | Iterable<Note>;
|
|
270
|
+
/**
|
|
271
|
+
* Compare two renders note-for-note **and** as a multiset.
|
|
272
|
+
*
|
|
273
|
+
* This is what proves "his notes are unchanged". Two answers, and they are not
|
|
274
|
+
* the same answer — reporting only one of them would let a real change pass or
|
|
275
|
+
* would condemn an honest one:
|
|
276
|
+
*
|
|
277
|
+
* `noteForNote` same pitches in the same order. A variation that keeps his
|
|
278
|
+
* order — a register split, a re-voicing — passes this.
|
|
279
|
+
* `multiset` same pitches in any order. A mirror or a re-ordering passes
|
|
280
|
+
* only this, and that is then the honest claim to make about
|
|
281
|
+
* it: his material, his order changed.
|
|
282
|
+
*
|
|
283
|
+
* On divergence, `firstDivergence` gives the index and both pitches, which is
|
|
284
|
+
* where to look and nowhere else.
|
|
285
|
+
*
|
|
286
|
+
* The comparison re-sorts by `(startTick, pitch)` on top of the canonical
|
|
287
|
+
* order from {@link read}, and relies on the sort being stable so that equal
|
|
288
|
+
* pitches keep their track and channel order. That is why {@link compareNotes}
|
|
289
|
+
* is a contract.
|
|
290
|
+
*/
|
|
291
|
+
export declare function samePitches(a: NoteSource, b: NoteSource): SamePitches;
|
|
292
|
+
export interface DrumHit {
|
|
293
|
+
pitch: number;
|
|
294
|
+
name: string;
|
|
295
|
+
tick: number;
|
|
296
|
+
bar: number;
|
|
297
|
+
/** Rounded eighth-note slot, wrapped into the bar. */
|
|
298
|
+
eighth: number;
|
|
299
|
+
/** The unrounded position. A hit at 1.97 is a writer artefact you want to see. */
|
|
300
|
+
exact: number;
|
|
301
|
+
eighthsPerBar: number;
|
|
302
|
+
meter: string;
|
|
303
|
+
track: number;
|
|
304
|
+
}
|
|
305
|
+
export interface DrumPositions {
|
|
306
|
+
hits: DrumHit[];
|
|
307
|
+
count: number;
|
|
308
|
+
/** pitch -> (eighth slot -> hits), both ascending. */
|
|
309
|
+
byPitch: Map<number, Map<number, number>>;
|
|
310
|
+
names: Map<number, string>;
|
|
311
|
+
/** Only for 35 and 36, and only when they sound at all. */
|
|
312
|
+
fourOnTheFloor: Map<number, boolean>;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* For channel 10, the eighth-note position of every drum note within its bar.
|
|
316
|
+
*
|
|
317
|
+
* This is what proved four-on-the-floor: the kick reads 0, 2, 4, 6 and nothing
|
|
318
|
+
* else. Reading the ABC would only prove what was *written*; the grid that
|
|
319
|
+
* actually reaches the ear is the one in the rendered file.
|
|
320
|
+
*
|
|
321
|
+
* Bar length follows the time-signature map (numerator x 4/denominator
|
|
322
|
+
* quarters), with 4/4 assumed when the file declares nothing and for anything
|
|
323
|
+
* before the first declaration. `exact` is the unrounded position — a drum
|
|
324
|
+
* that lands on 1.97 instead of 2 is a rounding artefact of the writer, and
|
|
325
|
+
* you want to see that rather than have it quantised away behind your back.
|
|
326
|
+
*
|
|
327
|
+
* Refuses on SMPTE division, which carries no beat grid: there is no honest
|
|
328
|
+
* bar number to return, so it throws instead of inventing one.
|
|
329
|
+
*/
|
|
330
|
+
export declare function drumPositions(notes: Iterable<Note>, division: number, timeSignatures?: readonly TimeSignature[]): DrumPositions;
|
|
331
|
+
/** `60` -> `"C4"`. Sharp spellings, middle C at 60, as every decoder prints it. */
|
|
332
|
+
export declare function pitchName(p: number): string;
|
|
333
|
+
//# sourceMappingURL=midi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../src/midi.ts"],"names":[],"mappings":"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"}
|