linked-rolls 0.18.0 → 0.19.1
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/lib/TrackerBar.d.ts +7 -0
- package/lib/TrackerBar.js +2 -2
- package/lib/alignment.d.ts +14 -8
- package/lib/alignment.js +16 -10
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/readers/stanfordAton.js +28 -3
- package/lib/systems/index.js +2 -1
- package/lib/systems/welteT98/bar.d.ts +31 -0
- package/lib/systems/welteT98/bar.js +57 -0
- package/package.json +1 -1
package/lib/TrackerBar.d.ts
CHANGED
|
@@ -83,6 +83,13 @@ export interface TrackerBarSpec {
|
|
|
83
83
|
};
|
|
84
84
|
/** Every position outside the note block, keyed by track. */
|
|
85
85
|
expressions: ReadonlyMap<number, string>;
|
|
86
|
+
/**
|
|
87
|
+
* The position the rewind runs on, where the system gives it no
|
|
88
|
+
* valve of its own to be named after. The T-98 drives its rewind
|
|
89
|
+
* with a long perforation on the bass sforzando-piano position.
|
|
90
|
+
* Left out, it is the position typed `Rewind`.
|
|
91
|
+
*/
|
|
92
|
+
rewindTrack?: number;
|
|
86
93
|
/** The speed the system runs its rolls at, where the literature states one. */
|
|
87
94
|
paperSpeed?: SpeedMeasure;
|
|
88
95
|
}
|
package/lib/TrackerBar.js
CHANGED
|
@@ -28,8 +28,8 @@ export const describeTrackerBar = (spec) => {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
return { type: 'expression', expressionType, scope: scopeOf(role) };
|
|
30
30
|
};
|
|
31
|
-
const rewind =
|
|
32
|
-
.find(([, type]) => type === 'Rewind')?.[0];
|
|
31
|
+
const rewind = spec.rewindTrack
|
|
32
|
+
?? [...spec.expressions].find(([, type]) => type === 'Rewind')?.[0];
|
|
33
33
|
if (rewind === undefined) {
|
|
34
34
|
throw new Error(`${spec.name} declares no rewind track`);
|
|
35
35
|
}
|
package/lib/alignment.d.ts
CHANGED
|
@@ -24,12 +24,18 @@ export interface AlignmentResult {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Finds the shift and scale that carry the places of `rollA` onto those
|
|
27
|
-
* of `rollB`, reading
|
|
28
|
-
* once on each roll anchor a first line; then every note is paired
|
|
29
|
-
* its nearest counterpart of the same pitch and the line refitted,
|
|
30
|
-
* window closing each time. A copy cut for another paper speed, a
|
|
31
|
-
* with a calibration pattern, and holes the other copy lacks are
|
|
32
|
-
* within reach of that. Nothing is found where the rolls share no
|
|
33
|
-
* pitches, as between two different pieces.
|
|
27
|
+
* of `rollB`, reading each through its own bar. Runs of pitches that
|
|
28
|
+
* occur once on each roll anchor a first line; then every note is paired
|
|
29
|
+
* with its nearest counterpart of the same pitch and the line refitted,
|
|
30
|
+
* the window closing each time. A copy cut for another paper speed, a
|
|
31
|
+
* scan with a calibration pattern, and holes the other copy lacks are
|
|
32
|
+
* all within reach of that. Nothing is found where the rolls share no
|
|
33
|
+
* run of pitches, as between two different pieces.
|
|
34
|
+
*
|
|
35
|
+
* Two bars are what lets a copy cut for one system be aligned against a
|
|
36
|
+
* copy cut for another, a T-98 against a T-100: the bars put the note
|
|
37
|
+
* positions on different tracks, but they agree on the pitch each track
|
|
38
|
+
* sounds, and it is the pitches that are matched. Where a copy's holes
|
|
39
|
+
* have already been put onto the edition's bar, that bar reads it.
|
|
34
40
|
*/
|
|
35
|
-
export declare function alignFeatures(rollA: readonly AnyFeature[], rollB: readonly AnyFeature[],
|
|
41
|
+
export declare function alignFeatures(rollA: readonly AnyFeature[], rollB: readonly AnyFeature[], barA?: TrackerBar, barB?: TrackerBar): AlignmentResult | undefined;
|
package/lib/alignment.js
CHANGED
|
@@ -159,17 +159,23 @@ const resultOf = ({ line, matches }) => {
|
|
|
159
159
|
};
|
|
160
160
|
/**
|
|
161
161
|
* Finds the shift and scale that carry the places of `rollA` onto those
|
|
162
|
-
* of `rollB`, reading
|
|
163
|
-
* once on each roll anchor a first line; then every note is paired
|
|
164
|
-
* its nearest counterpart of the same pitch and the line refitted,
|
|
165
|
-
* window closing each time. A copy cut for another paper speed, a
|
|
166
|
-
* with a calibration pattern, and holes the other copy lacks are
|
|
167
|
-
* within reach of that. Nothing is found where the rolls share no
|
|
168
|
-
* pitches, as between two different pieces.
|
|
162
|
+
* of `rollB`, reading each through its own bar. Runs of pitches that
|
|
163
|
+
* occur once on each roll anchor a first line; then every note is paired
|
|
164
|
+
* with its nearest counterpart of the same pitch and the line refitted,
|
|
165
|
+
* the window closing each time. A copy cut for another paper speed, a
|
|
166
|
+
* scan with a calibration pattern, and holes the other copy lacks are
|
|
167
|
+
* all within reach of that. Nothing is found where the rolls share no
|
|
168
|
+
* run of pitches, as between two different pieces.
|
|
169
|
+
*
|
|
170
|
+
* Two bars are what lets a copy cut for one system be aligned against a
|
|
171
|
+
* copy cut for another, a T-98 against a T-100: the bars put the note
|
|
172
|
+
* positions on different tracks, but they agree on the pitch each track
|
|
173
|
+
* sounds, and it is the pitches that are matched. Where a copy's holes
|
|
174
|
+
* have already been put onto the edition's bar, that bar reads it.
|
|
169
175
|
*/
|
|
170
|
-
export function alignFeatures(rollA, rollB,
|
|
171
|
-
const a = noteOnsets(rollA,
|
|
172
|
-
const b = noteOnsets(rollB,
|
|
176
|
+
export function alignFeatures(rollA, rollB, barA = welteT100, barB = barA) {
|
|
177
|
+
const a = noteOnsets(rollA, barA);
|
|
178
|
+
const b = noteOnsets(rollB, barB);
|
|
173
179
|
const coarse = robustLine(anchors(a, b));
|
|
174
180
|
if (!coarse)
|
|
175
181
|
return undefined;
|
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './TrackCalibration';
|
|
|
12
12
|
export * from './TrackerBar';
|
|
13
13
|
export * from './systems/welteT100/bar';
|
|
14
14
|
export * from './systems/welteLicensee/bar';
|
|
15
|
+
export * from './systems/welteT98/bar';
|
|
15
16
|
export * from './systems';
|
|
16
17
|
export * from './ReproducingSystem';
|
|
17
18
|
export * from './FeatureSource';
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './TrackCalibration';
|
|
|
12
12
|
export * from './TrackerBar';
|
|
13
13
|
export * from './systems/welteT100/bar';
|
|
14
14
|
export * from './systems/welteLicensee/bar';
|
|
15
|
+
export * from './systems/welteT98/bar';
|
|
15
16
|
export * from './systems';
|
|
16
17
|
export * from './ReproducingSystem';
|
|
17
18
|
export * from './FeatureSource';
|
|
@@ -41,6 +41,33 @@ const rewindTrackIn = (holes) => {
|
|
|
41
41
|
const trailing = holes.slice(lastMusical + 1).map(hole => +hole.TRACKER_HOLE);
|
|
42
42
|
return mostFrequent(trailing);
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The columns themselves, for a roll that punches its outermost
|
|
46
|
+
* positions. A hole can only sit on a position the bar reads, so
|
|
47
|
+
* columns spanning exactly as many positions as the bar has must run
|
|
48
|
+
* from its first to its last, and the lowest column is position one.
|
|
49
|
+
* Fewer columns leave the phase open and more mean something outside
|
|
50
|
+
* the bar was measured, and neither settles anything.
|
|
51
|
+
*/
|
|
52
|
+
const spannedShiftIn = (holes, bar) => {
|
|
53
|
+
const columns = holes.map(hole => +hole.TRACKER_HOLE).filter(Number.isFinite);
|
|
54
|
+
if (columns.length === 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
const lowest = Math.min(...columns);
|
|
57
|
+
return Math.max(...columns) - lowest + 1 === bar.trackCount ? track(1 - lowest) : undefined;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* How far the scanner's own hole numbering has to move to reach the
|
|
61
|
+
* bar. The rewind perforation is the usual landmark; where the roll
|
|
62
|
+
* carries punches past it, as Dyer's T-98 scans do, the span of the
|
|
63
|
+
* columns settles it instead.
|
|
64
|
+
*/
|
|
65
|
+
const calibrationShiftIn = (holes, bar) => {
|
|
66
|
+
const rewind = rewindTrackIn(holes);
|
|
67
|
+
if (rewind !== undefined)
|
|
68
|
+
return track(bar.rewindTrack - rewind);
|
|
69
|
+
return spannedShiftIn(holes, bar) ?? track(0);
|
|
70
|
+
};
|
|
44
71
|
/**
|
|
45
72
|
* The phase of the tracker grid within the image. The analysis file
|
|
46
73
|
* usually states it; where it does not, the holes themselves give it away,
|
|
@@ -92,9 +119,7 @@ export function readFromStanfordAton(atonString, { trackShift, bar = welteT100,
|
|
|
92
119
|
const separation = readPx(json.ROLLINFO.HOLE_SEPARATION);
|
|
93
120
|
const dpi = parseFloat(json.ROLLINFO.LENGTH_DPI);
|
|
94
121
|
const measuredBy = measuredByOf(json.ROLLINFO);
|
|
95
|
-
const
|
|
96
|
-
const shift = trackShift
|
|
97
|
-
?? (rewindTrack === undefined ? track(0) : track(system.rewindTrack - rewindTrack));
|
|
122
|
+
const shift = trackShift ?? calibrationShiftIn(holes, system);
|
|
98
123
|
const calibration = {
|
|
99
124
|
unit: 'px',
|
|
100
125
|
offset: gridOffsetOf(holes, separation, json.ROLLINFO.HOLE_OFFSET),
|
package/lib/systems/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { systemIdOf } from "../TrackerBar";
|
|
2
2
|
import { welteT100 } from "./welteT100/bar";
|
|
3
3
|
import { welteLicensee } from "./welteLicensee/bar";
|
|
4
|
+
import { welteT98 } from "./welteT98/bar";
|
|
4
5
|
/** The tracker bars the library knows, the T-100 first as the usual one. */
|
|
5
|
-
export const trackerBars = [welteT100, welteLicensee];
|
|
6
|
+
export const trackerBars = [welteT100, welteLicensee, welteT98];
|
|
6
7
|
/** The bar of a system the type vocabulary knows, from its concept. */
|
|
7
8
|
export const trackerBarOf = (system) => {
|
|
8
9
|
const id = systemIdOf(system);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TrackerBar } from "../../TrackerBar";
|
|
2
|
+
/**
|
|
3
|
+
* The commands of the Welte-Mignon T-98, as its tracker bar reads them.
|
|
4
|
+
*
|
|
5
|
+
* They are not the T-100's. Where the T-100 latches a function on with
|
|
6
|
+
* one perforation and cancels it with a second, the T-98 holds it for
|
|
7
|
+
* as long as one continuous perforation lasts (Hagmann, pp. 89 f. and
|
|
8
|
+
* 100–103; Phillips, p. 121), so none of these is an On or an Off. The
|
|
9
|
+
* two sforzando valves name the end of the range they pull towards.
|
|
10
|
+
*/
|
|
11
|
+
export declare const welteT98ExpressionTypes: readonly ['SforzandoPiano', 'SforzandoForte', 'Mezzoforte', 'Crescendo', 'SustainPedal', 'SoftPedal'];
|
|
12
|
+
export type WelteT98ExpressionType = typeof welteT98ExpressionTypes[number];
|
|
13
|
+
/**
|
|
14
|
+
* Welte-Mignon T-98 ("green Welte"), cf. Hagmann, Anhang 11 (p. 179)
|
|
15
|
+
* and Phillips, p. 121.
|
|
16
|
+
*
|
|
17
|
+
* 98 positions at nine to the inch on paper 286 mm wide, five expression
|
|
18
|
+
* valves on each side and 88 note positions between them, A0 to c⁵ (MIDI
|
|
19
|
+
* 21 to 108). A roll re-cut from a Mignon master uses only the middle 80
|
|
20
|
+
* of those, C1 to g⁴, which is the T-100's compass.
|
|
21
|
+
*
|
|
22
|
+
* The rewind is not a valve of its own: a long perforation on the bass
|
|
23
|
+
* sforzando-piano position drives it, which is why that position is
|
|
24
|
+
* named for the valve and the rewind stated separately.
|
|
25
|
+
*
|
|
26
|
+
* Measured against Julian Dyer's scan of the T-98 copy of roll 225
|
|
27
|
+
* (Grünfeld, Träumerei): 98 columns 2.818 mm apart on paper 285.6 mm
|
|
28
|
+
* wide, notes on the middle 80 columns, the sustain valve punched 53
|
|
29
|
+
* times where the T-100 copy latches its sustain on 53 times.
|
|
30
|
+
*/
|
|
31
|
+
export declare const welteT98: TrackerBar;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describeTrackerBar } from "../../TrackerBar";
|
|
2
|
+
import { mm } from "../../Quantity";
|
|
3
|
+
/**
|
|
4
|
+
* The commands of the Welte-Mignon T-98, as its tracker bar reads them.
|
|
5
|
+
*
|
|
6
|
+
* They are not the T-100's. Where the T-100 latches a function on with
|
|
7
|
+
* one perforation and cancels it with a second, the T-98 holds it for
|
|
8
|
+
* as long as one continuous perforation lasts (Hagmann, pp. 89 f. and
|
|
9
|
+
* 100–103; Phillips, p. 121), so none of these is an On or an Off. The
|
|
10
|
+
* two sforzando valves name the end of the range they pull towards.
|
|
11
|
+
*/
|
|
12
|
+
export const welteT98ExpressionTypes = [
|
|
13
|
+
'SforzandoPiano',
|
|
14
|
+
'SforzandoForte',
|
|
15
|
+
'Mezzoforte',
|
|
16
|
+
'Crescendo',
|
|
17
|
+
'SustainPedal',
|
|
18
|
+
'SoftPedal'
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Welte-Mignon T-98 ("green Welte"), cf. Hagmann, Anhang 11 (p. 179)
|
|
22
|
+
* and Phillips, p. 121.
|
|
23
|
+
*
|
|
24
|
+
* 98 positions at nine to the inch on paper 286 mm wide, five expression
|
|
25
|
+
* valves on each side and 88 note positions between them, A0 to c⁵ (MIDI
|
|
26
|
+
* 21 to 108). A roll re-cut from a Mignon master uses only the middle 80
|
|
27
|
+
* of those, C1 to g⁴, which is the T-100's compass.
|
|
28
|
+
*
|
|
29
|
+
* The rewind is not a valve of its own: a long perforation on the bass
|
|
30
|
+
* sforzando-piano position drives it, which is why that position is
|
|
31
|
+
* named for the valve and the rewind stated separately.
|
|
32
|
+
*
|
|
33
|
+
* Measured against Julian Dyer's scan of the T-98 copy of roll 225
|
|
34
|
+
* (Grünfeld, Träumerei): 98 columns 2.818 mm apart on paper 285.6 mm
|
|
35
|
+
* wide, notes on the middle 80 columns, the sustain valve punched 53
|
|
36
|
+
* times where the T-100 copy latches its sustain on 53 times.
|
|
37
|
+
*/
|
|
38
|
+
export const welteT98 = describeTrackerBar({
|
|
39
|
+
id: 'welte-green',
|
|
40
|
+
name: 'Welte-Mignon T98',
|
|
41
|
+
width: mm(286),
|
|
42
|
+
trackCount: 98,
|
|
43
|
+
notes: { from: 6, to: 93, lowestPitch: 21 },
|
|
44
|
+
rewindTrack: 1,
|
|
45
|
+
expressions: new Map([
|
|
46
|
+
[1, 'SforzandoPiano'],
|
|
47
|
+
[2, 'Mezzoforte'],
|
|
48
|
+
[3, 'SustainPedal'],
|
|
49
|
+
[4, 'Crescendo'],
|
|
50
|
+
[5, 'SforzandoForte'],
|
|
51
|
+
[94, 'SforzandoForte'],
|
|
52
|
+
[95, 'Crescendo'],
|
|
53
|
+
[96, 'SoftPedal'],
|
|
54
|
+
[97, 'Mezzoforte'],
|
|
55
|
+
[98, 'SforzandoPiano']
|
|
56
|
+
])
|
|
57
|
+
});
|
package/package.json
CHANGED