linked-rolls 0.19.1 → 0.21.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 +31 -6
- package/lib/Collation.d.ts +1 -3
- package/lib/Collation.js +9 -11
- package/lib/Edition.d.ts +1 -9
- package/lib/EditionView.d.ts +54 -6
- package/lib/EditionView.js +93 -20
- package/lib/Emulation.js +5 -4
- package/lib/ReproducingSystem.d.ts +38 -2
- package/lib/RollCopy.d.ts +25 -2
- package/lib/RollCopy.js +30 -4
- package/lib/TrackerBar.d.ts +57 -2
- package/lib/TrackerBar.js +35 -17
- package/lib/Version.d.ts +49 -1
- package/lib/asJsonLd.js +23 -8
- package/lib/constraints.d.ts +8 -19
- package/lib/constraints.js +71 -15
- package/lib/context.d.ts +10 -1
- package/lib/context.js +10 -1
- package/lib/editionOps.d.ts +3 -1
- package/lib/editionOps.js +77 -15
- package/lib/importJsonLd.js +4 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/migrate.js +85 -11
- package/lib/readers/phillipsEroll.d.ts +4 -6
- package/lib/readers/phillipsEroll.js +6 -7
- package/lib/readers/spencerBar.d.ts +4 -5
- package/lib/readers/spencerBar.js +7 -8
- package/lib/readers/stanfordAton.d.ts +4 -7
- package/lib/readers/stanfordAton.js +4 -5
- package/lib/reservations.d.ts +1 -1
- package/lib/reservations.js +24 -2
- package/lib/schema.json +27 -5
- package/lib/spec/context.json +1 -0
- package/lib/spec/welte-licensee.context.json +10 -0
- package/lib/spec/welte-t98.context.json +10 -0
- package/lib/substitution.d.ts +73 -0
- package/lib/substitution.js +175 -0
- package/lib/systems/velocity.d.ts +34 -0
- package/lib/systems/velocity.js +36 -0
- package/lib/systems/welteLicensee/system.d.ts +46 -0
- package/lib/systems/welteLicensee/system.js +60 -0
- package/lib/systems/welteT100/system.d.ts +19 -16
- package/lib/systems/welteT100/system.js +42 -47
- package/lib/systems/welteT98/bar.d.ts +15 -2
- package/lib/systems/welteT98/bar.js +31 -3
- package/lib/systems/welteT98/system.d.ts +119 -0
- package/lib/systems/welteT98/system.js +245 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ tracker bar and a `perform` function; the core does not depend on any one
|
|
|
79
79
|
instrument's model.
|
|
80
80
|
|
|
81
81
|
The first system is the red Welte, `linked-rolls/welte-t100`, built on
|
|
82
|
-
[welte-
|
|
82
|
+
[welte-mignon-emulator](https://github.com/pfefferniels/welte-t100): the
|
|
83
83
|
take-up spool sets the time axis, the Nuancierbälge fill through their
|
|
84
84
|
conduits and are arrested by the Mezzoforte pin, and the two pedals travel
|
|
85
85
|
rather than switch. The nuancing constants come as `instruments`: the
|
|
@@ -103,11 +103,36 @@ emulation.emulateVersion(version, view)
|
|
|
103
103
|
const midi = emulation.asMIDI()
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
The
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
The second is the green Welte, `linked-rolls/welte-t98`. Everything
|
|
107
|
+
downstream of the relay is the same mechanism — Hagmann has the nuancing
|
|
108
|
+
unit built the same for both tracker scales (p. 96) — and what differs is
|
|
109
|
+
in front of it: each function lasts exactly as long as its own perforation
|
|
110
|
+
runs over the glide block rather than latching until a cancel line is read,
|
|
111
|
+
four conduits stand on one bellows and their drives add as flows, the
|
|
112
|
+
crescendo's ceiling is the balance of its throttle against a permanently
|
|
113
|
+
open bleed rather than a cap, the two pedals sit on the opposite edges of
|
|
114
|
+
the paper, and a long perforation on the bass sforzando-piano line sends
|
|
115
|
+
the roll back. Its constants are **not fitted**: `instruments.genuine` and
|
|
116
|
+
`instruments.derived` are empty until their fits are run, and until then a
|
|
117
|
+
playback runs on unfitted starting values, which every curve says in its
|
|
118
|
+
own `instrument` field.
|
|
119
|
+
|
|
120
|
+
Both systems share one velocity map, and `DynamicsCurve.travel` means the
|
|
121
|
+
same thing on both: the position on the printed ordinate of Welte's own
|
|
122
|
+
ruled band, 0 at that half's P.P. gridline and 1 at the shared F.F. line.
|
|
123
|
+
That is what lets a red issue and a green issue of one recording be
|
|
124
|
+
compared at all, since the band is ruled the same way on both.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { welteT98System } from 'linked-rolls/welte-t98'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The emulator is an optional peer dependency: an application that uses one
|
|
131
|
+
of the Welte systems installs `welte-mignon-emulator` itself, and one that
|
|
132
|
+
only reads editions does not need it. For development on both at once,
|
|
133
|
+
`npm link` a checkout of
|
|
134
|
+
[welte-t100](https://github.com/pfefferniels/welte-t100) into this
|
|
135
|
+
repository.
|
|
111
136
|
|
|
112
137
|
### Alignment, order and pairing
|
|
113
138
|
|
package/lib/Collation.d.ts
CHANGED
|
@@ -13,9 +13,7 @@ export interface CollationTolerance {
|
|
|
13
13
|
}
|
|
14
14
|
export declare const defaultCollationTolerance: CollationTolerance;
|
|
15
15
|
/** Where a symbol lies along the roll, as its carriers put it, or nothing for a symbol without a place. */
|
|
16
|
-
export type Locate = (symbol: AnySymbol) => Readonly<
|
|
17
|
-
horizontal: HorizontalSpan;
|
|
18
|
-
}> | undefined;
|
|
16
|
+
export type Locate = (symbol: AnySymbol) => Readonly<HorizontalSpan> | undefined;
|
|
19
17
|
/**
|
|
20
18
|
* Two symbols collate when they are of one kind, say the same thing
|
|
21
19
|
* (pitch, or expression type and scope), and lie at about the same
|
package/lib/Collation.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
+
import { keyOf } from "./TrackerBar";
|
|
1
2
|
import { distance, mm } from "./Quantity";
|
|
2
3
|
import { partitionPoint } from "./sorted";
|
|
3
4
|
export const defaultCollationTolerance = { toleranceStart: mm(5), toleranceEnd: mm(5) };
|
|
4
5
|
/**
|
|
5
6
|
* What a symbol says, as a key: the pitch of a note, the type and scope
|
|
6
|
-
* of an expression. Symbols collate within one key only.
|
|
7
|
+
* of an expression. Symbols collate within one key only. This is the
|
|
8
|
+
* same key the tracker bars are indexed by, so two symbols collate
|
|
9
|
+
* exactly where two bars would read them as the same thing, which is
|
|
10
|
+
* what carries a note across a transfer between systems.
|
|
7
11
|
*/
|
|
8
|
-
const kindOf = (symbol) =>
|
|
9
|
-
switch (symbol.type) {
|
|
10
|
-
case 'note': return `note ${symbol.pitch}`;
|
|
11
|
-
case 'expression': return `expression ${symbol.scope} ${symbol.expressionType}`;
|
|
12
|
-
case 'text': return 'text';
|
|
13
|
-
}
|
|
14
|
-
};
|
|
12
|
+
const kindOf = (symbol) => symbol.type === 'text' ? 'text' : keyOf(symbol);
|
|
15
13
|
const nearby = (here, there, tolerance) => distance(here.from, there.from) <= tolerance.toleranceStart
|
|
16
14
|
&& distance(here.to, there.to) <= tolerance.toleranceEnd;
|
|
17
15
|
/**
|
|
@@ -22,12 +20,12 @@ const nearby = (here, there, tolerance) => distance(here.from, there.from) <= to
|
|
|
22
20
|
export const isCollatable = (a, b, locate, tolerance = defaultCollationTolerance) => {
|
|
23
21
|
if (kindOf(a) !== kindOf(b))
|
|
24
22
|
return false;
|
|
25
|
-
const here = locate(a)
|
|
26
|
-
const there = locate(b)
|
|
23
|
+
const here = locate(a);
|
|
24
|
+
const there = locate(b);
|
|
27
25
|
return here !== undefined && there !== undefined && nearby(here, there, tolerance);
|
|
28
26
|
};
|
|
29
27
|
const placed = (symbols, locate) => symbols.flatMap((symbol, index) => {
|
|
30
|
-
const horizontal = locate(symbol)
|
|
28
|
+
const horizontal = locate(symbol);
|
|
31
29
|
return horizontal ? [{ symbol, index, horizontal }] : [];
|
|
32
30
|
});
|
|
33
31
|
const groupBy = (items, keyOf) => items.reduce((groups, item) => {
|
package/lib/Edition.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { RollCopy } from "./RollCopy";
|
|
|
2
2
|
import { Version } from "./Version";
|
|
3
3
|
import { CollationTolerance } from "./Collation";
|
|
4
4
|
import { DateAssignment, ObjectAssumption } from "./Assumption";
|
|
5
|
-
import {
|
|
5
|
+
import { Editor, Person, Place } from "./Agent";
|
|
6
6
|
import { RollTempo } from "./ReproducingSystem";
|
|
7
7
|
/**
|
|
8
8
|
* This type describes the creation of an edition,
|
|
@@ -91,14 +91,6 @@ export interface Roll {
|
|
|
91
91
|
* @see dcterms:identifier
|
|
92
92
|
*/
|
|
93
93
|
catalogueNumber: string;
|
|
94
|
-
/**
|
|
95
|
-
* The reproducing system the roll was cut for. A system the
|
|
96
|
-
* type vocabulary knows carries the IRI of its concept as `id`,
|
|
97
|
-
* from which the export takes the system's own context, so that
|
|
98
|
-
* the expression types are read as that system's.
|
|
99
|
-
* @see crm:P2 has type
|
|
100
|
-
*/
|
|
101
|
-
system: Concept;
|
|
102
94
|
/**
|
|
103
95
|
* @see lrmoo:R19i was realised through
|
|
104
96
|
*/
|
package/lib/EditionView.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Edition } from "./Edition";
|
|
2
|
-
import { HorizontalSpan,
|
|
2
|
+
import { HorizontalSpan, AnyFeature } from "./Feature";
|
|
3
3
|
import { AnySymbol, Expression, Note } from "./Symbol";
|
|
4
4
|
import { Version } from "./Version";
|
|
5
5
|
import { NegotiatedEvent } from "./ReproducingSystem";
|
|
6
|
+
import { TrackerBar } from "./TrackerBar";
|
|
7
|
+
import { RollCopy } from "./RollCopy";
|
|
6
8
|
import { Millimeters } from "./Quantity";
|
|
7
9
|
export type Path = (string | number)[];
|
|
8
10
|
export declare const getAt: <T>(path: Path, obj: unknown) => T | undefined;
|
|
@@ -20,6 +22,8 @@ export declare class EditionView {
|
|
|
20
22
|
* Map from id to paths where it is referenced
|
|
21
23
|
*/
|
|
22
24
|
private readonly links;
|
|
25
|
+
/** Built when a copy is first asked for, the walk not recording it. */
|
|
26
|
+
private copiesByFeature?;
|
|
23
27
|
constructor(edition: Edition);
|
|
24
28
|
atPath<T>(path: Path): T | null;
|
|
25
29
|
indexObjects(): void;
|
|
@@ -31,11 +35,46 @@ export declare class EditionView {
|
|
|
31
35
|
/** The version and its ancestors, from the version up to the root. */
|
|
32
36
|
private lineageOf;
|
|
33
37
|
carriersOf(symbol: AnySymbol): Readonly<AnyFeature>[];
|
|
38
|
+
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
39
|
+
copyOf(featureId: string): Readonly<RollCopy> | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* How a place on the edition's shared axis relates to the paper of
|
|
42
|
+
* this version: place × factor = millimetres of its own paper.
|
|
43
|
+
*
|
|
44
|
+
* Copies cut for different systems are scaled onto one axis so that
|
|
45
|
+
* they can be collated at all, which leaves a version of another
|
|
46
|
+
* system carrying places in the axis copy's millimetres. A
|
|
47
|
+
* performance needs the paper the roll actually ran on, and the
|
|
48
|
+
* factor is the inverse of the scale `alignCopy` recorded.
|
|
49
|
+
*
|
|
50
|
+
* It is read only from the copies of the version's own system, since
|
|
51
|
+
* under the shared axis a green version's notes are carried by red
|
|
52
|
+
* copies too and those say nothing about green paper. A copy whose
|
|
53
|
+
* scale is put down to its own paper having stretched is left out as
|
|
54
|
+
* well: that is a fact about the one exemplar, not about the speed
|
|
55
|
+
* the system's rolls were cut at. Where what remains disagrees,
|
|
56
|
+
* `constraintProblems` reports it rather than averaging it away.
|
|
57
|
+
*/
|
|
58
|
+
toOwnPaperOf(version: Readonly<Version>): number | undefined;
|
|
59
|
+
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
60
|
+
speedScalesIn(version: Readonly<Version>): number[];
|
|
61
|
+
/** The copies of the version's own system that carry any of its symbols. */
|
|
62
|
+
copiesOwning(version: Readonly<Version>): Readonly<RollCopy>[];
|
|
34
63
|
predecessorOf(versionId: string): Readonly<Version> | undefined;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Where along the roll the symbol lies, as its carriers put it, or
|
|
66
|
+
* nothing for a symbol no copy carries.
|
|
67
|
+
*
|
|
68
|
+
* Only the place is measured. Which track the symbol sits on is not
|
|
69
|
+
* a measurement but a question for a tracker bar, since a note of
|
|
70
|
+
* one pitch sits on exactly one position of a given bar: ask
|
|
71
|
+
* `bar.positionOf`. The carriers cannot answer it, because a copy
|
|
72
|
+
* cut for another system numbers its tracks differently, and one
|
|
73
|
+
* symbol may be carried by copies of both — averaging a red carrier
|
|
74
|
+
* on track 47 with a green one on 45 would give 46, a legal
|
|
75
|
+
* position a semitone away on either bar.
|
|
76
|
+
*/
|
|
77
|
+
placeOf(symbol: AnySymbol): Readonly<HorizontalSpan> | undefined;
|
|
39
78
|
/** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
|
|
40
79
|
onsetOf(symbol: AnySymbol): Millimeters | undefined;
|
|
41
80
|
/** The symbols by onset, those without a place first; symbols at one place keep their order. */
|
|
@@ -51,5 +90,14 @@ export declare class EditionView {
|
|
|
51
90
|
withGenerations(): Array<Version & {
|
|
52
91
|
generation: number;
|
|
53
92
|
}>;
|
|
54
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The symbol as a performance needs it: where it lies, and the
|
|
95
|
+
* position the performing bar reads it on.
|
|
96
|
+
*
|
|
97
|
+
* Nothing where that bar reads nothing of it, which is the case a
|
|
98
|
+
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
99
|
+
* green version still inherits cannot be performed on a green
|
|
100
|
+
* machine, and an edit has yet to say what took its place.
|
|
101
|
+
*/
|
|
102
|
+
simplifySymbol(symbol: Note | Expression, bar: TrackerBar): NegotiatedEvent | null;
|
|
55
103
|
}
|
package/lib/EditionView.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { withBorneFeatures } from "./Feature";
|
|
1
2
|
import { deletedBy, insertedBy } from "./Version";
|
|
3
|
+
import { systemIdOf } from "./TrackerBar";
|
|
4
|
+
import { isPaperStretch } from "./RollCopy";
|
|
2
5
|
import { idOf, idsOf } from "./Assumption";
|
|
3
6
|
import { mean } from "./Quantity";
|
|
4
7
|
export const getAt = (path, obj) => {
|
|
@@ -58,6 +61,13 @@ export class EditionView {
|
|
|
58
61
|
writable: true,
|
|
59
62
|
value: new Map()
|
|
60
63
|
});
|
|
64
|
+
/** Built when a copy is first asked for, the walk not recording it. */
|
|
65
|
+
Object.defineProperty(this, "copiesByFeature", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
value: void 0
|
|
70
|
+
});
|
|
61
71
|
this.edition = edition;
|
|
62
72
|
this.indexObjects();
|
|
63
73
|
}
|
|
@@ -141,35 +151,87 @@ export class EditionView {
|
|
|
141
151
|
carriersOf(symbol) {
|
|
142
152
|
return this.getAll(idsOf(symbol.carriers));
|
|
143
153
|
}
|
|
154
|
+
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
155
|
+
copyOf(featureId) {
|
|
156
|
+
if (!this.copiesByFeature) {
|
|
157
|
+
this.copiesByFeature = new Map(this.edition.copies.flatMap(copy => copy.features
|
|
158
|
+
.flatMap(withBorneFeatures)
|
|
159
|
+
.map(feature => [feature.id, copy])));
|
|
160
|
+
}
|
|
161
|
+
return this.copiesByFeature.get(featureId);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* How a place on the edition's shared axis relates to the paper of
|
|
165
|
+
* this version: place × factor = millimetres of its own paper.
|
|
166
|
+
*
|
|
167
|
+
* Copies cut for different systems are scaled onto one axis so that
|
|
168
|
+
* they can be collated at all, which leaves a version of another
|
|
169
|
+
* system carrying places in the axis copy's millimetres. A
|
|
170
|
+
* performance needs the paper the roll actually ran on, and the
|
|
171
|
+
* factor is the inverse of the scale `alignCopy` recorded.
|
|
172
|
+
*
|
|
173
|
+
* It is read only from the copies of the version's own system, since
|
|
174
|
+
* under the shared axis a green version's notes are carried by red
|
|
175
|
+
* copies too and those say nothing about green paper. A copy whose
|
|
176
|
+
* scale is put down to its own paper having stretched is left out as
|
|
177
|
+
* well: that is a fact about the one exemplar, not about the speed
|
|
178
|
+
* the system's rolls were cut at. Where what remains disagrees,
|
|
179
|
+
* `constraintProblems` reports it rather than averaging it away.
|
|
180
|
+
*/
|
|
181
|
+
toOwnPaperOf(version) {
|
|
182
|
+
const scales = this.speedScalesIn(version);
|
|
183
|
+
return scales.length === 1 ? 1 / scales[0] : undefined;
|
|
184
|
+
}
|
|
185
|
+
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
186
|
+
speedScalesIn(version) {
|
|
187
|
+
return [...new Set(this.copiesOwning(version)
|
|
188
|
+
.filter(copy => !copy.conditions.some(isPaperStretch))
|
|
189
|
+
.map(copy => copy.measurements.scale)
|
|
190
|
+
.filter((scale) => scale !== undefined && scale > 0))];
|
|
191
|
+
}
|
|
192
|
+
/** The copies of the version's own system that carry any of its symbols. */
|
|
193
|
+
copiesOwning(version) {
|
|
194
|
+
const system = systemIdOf(version.system);
|
|
195
|
+
const carrying = new Set(this.snapshot(version.id)
|
|
196
|
+
.flatMap(symbol => idsOf(symbol.carriers))
|
|
197
|
+
.flatMap(id => {
|
|
198
|
+
const copy = this.copyOf(id);
|
|
199
|
+
return copy ? [copy.id] : [];
|
|
200
|
+
}));
|
|
201
|
+
return this.edition.copies.filter(copy => carrying.has(copy.id) && systemIdOf(copy.production?.system) === system);
|
|
202
|
+
}
|
|
144
203
|
predecessorOf(versionId) {
|
|
145
204
|
const v = this.get(versionId);
|
|
146
205
|
if (!v?.basedOn)
|
|
147
206
|
return;
|
|
148
207
|
return this.get(idOf(v.basedOn));
|
|
149
208
|
}
|
|
150
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Where along the roll the symbol lies, as its carriers put it, or
|
|
211
|
+
* nothing for a symbol no copy carries.
|
|
212
|
+
*
|
|
213
|
+
* Only the place is measured. Which track the symbol sits on is not
|
|
214
|
+
* a measurement but a question for a tracker bar, since a note of
|
|
215
|
+
* one pitch sits on exactly one position of a given bar: ask
|
|
216
|
+
* `bar.positionOf`. The carriers cannot answer it, because a copy
|
|
217
|
+
* cut for another system numbers its tracks differently, and one
|
|
218
|
+
* symbol may be carried by copies of both — averaging a red carrier
|
|
219
|
+
* on track 47 with a green one on 45 would give 46, a legal
|
|
220
|
+
* position a semitone away on either bar.
|
|
221
|
+
*/
|
|
222
|
+
placeOf(symbol) {
|
|
151
223
|
const carriers = this.getAll(idsOf(symbol.carriers));
|
|
152
|
-
if (carriers.length === 0)
|
|
224
|
+
if (carriers.length === 0)
|
|
153
225
|
return;
|
|
154
|
-
}
|
|
155
|
-
const farEnds = carriers.flatMap(carrier => carrier.vertical.to === undefined ? [] : [carrier.vertical.to]);
|
|
156
226
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
to: mean(carriers.map(carrier => carrier.horizontal.to))
|
|
161
|
-
},
|
|
162
|
-
vertical: {
|
|
163
|
-
unit: 'track',
|
|
164
|
-
from: mean(carriers.map(carrier => carrier.vertical.from)),
|
|
165
|
-
...(farEnds.length > 0 && { to: mean(farEnds) })
|
|
166
|
-
}
|
|
227
|
+
unit: 'mm',
|
|
228
|
+
from: mean(carriers.map(carrier => carrier.horizontal.from)),
|
|
229
|
+
to: mean(carriers.map(carrier => carrier.horizontal.to))
|
|
167
230
|
};
|
|
168
231
|
}
|
|
169
232
|
/** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
|
|
170
233
|
onsetOf(symbol) {
|
|
171
|
-
|
|
172
|
-
return carriers.length > 0 ? mean(carriers.map(carrier => carrier.horizontal.from)) : undefined;
|
|
234
|
+
return this.placeOf(symbol)?.from;
|
|
173
235
|
}
|
|
174
236
|
/** The symbols by onset, those without a place first; symbols at one place keep their order. */
|
|
175
237
|
inOrderOfPlace(symbols) {
|
|
@@ -231,13 +293,24 @@ export class EditionView {
|
|
|
231
293
|
}));
|
|
232
294
|
return withGen;
|
|
233
295
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
296
|
+
/**
|
|
297
|
+
* The symbol as a performance needs it: where it lies, and the
|
|
298
|
+
* position the performing bar reads it on.
|
|
299
|
+
*
|
|
300
|
+
* Nothing where that bar reads nothing of it, which is the case a
|
|
301
|
+
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
302
|
+
* green version still inherits cannot be performed on a green
|
|
303
|
+
* machine, and an edit has yet to say what took its place.
|
|
304
|
+
*/
|
|
305
|
+
simplifySymbol(symbol, bar) {
|
|
306
|
+
const horizontal = this.placeOf(symbol);
|
|
307
|
+
const position = bar.positionOf(symbol);
|
|
308
|
+
if (!horizontal || position === undefined)
|
|
237
309
|
return null;
|
|
238
310
|
return {
|
|
239
311
|
...symbol,
|
|
240
|
-
|
|
312
|
+
horizontal,
|
|
313
|
+
vertical: { unit: 'track', from: position }
|
|
241
314
|
};
|
|
242
315
|
}
|
|
243
316
|
}
|
package/lib/Emulation.js
CHANGED
|
@@ -9,9 +9,10 @@ const punchDiameterOf = (view) => {
|
|
|
9
9
|
.filter((value) => value !== undefined && value > 0);
|
|
10
10
|
return measured.length > 0 ? mean(measured) : undefined;
|
|
11
11
|
};
|
|
12
|
-
const propertiesOf = (view) => ({
|
|
12
|
+
const propertiesOf = (view, version) => ({
|
|
13
13
|
punchDiameter: punchDiameterOf(view),
|
|
14
|
-
tempo: view.edition.tempoAdjustment
|
|
14
|
+
tempo: view.edition.tempoAdjustment,
|
|
15
|
+
toOwnPaper: view.toOwnPaperOf(version)
|
|
15
16
|
});
|
|
16
17
|
/** The onset a symbol has on each copy carrying it, by the copy's index, as the mean of its holes there. */
|
|
17
18
|
const onsetsByCopy = (view, symbolId) => {
|
|
@@ -154,7 +155,7 @@ export class Emulation {
|
|
|
154
155
|
this.negotiatedEvents =
|
|
155
156
|
view.snapshot(version.id)
|
|
156
157
|
.filter(isPerforation)
|
|
157
|
-
.map(symbol => view.simplifySymbol(symbol))
|
|
158
|
+
.map(symbol => view.simplifySymbol(symbol, this.system.trackerBar))
|
|
158
159
|
.filter(event => event !== null)
|
|
159
160
|
.filter(inScope);
|
|
160
161
|
if (this.negotiatedEvents.length === 0) {
|
|
@@ -163,7 +164,7 @@ export class Emulation {
|
|
|
163
164
|
return this.midiEvents;
|
|
164
165
|
}
|
|
165
166
|
this.applyConstraints(view);
|
|
166
|
-
const performance = this.system.perform(this.negotiatedEvents, this.options, propertiesOf(view));
|
|
167
|
+
const performance = this.system.perform(this.negotiatedEvents, this.options, propertiesOf(view, version));
|
|
167
168
|
this.curves = performance.curves;
|
|
168
169
|
const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
|
|
169
170
|
const origin = skipToFirstNote ? earliestOf(onsets) : seconds(0);
|
|
@@ -63,8 +63,18 @@ interface CurveSamples {
|
|
|
63
63
|
readonly place: Float64Array;
|
|
64
64
|
readonly seconds: Float64Array;
|
|
65
65
|
/**
|
|
66
|
-
* Position
|
|
67
|
-
*
|
|
66
|
+
* Position on the printed ordinate of the roll's own ruled band: 0 at that
|
|
67
|
+
* half's P.P. gridline, 1 at the shared F.F. line, with M.F. at 0.5. For a
|
|
68
|
+
* pedal, 0 with it up and 1 with it down.
|
|
69
|
+
*
|
|
70
|
+
* Both Welte scales rule that band the same way — five rails, "P.P. M.F.
|
|
71
|
+
* F.F. M.F. P.P.", three named levels a side with the centre F.F. shared,
|
|
72
|
+
* measuring 20.0 mm between rails on red 3309 and on green Welte 184 alike —
|
|
73
|
+
* so a curve from one system and a curve from the other are in one unit, on
|
|
74
|
+
* Welte's authority rather than on ours. That is what lets a red issue and a
|
|
75
|
+
* green issue of one recording be compared at all. It is a unit of *drawn
|
|
76
|
+
* deflection*: whether the deflection is linear in the bellows' own travel is
|
|
77
|
+
* open, and is what the emulator's `scaleWarp` exists to answer.
|
|
68
78
|
*/
|
|
69
79
|
readonly travel: Float64Array;
|
|
70
80
|
}
|
|
@@ -72,6 +82,14 @@ interface CurveSamples {
|
|
|
72
82
|
export type DynamicsCurve = CurveSamples & {
|
|
73
83
|
readonly kind: 'dynamics';
|
|
74
84
|
readonly velocity: Float64Array;
|
|
85
|
+
/**
|
|
86
|
+
* The instrument whose constants produced it, by name. A comparison plot
|
|
87
|
+
* must not be able to put two curves side by side without saying which
|
|
88
|
+
* instrument each came from: a T-98 fitted to a green roll's drawn line and
|
|
89
|
+
* a T-98 fitted to a red copy's curve answer different questions, and their
|
|
90
|
+
* difference is the point of the comparison.
|
|
91
|
+
*/
|
|
92
|
+
readonly instrument: string;
|
|
75
93
|
};
|
|
76
94
|
export type PedalCurve = CurveSamples & {
|
|
77
95
|
readonly kind: 'pedal';
|
|
@@ -83,6 +101,24 @@ export type RollProperties = {
|
|
|
83
101
|
punchDiameter?: Millimeters;
|
|
84
102
|
/** The tempo the edition adjusts the roll to, where it states one. */
|
|
85
103
|
tempo?: RollTempo;
|
|
104
|
+
/**
|
|
105
|
+
* Place on the edition's shared axis × this = millimetres of the version's
|
|
106
|
+
* own paper. About 0.775 for a green issue of a red recording, 1 for a
|
|
107
|
+
* version on the axis it was measured on, which is the default.
|
|
108
|
+
*
|
|
109
|
+
* Versions of several systems share one place axis, so a green version's
|
|
110
|
+
* places are in red millimetres and would play about 29 % long if the
|
|
111
|
+
* spool read them as green paper. The factor is the inverse of the scale
|
|
112
|
+
* `alignCopy` recorded when it put the copy on the shared axis, taken from
|
|
113
|
+
* the copies of the version's own system: red copies carry a green
|
|
114
|
+
* version's notes too and say nothing about green paper.
|
|
115
|
+
*
|
|
116
|
+
* It scales **places only**. Punch diameter, the chain gap and the tracker
|
|
117
|
+
* bore are measured on the version's own paper already, and scaling them
|
|
118
|
+
* as well would put the aperture model out by the factor with nothing
|
|
119
|
+
* failing loudly.
|
|
120
|
+
*/
|
|
121
|
+
toOwnPaper?: number;
|
|
86
122
|
};
|
|
87
123
|
export type Performance = {
|
|
88
124
|
readonly events: readonly AnyPerformedRollFeature[];
|
package/lib/RollCopy.d.ts
CHANGED
|
@@ -301,17 +301,40 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
|
|
|
301
301
|
*/
|
|
302
302
|
readFrom?: FeatureSource;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Whether the condition is the stretch or shrinkage of the paper. A
|
|
306
|
+
* scale put down to this belongs to the one exemplar and says nothing
|
|
307
|
+
* about the speed its system's rolls were cut at.
|
|
308
|
+
*/
|
|
309
|
+
export declare const isPaperStretch: (condition: RollConditionAssignment) => boolean;
|
|
310
|
+
/**
|
|
311
|
+
* The bar a copy is read by, which is the one it was cut for. A copy
|
|
312
|
+
* that names no system falls back to the T-100, as every copy was read
|
|
313
|
+
* before the systems were told apart; `reservationsAbout` says so.
|
|
314
|
+
*/
|
|
315
|
+
export declare const barOf: (copy: Pick<RollCopy, 'production'>) => TrackerBar;
|
|
304
316
|
/**
|
|
305
317
|
* Reads the features of a copy as the tracker bar would read them.
|
|
306
318
|
* Holes on a position the bar does not read carry no symbol and are
|
|
307
319
|
* dropped, which is what happens physically as well.
|
|
320
|
+
*
|
|
321
|
+
* The bar is named rather than defaulted: a copy is read by its own
|
|
322
|
+
* bar, and reading a green copy with the red one is a silent semitone.
|
|
323
|
+
*
|
|
324
|
+
* Reading stops where the bar says the roll's content ends. What a
|
|
325
|
+
* scanner punched past the rewind is not the roll speaking: Julian
|
|
326
|
+
* Dyer's scan of the green 225 carries a staircase of test punches
|
|
327
|
+
* across nearly every track after its rewind, which read on as symbols
|
|
328
|
+
* would be 318 notes nobody played. The features stay on the copy,
|
|
329
|
+
* since they are really on the paper and are his calibration of his own
|
|
330
|
+
* scan; it is the reading that stops.
|
|
308
331
|
*/
|
|
309
|
-
export declare function asSymbols(features: AnyFeature[], bar
|
|
332
|
+
export declare function asSymbols(features: AnyFeature[], bar: TrackerBar): AnySymbol[];
|
|
310
333
|
/**
|
|
311
334
|
* The tracks a copy carries holes on that the tracker bar does not read.
|
|
312
335
|
* A non-empty result usually means the scan is calibrated wrongly.
|
|
313
336
|
*/
|
|
314
|
-
export declare function unreadTracks(features: AnyFeature[], bar
|
|
337
|
+
export declare function unreadTracks(features: AnyFeature[], bar: TrackerBar): Map<Track, number>;
|
|
315
338
|
/**
|
|
316
339
|
* Falls back to the way the scan geometry was reconstructed before the
|
|
317
340
|
* calibration was recorded: the bass hard margin stood in for the phase
|
package/lib/RollCopy.js
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
import { welteT100 } from "./systems/welteT100/bar";
|
|
3
|
+
import { trackerBarOf } from "./systems";
|
|
3
4
|
import { assignReference } from "./Assumption";
|
|
4
5
|
import { px, track } from "./Quantity";
|
|
5
6
|
export const rollConditions = [
|
|
6
7
|
'general',
|
|
7
8
|
'paper-stretch'
|
|
8
9
|
];
|
|
10
|
+
/**
|
|
11
|
+
* Whether the condition is the stretch or shrinkage of the paper. A
|
|
12
|
+
* scale put down to this belongs to the one exemplar and says nothing
|
|
13
|
+
* about the speed its system's rolls were cut at.
|
|
14
|
+
*/
|
|
15
|
+
export const isPaperStretch = (condition) => condition.conditionType === 'paper-stretch';
|
|
16
|
+
/**
|
|
17
|
+
* The bar a copy is read by, which is the one it was cut for. A copy
|
|
18
|
+
* that names no system falls back to the T-100, as every copy was read
|
|
19
|
+
* before the systems were told apart; `reservationsAbout` says so.
|
|
20
|
+
*/
|
|
21
|
+
export const barOf = (copy) => trackerBarOf(copy.production?.system) ?? welteT100;
|
|
9
22
|
/**
|
|
10
23
|
* Reads the features of a copy as the tracker bar would read them.
|
|
11
24
|
* Holes on a position the bar does not read carry no symbol and are
|
|
12
25
|
* dropped, which is what happens physically as well.
|
|
26
|
+
*
|
|
27
|
+
* The bar is named rather than defaulted: a copy is read by its own
|
|
28
|
+
* bar, and reading a green copy with the red one is a silent semitone.
|
|
29
|
+
*
|
|
30
|
+
* Reading stops where the bar says the roll's content ends. What a
|
|
31
|
+
* scanner punched past the rewind is not the roll speaking: Julian
|
|
32
|
+
* Dyer's scan of the green 225 carries a staircase of test punches
|
|
33
|
+
* across nearly every track after its rewind, which read on as symbols
|
|
34
|
+
* would be 318 notes nobody played. The features stay on the copy,
|
|
35
|
+
* since they are really on the paper and are his calibration of his own
|
|
36
|
+
* scan; it is the reading that stops.
|
|
13
37
|
*/
|
|
14
|
-
export function asSymbols(features, bar
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
export function asSymbols(features, bar) {
|
|
39
|
+
const holes = features.filter(feature => feature.type === 'Hole');
|
|
40
|
+
const end = bar.endsAt(holes);
|
|
41
|
+
return holes
|
|
42
|
+
.filter(feature => end === undefined || feature.horizontal.from <= end.at)
|
|
17
43
|
.flatMap((feature) => {
|
|
18
44
|
const meaning = bar.meaningOf(feature.vertical.from);
|
|
19
45
|
if (!meaning)
|
|
@@ -29,7 +55,7 @@ export function asSymbols(features, bar = welteT100) {
|
|
|
29
55
|
* The tracks a copy carries holes on that the tracker bar does not read.
|
|
30
56
|
* A non-empty result usually means the scan is calibrated wrongly.
|
|
31
57
|
*/
|
|
32
|
-
export function unreadTracks(features, bar
|
|
58
|
+
export function unreadTracks(features, bar) {
|
|
33
59
|
const counts = new Map();
|
|
34
60
|
features
|
|
35
61
|
.filter(feature => feature.type === 'Hole')
|
package/lib/TrackerBar.d.ts
CHANGED
|
@@ -56,13 +56,39 @@ export interface TrackerBar {
|
|
|
56
56
|
* of their own, as the Licensee's do, states none.
|
|
57
57
|
*/
|
|
58
58
|
readonly paperSpeed?: SpeedMeasure;
|
|
59
|
+
/**
|
|
60
|
+
* Where the roll's own content ends, from its perforations alone, or
|
|
61
|
+
* `undefined` where nothing on the paper says. A copy that reads
|
|
62
|
+
* `undefined` runs to the end of whatever was scanned, which is a fact
|
|
63
|
+
* about the scan rather than about the roll and should be reported as one.
|
|
64
|
+
*
|
|
65
|
+
* Takes anything with a place and a position, so a reader can ask it of a
|
|
66
|
+
* copy's features before there are symbols, and a collation can ask it of
|
|
67
|
+
* the symbols afterwards.
|
|
68
|
+
*/
|
|
69
|
+
endsAt(features: readonly PlacedOnBar[]): RollEnd | undefined;
|
|
59
70
|
/** `undefined` for a position the bar does not read. */
|
|
60
71
|
meaningOf(position: Track): TrackMeaning | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* `meaningOf` inverted: the position this bar reads the meaning on,
|
|
74
|
+
* or `undefined` where it does not read it at all. A symbol's track
|
|
75
|
+
* is this rather than anything measured, since a note of one pitch
|
|
76
|
+
* sits on exactly one position of a given bar.
|
|
77
|
+
*/
|
|
78
|
+
positionOf(meaning: TrackMeaning): Track | undefined;
|
|
61
79
|
/** `undefined` for a position the bar does not read. */
|
|
62
80
|
roleOf(position: Track): TrackRole | undefined;
|
|
63
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* What a position says, as a key. Two bars read the same thing exactly
|
|
84
|
+
* where their keys agree, which is what lets a symbol cross from one
|
|
85
|
+
* system to another and what decides whether two symbols collate.
|
|
86
|
+
*/
|
|
87
|
+
export declare const keyOf: (meaning: TrackMeaning) => string;
|
|
64
88
|
/** The roll system a tracker bar belongs to, as the roll metadata states it. */
|
|
65
89
|
export declare const systemOf: (bar: TrackerBar) => Concept;
|
|
90
|
+
/** The identifier of a system the type vocabulary knows, from the IRI naming it. */
|
|
91
|
+
export declare const systemIdIn: (id: string | undefined) => string | undefined;
|
|
66
92
|
/** The identifier of a system the type vocabulary knows, from its concept. */
|
|
67
93
|
export declare const systemIdOf: (system: Concept | undefined) => string | undefined;
|
|
68
94
|
/**
|
|
@@ -70,6 +96,28 @@ export declare const systemIdOf: (system: Concept | undefined) => string | undef
|
|
|
70
96
|
* in the bar's own 1-based numbering; `describeTrackerBar` gives them
|
|
71
97
|
* their type.
|
|
72
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Where a roll's own content ends, as far as its perforations say.
|
|
101
|
+
*
|
|
102
|
+
* This is a question about the paper, not about the mechanism: it asks where
|
|
103
|
+
* the rewind is *punched*, not when the rewind pneumatic takes hold. The second
|
|
104
|
+
* is a matter of valve lift and belongs to whatever performs the roll. Keeping
|
|
105
|
+
* them apart is what lets collation and counting ask this without an emulator.
|
|
106
|
+
*/
|
|
107
|
+
export type RollEnd = {
|
|
108
|
+
readonly at: Millimeters;
|
|
109
|
+
readonly because: 'rewind';
|
|
110
|
+
};
|
|
111
|
+
/** Anything carrying a place on the roll and a position on the bar. */
|
|
112
|
+
export type PlacedOnBar = {
|
|
113
|
+
readonly horizontal: {
|
|
114
|
+
readonly from: Millimeters;
|
|
115
|
+
readonly to: Millimeters;
|
|
116
|
+
};
|
|
117
|
+
readonly vertical: {
|
|
118
|
+
readonly from: Track;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
73
121
|
export interface TrackerBarSpec {
|
|
74
122
|
id: string;
|
|
75
123
|
name: string;
|
|
@@ -92,12 +140,19 @@ export interface TrackerBarSpec {
|
|
|
92
140
|
rewindTrack?: number;
|
|
93
141
|
/** The speed the system runs its rolls at, where the literature states one. */
|
|
94
142
|
paperSpeed?: SpeedMeasure;
|
|
143
|
+
/**
|
|
144
|
+
* How long a perforation on a *shared* rewind position has to be before it
|
|
145
|
+
* is the rewind rather than the command the position usually carries. Only
|
|
146
|
+
* meaningful with `rewindTrack`: a system that gives the rewind a line of
|
|
147
|
+
* its own needs no threshold, since anything there is the rewind.
|
|
148
|
+
*/
|
|
149
|
+
rewindHold?: Millimeters;
|
|
95
150
|
}
|
|
96
151
|
export declare const describeTrackerBar: (spec: TrackerBarSpec) => TrackerBar;
|
|
97
152
|
/**
|
|
98
153
|
* Puts a position of one bar onto the position of another that reads
|
|
99
154
|
* the same thing, or nowhere when the other bar does not read it. This
|
|
100
|
-
* is how a copy
|
|
101
|
-
*
|
|
155
|
+
* is how a copy read in one system's numbering is put into another's,
|
|
156
|
+
* as the migration does for a Licensee copy stored on T-100 tracks.
|
|
102
157
|
*/
|
|
103
158
|
export declare const translationBetween: (from: TrackerBar, to: TrackerBar) => (position: Track) => Track | undefined;
|