linked-rolls 0.0.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/README.md +18 -0
- package/lib/Collation.d.ts +4 -0
- package/lib/Collation.js +1 -0
- package/lib/Condition.d.ts +10 -0
- package/lib/Condition.js +1 -0
- package/lib/ConditionState.d.ts +9 -0
- package/lib/ConditionState.js +6 -0
- package/lib/Edit.d.ts +21 -0
- package/lib/Edit.js +112 -0
- package/lib/Edition.d.ts +54 -0
- package/lib/Edition.js +1 -0
- package/lib/EditorialAssumption.d.ts +67 -0
- package/lib/EditorialAssumption.js +26 -0
- package/lib/Emulation.d.ts +76 -0
- package/lib/Emulation.js +474 -0
- package/lib/Feature.d.ts +37 -0
- package/lib/Feature.js +3 -0
- package/lib/Measurement.d.ts +9 -0
- package/lib/Measurement.js +1 -0
- package/lib/PlaceTimeConversion.d.ts +65 -0
- package/lib/PlaceTimeConversion.js +175 -0
- package/lib/RollCopy.d.ts +85 -0
- package/lib/RollCopy.js +273 -0
- package/lib/RollEvent.d.ts +76 -0
- package/lib/RollEvent.js +3 -0
- package/lib/Stage.d.ts +37 -0
- package/lib/Stage.js +165 -0
- package/lib/Symbol.d.ts +62 -0
- package/lib/Symbol.js +29 -0
- package/lib/TrackerBar.d.ts +8 -0
- package/lib/TrackerBar.js +53 -0
- package/lib/Transcription.d.ts +7 -0
- package/lib/Transcription.js +9 -0
- package/lib/Version.d.ts +42 -0
- package/lib/Version.js +217 -0
- package/lib/WithId.d.ts +3 -0
- package/lib/WithId.js +1 -0
- package/lib/alignFeatures.d.ts +11 -0
- package/lib/alignFeatures.js +54 -0
- package/lib/alignRolls.d.ts +7 -0
- package/lib/alignRolls.js +49 -0
- package/lib/alignSymbols.d.ts +7 -0
- package/lib/alignSymbols.js +49 -0
- package/lib/asJsonLd.d.ts +3 -0
- package/lib/asJsonLd.js +67 -0
- package/lib/asMIDISpans.d.ts +23 -0
- package/lib/asMIDISpans.js +111 -0
- package/lib/aton/AtonParser.d.ts +48 -0
- package/lib/aton/AtonParser.js +245 -0
- package/lib/aton/AtonParser.test.d.ts +1 -0
- package/lib/aton/AtonParser.test.js +16 -0
- package/lib/build-schema.cjs +113 -0
- package/lib/context.d.ts +1 -0
- package/lib/context.js +1 -0
- package/lib/importJsonLd.d.ts +4 -0
- package/lib/importJsonLd.js +121 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +17 -0
- package/lib/spec/context.json +192 -0
- package/package.json +27 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export class PlaceTimeConversion {
|
|
2
|
+
}
|
|
3
|
+
export class NoAccelerationConversion {
|
|
4
|
+
constructor() {
|
|
5
|
+
Object.defineProperty(this, "metersPerMinute", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: 3
|
|
10
|
+
}); // according to most sources
|
|
11
|
+
}
|
|
12
|
+
get summary() {
|
|
13
|
+
return `no aceleration (${this.metersPerMinute} m/min)`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Converts physical place (in cm) to time (in seconds).
|
|
17
|
+
* @param x1 - The distance in mm.
|
|
18
|
+
* @returns Time in seconds.
|
|
19
|
+
*/
|
|
20
|
+
placeToTime(cm) {
|
|
21
|
+
const meters = cm / 100; // convert to m
|
|
22
|
+
const t = meters / this.metersPerMinute;
|
|
23
|
+
return t * 60;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Converts time (in seconds) to physical place (in cm).
|
|
27
|
+
* @param t1 - The time in seconds.
|
|
28
|
+
* @returns Distance in cm.
|
|
29
|
+
*/
|
|
30
|
+
timeToPlace(t1) {
|
|
31
|
+
return (this.metersPerMinute * (t1 / 60)) * 100;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var SpeedUnit;
|
|
35
|
+
(function (SpeedUnit) {
|
|
36
|
+
SpeedUnit[SpeedUnit["MetersPerMinute"] = 0] = "MetersPerMinute";
|
|
37
|
+
SpeedUnit[SpeedUnit["FeetPerMinute"] = 1] = "FeetPerMinute";
|
|
38
|
+
})(SpeedUnit || (SpeedUnit = {}));
|
|
39
|
+
var AccelerationUnit;
|
|
40
|
+
(function (AccelerationUnit) {
|
|
41
|
+
AccelerationUnit[AccelerationUnit["MillimetersPerSecondSquared"] = 0] = "MillimetersPerSecondSquared";
|
|
42
|
+
AccelerationUnit[AccelerationUnit["FeetPerMinuteSquared"] = 1] = "FeetPerMinuteSquared";
|
|
43
|
+
})(AccelerationUnit || (AccelerationUnit = {}));
|
|
44
|
+
export class KinematicConversion {
|
|
45
|
+
get summary() {
|
|
46
|
+
return `kinematic (acceleration: ${this.acceleration} ft/s², speed: ${this.feetPerMinute} ft/min)`;
|
|
47
|
+
}
|
|
48
|
+
constructor(feetPerMinute = KinematicConversion.normalSpeed, acceleration = KinematicConversion.baertschAcceleration) {
|
|
49
|
+
Object.defineProperty(this, "feetPerMinute", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: void 0
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "acceleration", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: void 0
|
|
60
|
+
});
|
|
61
|
+
this.feetPerMinute = feetPerMinute;
|
|
62
|
+
this.acceleration = acceleration;
|
|
63
|
+
}
|
|
64
|
+
setSpeed(speed, unit) {
|
|
65
|
+
if (unit === SpeedUnit.FeetPerMinute) {
|
|
66
|
+
this.feetPerMinute = speed;
|
|
67
|
+
}
|
|
68
|
+
else if (unit === SpeedUnit.MetersPerMinute) {
|
|
69
|
+
this.feetPerMinute = speed * 3.2808399;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error('Unsupported unit');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
setAcceleration(acceleration, unit) {
|
|
76
|
+
if (unit === AccelerationUnit.FeetPerMinuteSquared) {
|
|
77
|
+
this.acceleration = acceleration;
|
|
78
|
+
}
|
|
79
|
+
else if (unit === AccelerationUnit.MillimetersPerSecondSquared) {
|
|
80
|
+
this.acceleration = acceleration * 0.0032808399 * 3600;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new Error('Unsupported unit');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Converts physical place (in cm) to time (in seconds).
|
|
88
|
+
* @param x1 - The distance in mm.
|
|
89
|
+
* @returns Time in seconds.
|
|
90
|
+
*/
|
|
91
|
+
placeToTime(cm) {
|
|
92
|
+
const x1 = cm / 30.48;
|
|
93
|
+
const v = this.feetPerMinute;
|
|
94
|
+
const a = this.acceleration;
|
|
95
|
+
const t = (Math.sqrt(2 * a * x1 + Math.pow(v, 2)) - v) / a;
|
|
96
|
+
return t * 60;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Converts time (in seconds) to physical place (in cm).
|
|
100
|
+
* @param t1 - The time in seconds.
|
|
101
|
+
* @returns Distance in cm.
|
|
102
|
+
*/
|
|
103
|
+
timeToPlace(t1) {
|
|
104
|
+
// Convert seconds -> minutes
|
|
105
|
+
const T = t1 / 60;
|
|
106
|
+
const xFeet = this.feetPerMinute * T + 0.5 * this.acceleration * Math.pow(T, 2);
|
|
107
|
+
// Convert feet -> millimeters
|
|
108
|
+
const xMM = xFeet * 304.8;
|
|
109
|
+
return xMM / 10;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
Object.defineProperty(KinematicConversion, "slowSpeed", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true,
|
|
115
|
+
writable: true,
|
|
116
|
+
value: 9.46
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(KinematicConversion, "normalSpeed", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
configurable: true,
|
|
121
|
+
writable: true,
|
|
122
|
+
value: 9.85
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(KinematicConversion, "stanfordAcceleration", {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
writable: true,
|
|
128
|
+
value: 0.3147
|
|
129
|
+
}); // cf. https://github.com/pianoroll/midi2exp/commit/6a29060
|
|
130
|
+
Object.defineProperty(KinematicConversion, "baertschAcceleration", {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
configurable: true,
|
|
133
|
+
writable: true,
|
|
134
|
+
value: 0.1772
|
|
135
|
+
}); // = 0.015 mm/s^2, cf. Baertsch p. 33
|
|
136
|
+
export class GottschewskiConversion {
|
|
137
|
+
constructor() {
|
|
138
|
+
Object.defineProperty(this, "paperThickness", {
|
|
139
|
+
enumerable: true,
|
|
140
|
+
configurable: true,
|
|
141
|
+
writable: true,
|
|
142
|
+
value: 0.0075
|
|
143
|
+
});
|
|
144
|
+
Object.defineProperty(this, "initialCircumference", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
writable: true,
|
|
148
|
+
value: 22.25
|
|
149
|
+
});
|
|
150
|
+
Object.defineProperty(this, "secondsPerTurn", {
|
|
151
|
+
enumerable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
writable: true,
|
|
154
|
+
value: 4.64
|
|
155
|
+
}); // is that correct? Philips says 120 RPM (p. 113)
|
|
156
|
+
}
|
|
157
|
+
get summary() {
|
|
158
|
+
return `Gottschewski (assuming paper thickness: ${this.paperThickness}, initial circumference: ${this.initialCircumference}, seconds per turn: ${this.secondsPerTurn})`;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* @param x1 in centimeters
|
|
162
|
+
*/
|
|
163
|
+
placeToTime(x1) {
|
|
164
|
+
const Q0 = Math.pow(this.initialCircumference, 2) / (4 * Math.PI);
|
|
165
|
+
const v0 = this.initialCircumference / this.secondsPerTurn;
|
|
166
|
+
const d = Q0 / this.paperThickness;
|
|
167
|
+
return Math.pow(d, 0.5) * (1 / v0) * 2 * (Math.pow(d + x1, 0.5) - Math.pow(d, 0.5));
|
|
168
|
+
}
|
|
169
|
+
timeToPlace(t1) {
|
|
170
|
+
const Q0 = Math.pow(this.initialCircumference, 2) / (4 * Math.PI);
|
|
171
|
+
const v0 = this.initialCircumference / this.secondsPerTurn;
|
|
172
|
+
const d = Q0 / this.paperThickness;
|
|
173
|
+
return Math.pow((t1 * v0) / (2 * Math.pow(d, 0.5)) + Math.pow(d, 0.5), 2) - d;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ConditionState } from "./ConditionState";
|
|
2
|
+
import { AnySymbol } from "./Symbol";
|
|
3
|
+
import { EditorialAssumption } from "./EditorialAssumption";
|
|
4
|
+
import { PlaceTimeConversion } from "./PlaceTimeConversion";
|
|
5
|
+
import { RollFeature } from "./Feature";
|
|
6
|
+
/**
|
|
7
|
+
* This condition state is used to describe to roll's
|
|
8
|
+
* paper shrinkage or stretching. It might be calculated
|
|
9
|
+
* on the basis of comparing the vertical or horizontal
|
|
10
|
+
* extent with other witnesses of the same roll.
|
|
11
|
+
*/
|
|
12
|
+
export interface PaperStretch extends ConditionState<'paper-stretch'> {
|
|
13
|
+
factor: number;
|
|
14
|
+
}
|
|
15
|
+
export interface GeneralRollCondition extends ConditionState<'general'> {
|
|
16
|
+
}
|
|
17
|
+
export type RollConditionAssignment = EditorialAssumption<'conditionAssignment', GeneralRollCondition | PaperStretch>;
|
|
18
|
+
export interface Shift {
|
|
19
|
+
horizontal: number;
|
|
20
|
+
vertical: number;
|
|
21
|
+
}
|
|
22
|
+
export type DateAssignment = EditorialAssumption<'dateAssignment', Date>;
|
|
23
|
+
export interface ProductionEvent {
|
|
24
|
+
company: string;
|
|
25
|
+
system: string;
|
|
26
|
+
paper: string;
|
|
27
|
+
date: DateAssignment;
|
|
28
|
+
}
|
|
29
|
+
export declare class RollCopy {
|
|
30
|
+
type: 'RollCopy';
|
|
31
|
+
id: string;
|
|
32
|
+
measurements: Partial<{
|
|
33
|
+
dimensions: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
unit: string;
|
|
37
|
+
};
|
|
38
|
+
punchDiameter: {
|
|
39
|
+
value: number;
|
|
40
|
+
unit: string;
|
|
41
|
+
};
|
|
42
|
+
holeSeparation: {
|
|
43
|
+
value: number;
|
|
44
|
+
unit: string;
|
|
45
|
+
};
|
|
46
|
+
margins: {
|
|
47
|
+
treble: number;
|
|
48
|
+
bass: number;
|
|
49
|
+
unit: string;
|
|
50
|
+
};
|
|
51
|
+
shift: Shift;
|
|
52
|
+
measuredBy: {
|
|
53
|
+
software: string;
|
|
54
|
+
version: string;
|
|
55
|
+
date: Date;
|
|
56
|
+
};
|
|
57
|
+
}>;
|
|
58
|
+
productionEvent?: ProductionEvent;
|
|
59
|
+
conditions: RollConditionAssignment[];
|
|
60
|
+
location: string;
|
|
61
|
+
/**
|
|
62
|
+
* Provides a reconstructed version of the roll,
|
|
63
|
+
* with shift, stretch and emendations already
|
|
64
|
+
* taken into account. This property will not be
|
|
65
|
+
* exported in the final JSON.
|
|
66
|
+
*/
|
|
67
|
+
features: RollFeature[];
|
|
68
|
+
scan?: string;
|
|
69
|
+
insertFeature(feature: RollFeature): void;
|
|
70
|
+
setShift(shift: Shift): void;
|
|
71
|
+
setStretch(stretch: EditorialAssumption<'conditionAssignment', PaperStretch>): void;
|
|
72
|
+
shallowClone(): RollCopy;
|
|
73
|
+
}
|
|
74
|
+
export declare function asSymbols(features: RollFeature[]): AnySymbol[];
|
|
75
|
+
export declare function readFromStanfordAton(atonString: string, adjustByRewind?: boolean, shift?: number): RollCopy;
|
|
76
|
+
/**
|
|
77
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
78
|
+
* 83 (=8.3 feet per minute).
|
|
79
|
+
*
|
|
80
|
+
* @param midiBuffer
|
|
81
|
+
* @param conversion
|
|
82
|
+
*/
|
|
83
|
+
export declare function readFromSpencerMIDI(midiBuffer: ArrayBuffer, conversion?: PlaceTimeConversion): RollCopy;
|
|
84
|
+
export declare function shiftVertically(features: RollFeature[], amount: number): void;
|
|
85
|
+
export declare const findCopiesCarrying: (sources: RollCopy[], symbol: AnySymbol) => Set<string>;
|
package/lib/RollCopy.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { AtonParser } from "./aton/AtonParser";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { assign, flat } from "./EditorialAssumption";
|
|
4
|
+
import { read } from "midifile-ts";
|
|
5
|
+
import { asSpans } from "./asMIDISpans";
|
|
6
|
+
import { KinematicConversion } from "./PlaceTimeConversion";
|
|
7
|
+
import { WelteT100 } from "./TrackerBar";
|
|
8
|
+
const applyShift = (shift, to) => {
|
|
9
|
+
for (const event of to) {
|
|
10
|
+
event.horizontal.from += shift.horizontal;
|
|
11
|
+
if (event.horizontal.to) {
|
|
12
|
+
event.horizontal.to += shift.horizontal;
|
|
13
|
+
}
|
|
14
|
+
event.vertical.from += shift.vertical;
|
|
15
|
+
if (event.vertical.to) {
|
|
16
|
+
event.vertical.to += shift.vertical;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const applyStretch = (stretch, to) => {
|
|
21
|
+
for (const event of to) {
|
|
22
|
+
event.horizontal.from *= stretch;
|
|
23
|
+
if (event.horizontal.to) {
|
|
24
|
+
event.horizontal.to *= stretch;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export class RollCopy {
|
|
29
|
+
constructor() {
|
|
30
|
+
Object.defineProperty(this, "type", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: 'RollCopy'
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(this, "id", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: v4()
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(this, "measurements", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
writable: true,
|
|
46
|
+
value: {}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(this, "productionEvent", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: void 0
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(this, "conditions", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
configurable: true,
|
|
57
|
+
writable: true,
|
|
58
|
+
value: []
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(this, "location", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: ''
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Provides a reconstructed version of the roll,
|
|
68
|
+
* with shift, stretch and emendations already
|
|
69
|
+
* taken into account. This property will not be
|
|
70
|
+
* exported in the final JSON.
|
|
71
|
+
*/
|
|
72
|
+
Object.defineProperty(this, "features", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: []
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(this, "scan", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
writable: true,
|
|
82
|
+
value: void 0
|
|
83
|
+
}); // P138i has representation => IIIF Image Link
|
|
84
|
+
}
|
|
85
|
+
insertFeature(feature) {
|
|
86
|
+
this.measurements.shift && applyShift(this.measurements.shift, [feature]);
|
|
87
|
+
const stretch = flat(this.conditions)
|
|
88
|
+
.find(state => state.type === 'paper-stretch');
|
|
89
|
+
if (stretch) {
|
|
90
|
+
applyStretch(stretch.factor, [feature]);
|
|
91
|
+
}
|
|
92
|
+
this.features.push(feature);
|
|
93
|
+
}
|
|
94
|
+
setShift(shift) {
|
|
95
|
+
this.measurements.shift = shift;
|
|
96
|
+
applyShift(shift, this.features);
|
|
97
|
+
}
|
|
98
|
+
setStretch(stretch) {
|
|
99
|
+
this.conditions.push(stretch);
|
|
100
|
+
applyStretch(flat(stretch).factor, this.features);
|
|
101
|
+
}
|
|
102
|
+
shallowClone() {
|
|
103
|
+
const copy = new RollCopy();
|
|
104
|
+
copy.id = this.id;
|
|
105
|
+
copy.measurements = { ...this.measurements };
|
|
106
|
+
copy.productionEvent = this.productionEvent;
|
|
107
|
+
copy.location = this.location;
|
|
108
|
+
copy.conditions = [...this.conditions];
|
|
109
|
+
copy.scan = this.scan;
|
|
110
|
+
copy.features = [...this.features];
|
|
111
|
+
return copy;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function asSymbols(features) {
|
|
115
|
+
return features.map((feature) => {
|
|
116
|
+
return {
|
|
117
|
+
id: `symbol_${v4()}`,
|
|
118
|
+
...new WelteT100().meaningOf(feature.vertical.from),
|
|
119
|
+
carriers: [assign('carrierAssignment', feature)]
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export function readFromStanfordAton(atonString, adjustByRewind = true, shift = 0) {
|
|
124
|
+
function pixelsToMillimeters(pixels, dpi) {
|
|
125
|
+
return pixels / dpi * 25.4;
|
|
126
|
+
}
|
|
127
|
+
const parser = new AtonParser();
|
|
128
|
+
const json = parser.parse(atonString);
|
|
129
|
+
const holes = json.ROLLINFO.HOLES.HOLE;
|
|
130
|
+
const druid = json.ROLLINFO.DRUID;
|
|
131
|
+
const holeSeparation = parseFloat(json.ROLLINFO.HOLE_SEPARATION.replace('px'));
|
|
132
|
+
const hardMarginBass = parseFloat(json.ROLLINFO.HARD_MARGIN_BASS.replace('px'));
|
|
133
|
+
const hardMarginTreble = parseFloat(json.ROLLINFO.HARD_MARGIN_TREBLE.replace('px'));
|
|
134
|
+
const dpi = parseFloat(json.ROLLINFO.LENGTH_DPI.replace('ppi'));
|
|
135
|
+
const rollWidth = parseFloat(json.ROLLINFO.ROLL_WIDTH.replace('px')) / dpi * 25.4;
|
|
136
|
+
const rollHeight = parseFloat(json.ROLLINFO.IMAGE_LENGTH.replace('px')) / dpi * 25.4;
|
|
137
|
+
let averagePunchDiameter = -1;
|
|
138
|
+
const lastHole = +holes[holes.length - 1].TRACKER_HOLE;
|
|
139
|
+
const rewindShift = adjustByRewind ? 91 - lastHole : shift;
|
|
140
|
+
const copy = new RollCopy();
|
|
141
|
+
copy.measurements = {
|
|
142
|
+
dimensions: {
|
|
143
|
+
width: rollWidth,
|
|
144
|
+
height: rollHeight,
|
|
145
|
+
unit: 'mm'
|
|
146
|
+
},
|
|
147
|
+
punchDiameter: {
|
|
148
|
+
value: averagePunchDiameter,
|
|
149
|
+
unit: 'mm'
|
|
150
|
+
},
|
|
151
|
+
holeSeparation: {
|
|
152
|
+
value: holeSeparation,
|
|
153
|
+
unit: 'px'
|
|
154
|
+
},
|
|
155
|
+
margins: {
|
|
156
|
+
treble: hardMarginTreble,
|
|
157
|
+
bass: hardMarginBass,
|
|
158
|
+
unit: 'px'
|
|
159
|
+
},
|
|
160
|
+
// todo
|
|
161
|
+
shift: {
|
|
162
|
+
horizontal: 0,
|
|
163
|
+
vertical: 0
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
let circularPunches = 0;
|
|
167
|
+
const features = [];
|
|
168
|
+
for (let i = 0; i < holes.length; i++) {
|
|
169
|
+
const hole = holes[i];
|
|
170
|
+
const circularity = +hole.CIRCULARITY.replace('px', '');
|
|
171
|
+
if (circularity > 0.95) {
|
|
172
|
+
const perimeterInMM = pixelsToMillimeters(+hole.PERIMETER.replace('px', ''), dpi);
|
|
173
|
+
averagePunchDiameter += perimeterInMM;
|
|
174
|
+
circularPunches += 1;
|
|
175
|
+
}
|
|
176
|
+
if (!hole.NOTE_ATTACK || !hole.OFF_TIME)
|
|
177
|
+
continue;
|
|
178
|
+
const trackerHole = +hole.TRACKER_HOLE + rewindShift;
|
|
179
|
+
const noteAttack = +hole.NOTE_ATTACK.replace('px', '');
|
|
180
|
+
const offset = +hole.OFF_TIME.replace('px', '');
|
|
181
|
+
const height = offset - noteAttack;
|
|
182
|
+
const column = +hole.ORIGIN_COL.replace('px', '');
|
|
183
|
+
const columnWidth = +hole.WIDTH_COL.replace('px', '');
|
|
184
|
+
const annotates = `https://stacks.stanford.edu/image/iiif/${druid}/${druid}_0001/${column},${noteAttack},${columnWidth},${height}/128,/270/default.jpg`;
|
|
185
|
+
const feature = {
|
|
186
|
+
id: v4(),
|
|
187
|
+
annotates,
|
|
188
|
+
vertical: {
|
|
189
|
+
from: trackerHole,
|
|
190
|
+
unit: 'track'
|
|
191
|
+
},
|
|
192
|
+
horizontal: {
|
|
193
|
+
unit: 'mm',
|
|
194
|
+
from: pixelsToMillimeters(noteAttack, dpi),
|
|
195
|
+
to: pixelsToMillimeters(offset, dpi)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
features.push(feature);
|
|
199
|
+
}
|
|
200
|
+
averagePunchDiameter /= circularPunches;
|
|
201
|
+
averagePunchDiameter /= Math.PI;
|
|
202
|
+
copy.scan = `https://stacks.stanford.edu/image/iiif/${druid}%2F${druid}_0001/`;
|
|
203
|
+
copy.features = features;
|
|
204
|
+
return copy;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
208
|
+
* 83 (=8.3 feet per minute).
|
|
209
|
+
*
|
|
210
|
+
* @param midiBuffer
|
|
211
|
+
* @param conversion
|
|
212
|
+
*/
|
|
213
|
+
export function readFromSpencerMIDI(midiBuffer, conversion = new KinematicConversion(8.3)) {
|
|
214
|
+
const midi = read(midiBuffer);
|
|
215
|
+
const features = [];
|
|
216
|
+
const copy = new RollCopy();
|
|
217
|
+
const spans = asSpans(midi);
|
|
218
|
+
for (const span of spans) {
|
|
219
|
+
const left = conversion.timeToPlace(span.onsetMs / 1000) * 10;
|
|
220
|
+
const right = conversion.timeToPlace(span.offsetMs / 1000) * 10;
|
|
221
|
+
const horizontalDimension = {
|
|
222
|
+
from: left,
|
|
223
|
+
to: right,
|
|
224
|
+
unit: 'mm'
|
|
225
|
+
};
|
|
226
|
+
if (span.type !== 'note')
|
|
227
|
+
continue;
|
|
228
|
+
const midiShift = 13;
|
|
229
|
+
let trackerHole = span.pitch - midiShift;
|
|
230
|
+
// for whatever reasons, the expression tracks
|
|
231
|
+
// of the Spencer Chase's MIDI rolls are off
|
|
232
|
+
// by two on the bass-side.
|
|
233
|
+
if (trackerHole < 10) {
|
|
234
|
+
trackerHole -= 2;
|
|
235
|
+
}
|
|
236
|
+
const feature = {
|
|
237
|
+
vertical: {
|
|
238
|
+
from: trackerHole,
|
|
239
|
+
unit: 'track'
|
|
240
|
+
},
|
|
241
|
+
horizontal: horizontalDimension,
|
|
242
|
+
id: v4()
|
|
243
|
+
};
|
|
244
|
+
features.push(feature);
|
|
245
|
+
}
|
|
246
|
+
copy.features = features;
|
|
247
|
+
return copy;
|
|
248
|
+
}
|
|
249
|
+
export function shiftVertically(features, amount) {
|
|
250
|
+
for (const event of features) {
|
|
251
|
+
event.vertical.from += amount;
|
|
252
|
+
try {
|
|
253
|
+
const newEvent = new WelteT100().meaningOf(event.vertical.from);
|
|
254
|
+
for (const key in newEvent) {
|
|
255
|
+
event[key] = newEvent[key];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
console.error(e);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export const findCopiesCarrying = (sources, symbol) => {
|
|
264
|
+
const result = new Set();
|
|
265
|
+
for (const feature of flat(symbol.carriers)) {
|
|
266
|
+
for (const copy of sources) {
|
|
267
|
+
if (copy.features.includes(feature)) {
|
|
268
|
+
result.add(copy.id);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { RollMeasurement } from "./Measurement";
|
|
2
|
+
import { WithId } from "./WithId";
|
|
3
|
+
export interface HorizontalSpan {
|
|
4
|
+
unit: 'mm';
|
|
5
|
+
from: number;
|
|
6
|
+
to: number;
|
|
7
|
+
}
|
|
8
|
+
export interface VerticalSpan {
|
|
9
|
+
unit: 'track';
|
|
10
|
+
from: number;
|
|
11
|
+
to?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface RollFeature<T> extends WithId {
|
|
14
|
+
type: T;
|
|
15
|
+
/**
|
|
16
|
+
* IIIF region in string form.
|
|
17
|
+
*/
|
|
18
|
+
annotates?: string;
|
|
19
|
+
horizontal: HorizontalSpan;
|
|
20
|
+
vertical: VerticalSpan;
|
|
21
|
+
/**
|
|
22
|
+
* Describes whether the event takes place
|
|
23
|
+
* on the verso or recto side of the roll.
|
|
24
|
+
* Since the same perforation is present on both
|
|
25
|
+
* sides, this property is left optional for now.
|
|
26
|
+
*/
|
|
27
|
+
side?: 'verso' | 'recto';
|
|
28
|
+
measurement: RollMeasurement;
|
|
29
|
+
}
|
|
30
|
+
export interface Note extends RollFeature<'note'> {
|
|
31
|
+
pitch: number;
|
|
32
|
+
}
|
|
33
|
+
export type ExpressionScope = 'bass' | 'treble';
|
|
34
|
+
export type ExpressionType = 'SustainPedalOn' | 'SustainPedalOff' | 'SoftPedalOn' | 'SoftPedalOff' | 'MezzoforteOff' | 'MezzoforteOn' | 'SlowCrescendoOn' | 'SlowCrescendoOff' | 'ForzandoOn' | 'ForzandoOff' | 'MotorOff' | 'MotorOn' | 'Rewind' | 'ElectricCutOff';
|
|
35
|
+
export interface Expression extends RollFeature<'expression'> {
|
|
36
|
+
scope: ExpressionScope;
|
|
37
|
+
expressionType: ExpressionType;
|
|
38
|
+
}
|
|
39
|
+
export declare const isRollEvent: (e: any) => e is AnyRollEvent;
|
|
40
|
+
export interface Perforation extends RollFeature<'perforation'> {
|
|
41
|
+
accelerating?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* This denotes perforations that are covered by an editor.
|
|
45
|
+
* The covered perforation is not considered to be part
|
|
46
|
+
* of the original note or expression hole anymore.
|
|
47
|
+
*/
|
|
48
|
+
export interface Cover extends RollFeature<'cover'> {
|
|
49
|
+
/**
|
|
50
|
+
* This property can be used to indicate e.g. the
|
|
51
|
+
* color or material of the cover.
|
|
52
|
+
*/
|
|
53
|
+
note?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* For handwritten insertions like e. g. the
|
|
57
|
+
* perforation date in the end of a roll.
|
|
58
|
+
*/
|
|
59
|
+
export interface HandwrittenText extends RollFeature<'handwrittenText'> {
|
|
60
|
+
text: string;
|
|
61
|
+
rotation?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* This type can be used to indicate stamps like e. g. the
|
|
65
|
+
* "controlliert" stamp in the beginning of rolls or the
|
|
66
|
+
* date at the end of (later) Welte rolls.
|
|
67
|
+
*/
|
|
68
|
+
export interface Stamp extends RollFeature<'stamp'> {
|
|
69
|
+
text: string;
|
|
70
|
+
rotation?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface RollLabel extends RollFeature<'rollLabel'> {
|
|
73
|
+
text: string;
|
|
74
|
+
signed: boolean;
|
|
75
|
+
}
|
|
76
|
+
export type AnyRollEvent = Note | Expression | HandwrittenText | Stamp | Cover | RollLabel;
|
package/lib/RollEvent.js
ADDED
package/lib/Stage.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ActorAssignment, Edit } from "./Edit";
|
|
2
|
+
import { EditorialAssumption, Motivation } from "./EditorialAssumption";
|
|
3
|
+
import { AnySymbol } from "./Symbol";
|
|
4
|
+
export interface Derivation extends EditorialAssumption<'derivation', Stage> {
|
|
5
|
+
}
|
|
6
|
+
declare const versionType: readonly ["edition", "authorised-revision", "unauthorised-revision", "gloss"];
|
|
7
|
+
export type VersionType = typeof versionType[number];
|
|
8
|
+
/**
|
|
9
|
+
* Stage + Stage Creation
|
|
10
|
+
*/
|
|
11
|
+
export interface Stage {
|
|
12
|
+
id: string;
|
|
13
|
+
siglum: string;
|
|
14
|
+
actor?: ActorAssignment;
|
|
15
|
+
basedOn?: Derivation;
|
|
16
|
+
edits: Edit[];
|
|
17
|
+
motivations: Motivation<string>[];
|
|
18
|
+
type: VersionType;
|
|
19
|
+
}
|
|
20
|
+
export declare const traverseStages: (stage: Stage, callback: (stage: Stage) => void) => void;
|
|
21
|
+
export declare const getSnapshot: (stage: Stage) => AnySymbol[];
|
|
22
|
+
/**
|
|
23
|
+
* Walks through the stages. If it finds a symbol that is still
|
|
24
|
+
* part of the tradition (i.e. it is included in the current snapshot)
|
|
25
|
+
* and is equivalent with the given symbol, it will add the feature
|
|
26
|
+
* carrying the symbol to the collated symbol. Otherwise, the
|
|
27
|
+
* given symbol will be added to the current stage's insertions.
|
|
28
|
+
*
|
|
29
|
+
* All symbols of the tradition that are not included in the given
|
|
30
|
+
* symbols are considered to be deleted.
|
|
31
|
+
*
|
|
32
|
+
* @param creation
|
|
33
|
+
* @param symbols
|
|
34
|
+
* @returns
|
|
35
|
+
*/
|
|
36
|
+
export declare function fillEdits(currentStage: Stage, symbols: AnySymbol[]): void;
|
|
37
|
+
export {};
|