linked-rolls 0.5.0 → 0.6.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 +7 -4
- package/lib/Agent.d.ts +59 -0
- package/lib/Agent.js +1 -0
- package/lib/Assumption.d.ts +19 -1
- package/lib/Collation.d.ts +19 -0
- package/lib/Collation.js +25 -1
- package/lib/Edit.d.ts +1 -8
- package/lib/Edition.d.ts +4 -77
- package/lib/Edition.js +1 -1
- package/lib/EditionView.d.ts +0 -7
- package/lib/EditionView.js +0 -50
- package/lib/Feature.d.ts +3 -1
- package/lib/ReproducingSystem.d.ts +23 -1
- package/lib/RollCopy.d.ts +2 -68
- package/lib/RollCopy.js +2 -303
- package/lib/TrackerBar.d.ts +16 -14
- package/lib/TrackerBar.js +1 -56
- package/lib/alignment.d.ts +21 -0
- package/lib/alignment.js +117 -0
- package/lib/constraints.d.ts +27 -0
- package/lib/constraints.js +61 -0
- package/lib/editionOps.js +7 -10
- package/lib/index.d.ts +18 -13
- package/lib/index.js +18 -13
- package/lib/migrate.js +2 -1
- package/lib/readers/spencerMidi.d.ts +26 -0
- package/lib/readers/spencerMidi.js +58 -0
- package/lib/readers/stanfordAton.d.ts +18 -0
- package/lib/readers/stanfordAton.js +158 -0
- package/lib/schema.json +2 -2
- package/lib/systems/welteT100/bar.d.ts +14 -0
- package/lib/systems/welteT100/bar.js +56 -0
- package/lib/systems/{welteT100.d.ts → welteT100/system.d.ts} +17 -2
- package/lib/systems/{welteT100.js → welteT100/system.js} +19 -3
- package/lib/utils.d.ts +0 -8
- package/lib/validate.d.ts +0 -27
- package/lib/validate.js +0 -61
- package/package.json +4 -4
- package/lib/alignFeatures.d.ts +0 -12
- package/lib/alignFeatures.js +0 -55
- /package/lib/{aton → readers}/AtonParser.d.ts +0 -0
- /package/lib/{aton → readers}/AtonParser.js +0 -0
- /package/lib/{asMIDISpans.d.ts → readers/midiSpans.d.ts} +0 -0
- /package/lib/{asMIDISpans.js → readers/midiSpans.js} +0 -0
package/lib/editionOps.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
2
|
import { getAt } from "./EditionView";
|
|
3
3
|
import { isPerforation, placementRelations } from "./Symbol";
|
|
4
|
-
import {
|
|
4
|
+
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
5
|
+
import { asSymbols } from "./RollCopy";
|
|
6
|
+
import { applyShift, applyStretch, revertShift, revertStretch } from "./alignment";
|
|
5
7
|
import { assignReference, idOf } from "./Assumption";
|
|
6
8
|
const noChange = () => undefined;
|
|
7
9
|
const onCopy = (copyId, op) => draft => {
|
|
@@ -16,7 +18,6 @@ const onVersion = (versionId, op) => draft => {
|
|
|
16
18
|
};
|
|
17
19
|
/** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
|
|
18
20
|
const without = (items, matches) => items.some(matches) ? items.filter(item => !matches(item)) : items;
|
|
19
|
-
const defaultTolerance = { toleranceStart: 5, toleranceEnd: 5 };
|
|
20
21
|
const insertion = (symbol) => ({ type: 'edit', id: v4(), insert: [symbol] });
|
|
21
22
|
const deletion = (symbolId) => ({ type: 'edit', id: v4(), delete: [symbolId] });
|
|
22
23
|
const isEmpty = (edit) => !edit.insert?.length && !edit.delete?.length;
|
|
@@ -113,10 +114,6 @@ export const removeCopy = (copyId) => onCopy(copyId, (copy, draft) => {
|
|
|
113
114
|
forgetFeatures(draft, featureIdsOf(copy));
|
|
114
115
|
draft.copies = draft.copies.filter(c => c.id !== copyId);
|
|
115
116
|
});
|
|
116
|
-
/** Each of the version's own symbols with every inherited symbol it collates with. */
|
|
117
|
-
const collationsOf = (view, own, inherited, tolerance) => own.flatMap(symbol => inherited
|
|
118
|
-
.filter(candidate => view.isCollatable(symbol, candidate, tolerance))
|
|
119
|
-
.map(counterpart => ({ symbol, counterpart })));
|
|
120
117
|
/** The carriers of each collated symbol pass to its counterpart. */
|
|
121
118
|
const handOverCarriers = (view, draft, collations) => collations.forEach(({ symbol, counterpart }) => {
|
|
122
119
|
const path = view.getPath(counterpart.id);
|
|
@@ -129,10 +126,10 @@ const handOverCarriers = (view, draft, collations) => collations.forEach(({ symb
|
|
|
129
126
|
* rest become the child's insertions, and what the parent hands down
|
|
130
127
|
* and the child lacks becomes its deletions.
|
|
131
128
|
*/
|
|
132
|
-
export const connectVersions = (view, childId, parentId, tolerance =
|
|
129
|
+
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
133
130
|
const inherited = view.snapshot(parentId);
|
|
134
131
|
const own = view.snapshot(childId);
|
|
135
|
-
const collations = collationsOf(
|
|
132
|
+
const collations = collationsOf(own, inherited, symbol => view.dimensionOf(symbol), tolerance);
|
|
136
133
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
137
134
|
const matched = new Set(collations.map(({ counterpart }) => counterpart.id));
|
|
138
135
|
const edits = [
|
|
@@ -149,13 +146,13 @@ export const connectVersions = (view, childId, parentId, tolerance = defaultTole
|
|
|
149
146
|
* Folds the version's own symbols into those it inherits and collates
|
|
150
147
|
* with: the carriers pass over, and the insertions go.
|
|
151
148
|
*/
|
|
152
|
-
export const collateSymbols = (view, versionId, symbolIds, tolerance =
|
|
149
|
+
export const collateSymbols = (view, versionId, symbolIds, tolerance = defaultCollationTolerance) => {
|
|
153
150
|
const version = view.get(versionId);
|
|
154
151
|
if (!version?.basedOn)
|
|
155
152
|
return noChange;
|
|
156
153
|
const chosen = new Set(symbolIds);
|
|
157
154
|
const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
|
|
158
|
-
const collations = collationsOf(
|
|
155
|
+
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance);
|
|
159
156
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
160
157
|
return onVersion(versionId, (version, draft) => {
|
|
161
158
|
handOverCarriers(view, draft, collations);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
export * from './utils';
|
|
2
|
+
export * from './Agent';
|
|
1
3
|
export * from './Assumption';
|
|
2
|
-
export * from './asJsonLd';
|
|
3
|
-
export * from './Collation';
|
|
4
4
|
export * from './ConditionState';
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './Edit';
|
|
7
|
-
export * from './Edition';
|
|
8
|
-
export * from './Emulation';
|
|
9
|
-
export * from './ReproducingSystem';
|
|
5
|
+
export * from './Symbol';
|
|
10
6
|
export * from './Feature';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './RollCopy';
|
|
7
|
+
export * from './Edit';
|
|
13
8
|
export * from './Version';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './TrackerBar';
|
|
9
|
+
export * from './Collation';
|
|
16
10
|
export * from './TrackCalibration';
|
|
17
|
-
export * from './
|
|
11
|
+
export * from './TrackerBar';
|
|
12
|
+
export * from './systems/welteT100/bar';
|
|
13
|
+
export * from './ReproducingSystem';
|
|
14
|
+
export * from './RollCopy';
|
|
15
|
+
export * from './alignment';
|
|
16
|
+
export * from './Edition';
|
|
18
17
|
export * from './EditionView';
|
|
19
18
|
export * from './editionOps';
|
|
19
|
+
export * from './Emulation';
|
|
20
20
|
export * from './validate';
|
|
21
|
-
export * from './
|
|
21
|
+
export * from './constraints';
|
|
22
|
+
export * from './context';
|
|
23
|
+
export * from './asJsonLd';
|
|
24
|
+
export * from './importJsonLd';
|
|
25
|
+
export { readFromStanfordAton, type StanfordAtonOptions } from './readers/stanfordAton';
|
|
26
|
+
export { readFromSpencerMIDI, atConstantSpeed, spencerTrackOf, SPENCER_FEET_PER_MINUTE } from './readers/spencerMidi';
|
package/lib/index.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
export * from './utils';
|
|
2
|
+
export * from './Agent';
|
|
1
3
|
export * from './Assumption';
|
|
2
|
-
export * from './asJsonLd';
|
|
3
|
-
export * from './Collation';
|
|
4
4
|
export * from './ConditionState';
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './Edit';
|
|
7
|
-
export * from './Edition';
|
|
8
|
-
export * from './Emulation';
|
|
9
|
-
export * from './ReproducingSystem';
|
|
5
|
+
export * from './Symbol';
|
|
10
6
|
export * from './Feature';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './RollCopy';
|
|
7
|
+
export * from './Edit';
|
|
13
8
|
export * from './Version';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './TrackerBar';
|
|
9
|
+
export * from './Collation';
|
|
16
10
|
export * from './TrackCalibration';
|
|
17
|
-
export * from './
|
|
11
|
+
export * from './TrackerBar';
|
|
12
|
+
export * from './systems/welteT100/bar';
|
|
13
|
+
export * from './ReproducingSystem';
|
|
14
|
+
export * from './RollCopy';
|
|
15
|
+
export * from './alignment';
|
|
16
|
+
export * from './Edition';
|
|
18
17
|
export * from './EditionView';
|
|
19
18
|
export * from './editionOps';
|
|
19
|
+
export * from './Emulation';
|
|
20
20
|
export * from './validate';
|
|
21
|
-
export * from './
|
|
21
|
+
export * from './constraints';
|
|
22
|
+
export * from './context';
|
|
23
|
+
export * from './asJsonLd';
|
|
24
|
+
export * from './importJsonLd';
|
|
25
|
+
export { readFromStanfordAton } from './readers/stanfordAton';
|
|
26
|
+
export { readFromSpencerMIDI, atConstantSpeed, spencerTrackOf, SPENCER_FEET_PER_MINUTE } from './readers/spencerMidi';
|
package/lib/migrate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { conditions } from "./Feature";
|
|
2
2
|
import { rollConditions } from "./RollCopy";
|
|
3
|
-
import { systemOf
|
|
3
|
+
import { systemOf } from "./TrackerBar";
|
|
4
|
+
import { welteT100 } from "./systems/welteT100/bar";
|
|
4
5
|
import { versionTypes } from "./Version";
|
|
5
6
|
const versionTypeValues = new Set(versionTypes);
|
|
6
7
|
const conditionTypeValues = new Set([...rollConditions, ...Object.values(conditions).flat()]);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RollCopy } from "../RollCopy";
|
|
2
|
+
/**
|
|
3
|
+
* How a MIDI key number in one of Spencer Chase's roll files names a
|
|
4
|
+
* tracker bar track.
|
|
5
|
+
*
|
|
6
|
+
* The note block follows the obvious rule, `pitch - 13`, which puts
|
|
7
|
+
* track 11 on MIDI 24 as the T100 compass requires. The bass expression
|
|
8
|
+
* block does not: it reads two tracks high, and subtracting two is what
|
|
9
|
+
* has made these files come out right so far.
|
|
10
|
+
*
|
|
11
|
+
* The boundary between the two rules is unresolved. Taken literally the
|
|
12
|
+
* rules leave tracks 8 and 9 unreachable and jump from track 7 to track 10,
|
|
13
|
+
* which no lateral offset can produce, so at least one of them is
|
|
14
|
+
* approximate. Settling it needs a Spencer file whose expression holes
|
|
15
|
+
* can be checked against the roll, hence the option to override.
|
|
16
|
+
*/
|
|
17
|
+
export declare const spencerTrackOf: (pitch: number) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
20
|
+
* 83 (=8.3 feet per minute). A scanner feeds the paper at one
|
|
21
|
+
* speed, so time in his files is proportional to place.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SPENCER_FEET_PER_MINUTE = 8.3;
|
|
24
|
+
/** Place on the roll in mm after `seconds` at a constant `feetPerMinute`. */
|
|
25
|
+
export declare const atConstantSpeed: (feetPerMinute: number) => (seconds: number) => number;
|
|
26
|
+
export declare function readFromSpencerMIDI(midiBuffer: ArrayBuffer, placeAt?: (seconds: number) => number, trackOf?: (pitch: number) => number): RollCopy;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { read } from "midifile-ts";
|
|
3
|
+
import { asSpans } from "./midiSpans";
|
|
4
|
+
/**
|
|
5
|
+
* How a MIDI key number in one of Spencer Chase's roll files names a
|
|
6
|
+
* tracker bar track.
|
|
7
|
+
*
|
|
8
|
+
* The note block follows the obvious rule, `pitch - 13`, which puts
|
|
9
|
+
* track 11 on MIDI 24 as the T100 compass requires. The bass expression
|
|
10
|
+
* block does not: it reads two tracks high, and subtracting two is what
|
|
11
|
+
* has made these files come out right so far.
|
|
12
|
+
*
|
|
13
|
+
* The boundary between the two rules is unresolved. Taken literally the
|
|
14
|
+
* rules leave tracks 8 and 9 unreachable and jump from track 7 to track 10,
|
|
15
|
+
* which no lateral offset can produce, so at least one of them is
|
|
16
|
+
* approximate. Settling it needs a Spencer file whose expression holes
|
|
17
|
+
* can be checked against the roll, hence the option to override.
|
|
18
|
+
*/
|
|
19
|
+
export const spencerTrackOf = (pitch) => {
|
|
20
|
+
const track = pitch - 13;
|
|
21
|
+
return track < 10 ? track - 2 : track;
|
|
22
|
+
};
|
|
23
|
+
const MM_PER_FOOT = 304.8;
|
|
24
|
+
/**
|
|
25
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
26
|
+
* 83 (=8.3 feet per minute). A scanner feeds the paper at one
|
|
27
|
+
* speed, so time in his files is proportional to place.
|
|
28
|
+
*/
|
|
29
|
+
export const SPENCER_FEET_PER_MINUTE = 8.3;
|
|
30
|
+
/** Place on the roll in mm after `seconds` at a constant `feetPerMinute`. */
|
|
31
|
+
export const atConstantSpeed = (feetPerMinute) => (seconds) => feetPerMinute * MM_PER_FOOT / 60 * seconds;
|
|
32
|
+
export function readFromSpencerMIDI(midiBuffer, placeAt = atConstantSpeed(SPENCER_FEET_PER_MINUTE), trackOf = spencerTrackOf) {
|
|
33
|
+
const features = asSpans(read(midiBuffer))
|
|
34
|
+
.filter(span => span.type === 'note')
|
|
35
|
+
.map((span) => ({
|
|
36
|
+
type: 'Hole',
|
|
37
|
+
id: v4(),
|
|
38
|
+
vertical: {
|
|
39
|
+
from: trackOf(span.pitch),
|
|
40
|
+
unit: 'track'
|
|
41
|
+
},
|
|
42
|
+
horizontal: {
|
|
43
|
+
from: placeAt(span.onsetMs / 1000),
|
|
44
|
+
to: placeAt(span.offsetMs / 1000),
|
|
45
|
+
unit: 'mm'
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
return {
|
|
49
|
+
type: 'RollCopy',
|
|
50
|
+
id: v4(),
|
|
51
|
+
ops: [],
|
|
52
|
+
conditions: [],
|
|
53
|
+
keeper: { name: '', sameAs: [] },
|
|
54
|
+
measurements: {},
|
|
55
|
+
modifications: [],
|
|
56
|
+
features
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RollCopy } from "../RollCopy";
|
|
2
|
+
import { TrackerBar } from "../TrackerBar";
|
|
3
|
+
export interface StanfordAtonOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Added to the scanning software's hole numbering to reach the
|
|
6
|
+
* tracker bar. Left out, it is inferred by putting the rewind
|
|
7
|
+
* perforation on the bar's rewind track.
|
|
8
|
+
*/
|
|
9
|
+
trackShift?: number;
|
|
10
|
+
bar?: TrackerBar;
|
|
11
|
+
/**
|
|
12
|
+
* Where the scan the analysis was made from can be seen. Stanford's
|
|
13
|
+
* files name their scan by DRUID, so this is only needed for an
|
|
14
|
+
* analysis of a scan hosted elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
scan?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function readFromStanfordAton(atonString: string, { trackShift, bar, scan }?: StanfordAtonOptions): RollCopy;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { AtonParser } from "./AtonParser";
|
|
3
|
+
import { welteT100 } from "../systems/welteT100/bar";
|
|
4
|
+
/** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
|
|
5
|
+
const px = (value) => parseFloat(value);
|
|
6
|
+
/** The parser writes one record as an object and several as an array. */
|
|
7
|
+
const listOf = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
8
|
+
/**
|
|
9
|
+
* Holes the parser set aside as suspicious after it had already chained
|
|
10
|
+
* them into a note: the head of such a chain is usually a punch that
|
|
11
|
+
* came out a little short, and leaving it out would lose the whole
|
|
12
|
+
* chain. They carry no track number of their own, so the column says
|
|
13
|
+
* which track they sit on.
|
|
14
|
+
*/
|
|
15
|
+
const chainedBadHoles = (holes, calibration) => holes
|
|
16
|
+
.filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
|
|
17
|
+
.map(hole => ({
|
|
18
|
+
...hole,
|
|
19
|
+
TRACKER_HOLE: `${Math.round((px(hole.CENTROID_COL) - calibration.offset) / calibration.separation)}`
|
|
20
|
+
}));
|
|
21
|
+
const millimeters = (pixels, dpi) => pixels / dpi * 25.4;
|
|
22
|
+
const median = (values) => {
|
|
23
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
24
|
+
const middle = Math.floor(sorted.length / 2);
|
|
25
|
+
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
26
|
+
};
|
|
27
|
+
const mostFrequent = (values) => {
|
|
28
|
+
const counts = values.reduce((acc, value) => acc.set(value, (acc.get(value) || 0) + 1), new Map());
|
|
29
|
+
return [...counts].sort(([, a], [, b]) => b - a)[0]?.[0];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Only the first hole of a chain carries an attack and an off time;
|
|
33
|
+
* the rest continue it. The rewind perforation carries neither, and it
|
|
34
|
+
* is the last thing punched on the roll, so whatever the holes past the
|
|
35
|
+
* final musical attack sit on is the rewind track.
|
|
36
|
+
*/
|
|
37
|
+
const rewindTrackIn = (holes) => {
|
|
38
|
+
const lastMusical = holes.findLastIndex(hole => hole.NOTE_ATTACK);
|
|
39
|
+
const trailing = holes.slice(lastMusical + 1).map(hole => +hole.TRACKER_HOLE);
|
|
40
|
+
return mostFrequent(trailing);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The phase of the tracker grid within the image. The analysis file
|
|
44
|
+
* usually states it; where it does not, the holes themselves give it away,
|
|
45
|
+
* since each sits close to the centre of its column.
|
|
46
|
+
*/
|
|
47
|
+
const gridOffsetOf = (holes, separation, stated) => {
|
|
48
|
+
if (stated !== undefined)
|
|
49
|
+
return px(stated);
|
|
50
|
+
return median(holes.map(hole => px(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation));
|
|
51
|
+
};
|
|
52
|
+
const punchDiameterOf = (holes, dpi) => {
|
|
53
|
+
const circular = holes
|
|
54
|
+
.filter(hole => px(hole.CIRCULARITY) > 0.95)
|
|
55
|
+
.map(hole => millimeters(px(hole.PERIMETER), dpi) / Math.PI);
|
|
56
|
+
if (!circular.length)
|
|
57
|
+
return undefined;
|
|
58
|
+
return circular.reduce((sum, diameter) => sum + diameter, 0) / circular.length;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The image service Stanford keeps for a scan, and a crop of a
|
|
62
|
+
* feature from it.
|
|
63
|
+
*/
|
|
64
|
+
const stanfordScan = (druid) => ({
|
|
65
|
+
scan: `https://stacks.stanford.edu/image/iiif/${druid}%2F${druid}_0001/`,
|
|
66
|
+
depictionOf: (column, row, width, height) => `https://stacks.stanford.edu/image/iiif/${druid}/${druid}_0001/${column},${row},${width},${height}/128,/270/default.jpg`
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* The software behind an analysis and when it was run, as the
|
|
70
|
+
* analysis file states them.
|
|
71
|
+
*/
|
|
72
|
+
const measuredByOf = (rollinfo) => {
|
|
73
|
+
const date = new Date(rollinfo.ANALYSIS_DATE);
|
|
74
|
+
if (!rollinfo.HOLE_SOFTWARE || isNaN(date.getTime()))
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
software: rollinfo.HOLE_SOFTWARE,
|
|
78
|
+
version: rollinfo.SOFTWARE_DATE ?? '',
|
|
79
|
+
date
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export function readFromStanfordAton(atonString, { trackShift, bar = welteT100, scan } = {}) {
|
|
83
|
+
const parser = new AtonParser();
|
|
84
|
+
const json = parser.parse(atonString);
|
|
85
|
+
const holes = json.ROLLINFO.HOLES.HOLE;
|
|
86
|
+
const druid = json.ROLLINFO.DRUID;
|
|
87
|
+
const stanford = druid ? stanfordScan(druid) : undefined;
|
|
88
|
+
const separation = px(json.ROLLINFO.HOLE_SEPARATION);
|
|
89
|
+
const dpi = parseFloat(json.ROLLINFO.LENGTH_DPI);
|
|
90
|
+
const measuredBy = measuredByOf(json.ROLLINFO);
|
|
91
|
+
const rewindTrack = rewindTrackIn(holes);
|
|
92
|
+
const shift = trackShift
|
|
93
|
+
?? (rewindTrack === undefined ? 0 : bar.rewindTrack - rewindTrack);
|
|
94
|
+
const calibration = {
|
|
95
|
+
unit: 'px',
|
|
96
|
+
offset: gridOffsetOf(holes, separation, json.ROLLINFO.HOLE_OFFSET),
|
|
97
|
+
separation,
|
|
98
|
+
shift
|
|
99
|
+
};
|
|
100
|
+
const punchDiameter = punchDiameterOf(holes, dpi);
|
|
101
|
+
const chains = [...holes, ...chainedBadHoles(listOf(json.ROLLINFO.BADHOLES?.HOLE), calibration)]
|
|
102
|
+
.filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
|
|
103
|
+
.sort((a, b) => px(a.NOTE_ATTACK) - px(b.NOTE_ATTACK));
|
|
104
|
+
const features = chains
|
|
105
|
+
.map((hole) => {
|
|
106
|
+
const attack = px(hole.NOTE_ATTACK);
|
|
107
|
+
const release = px(hole.OFF_TIME);
|
|
108
|
+
const column = px(hole.ORIGIN_COL);
|
|
109
|
+
const columnWidth = px(hole.WIDTH_COL);
|
|
110
|
+
return {
|
|
111
|
+
type: 'Hole',
|
|
112
|
+
id: v4(),
|
|
113
|
+
...(stanford && {
|
|
114
|
+
depiction: stanford.depictionOf(column, attack, columnWidth, release - attack)
|
|
115
|
+
}),
|
|
116
|
+
vertical: {
|
|
117
|
+
from: +hole.TRACKER_HOLE + shift,
|
|
118
|
+
unit: 'track'
|
|
119
|
+
},
|
|
120
|
+
horizontal: {
|
|
121
|
+
unit: 'mm',
|
|
122
|
+
from: millimeters(attack, dpi),
|
|
123
|
+
to: millimeters(release, dpi)
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
type: 'RollCopy',
|
|
129
|
+
id: v4(),
|
|
130
|
+
ops: [],
|
|
131
|
+
conditions: [],
|
|
132
|
+
keeper: { name: '', sameAs: [] },
|
|
133
|
+
modifications: [],
|
|
134
|
+
...((scan ?? stanford) && { scan: scan ?? stanford?.scan }),
|
|
135
|
+
measurements: {
|
|
136
|
+
dimensions: {
|
|
137
|
+
width: millimeters(px(json.ROLLINFO.ROLL_WIDTH), dpi),
|
|
138
|
+
height: millimeters(px(json.ROLLINFO.IMAGE_LENGTH), dpi),
|
|
139
|
+
unit: 'mm'
|
|
140
|
+
},
|
|
141
|
+
...(punchDiameter !== undefined && {
|
|
142
|
+
punchDiameter: { value: punchDiameter, unit: 'mm' }
|
|
143
|
+
}),
|
|
144
|
+
holeSeparation: {
|
|
145
|
+
value: separation,
|
|
146
|
+
unit: 'px'
|
|
147
|
+
},
|
|
148
|
+
margins: {
|
|
149
|
+
treble: px(json.ROLLINFO.HARD_MARGIN_TREBLE),
|
|
150
|
+
bass: px(json.ROLLINFO.HARD_MARGIN_BASS),
|
|
151
|
+
unit: 'px'
|
|
152
|
+
},
|
|
153
|
+
trackCalibration: calibration,
|
|
154
|
+
...(measuredBy && { measuredBy })
|
|
155
|
+
},
|
|
156
|
+
features
|
|
157
|
+
};
|
|
158
|
+
}
|
package/lib/schema.json
CHANGED
|
@@ -1189,7 +1189,7 @@
|
|
|
1189
1189
|
],
|
|
1190
1190
|
"type": "object"
|
|
1191
1191
|
},
|
|
1192
|
-
"ObjectAssumption<
|
|
1192
|
+
"ObjectAssumption<Transcription>": {
|
|
1193
1193
|
"description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
|
|
1194
1194
|
"properties": {
|
|
1195
1195
|
"@annotation": {
|
|
@@ -1760,7 +1760,7 @@
|
|
|
1760
1760
|
"description": "The method by which this writing was produced (e.g. through print, handwriting, or stamping). [ontology: crm:P2 has type]"
|
|
1761
1761
|
},
|
|
1762
1762
|
"transcription": {
|
|
1763
|
-
"$ref": "#/definitions/ObjectAssumption%
|
|
1763
|
+
"$ref": "#/definitions/ObjectAssumption%3CTranscription%3E",
|
|
1764
1764
|
"description": "A transcription of the text content of the writing. This is an object assumption so that the transcription can be annotated with a belief about its correctness. [ontology: crm:P128 carries]"
|
|
1765
1765
|
},
|
|
1766
1766
|
"vertical": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TrackerBar } from "../../TrackerBar";
|
|
2
|
+
/**
|
|
3
|
+
* The commands of the Welte-Mignon T-100, as its tracker bar reads them.
|
|
4
|
+
*/
|
|
5
|
+
export declare const welteT100ExpressionTypes: readonly ["SustainPedalOn", "SustainPedalOff", "SoftPedalOn", "SoftPedalOff", "MezzoforteOff", "MezzoforteOn", "SlowCrescendoOn", "SlowCrescendoOff", "ForzandoOn", "ForzandoOff", "MotorOff", "MotorOn", "Rewind", "ElectricCutOff"];
|
|
6
|
+
export type WelteT100ExpressionType = typeof welteT100ExpressionTypes[number];
|
|
7
|
+
/**
|
|
8
|
+
* Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
|
|
9
|
+
*
|
|
10
|
+
* The note block spans 80 positions from C1 to g⁴, i.e. MIDI 24 to 103.
|
|
11
|
+
* The expression valves are duplicated, bass below the note block and
|
|
12
|
+
* treble above it, in mirrored order.
|
|
13
|
+
*/
|
|
14
|
+
export declare const welteT100: TrackerBar;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describeTrackerBar } from "../../TrackerBar";
|
|
2
|
+
/**
|
|
3
|
+
* The commands of the Welte-Mignon T-100, as its tracker bar reads them.
|
|
4
|
+
*/
|
|
5
|
+
export const welteT100ExpressionTypes = [
|
|
6
|
+
'SustainPedalOn',
|
|
7
|
+
'SustainPedalOff',
|
|
8
|
+
'SoftPedalOn',
|
|
9
|
+
'SoftPedalOff',
|
|
10
|
+
'MezzoforteOff',
|
|
11
|
+
'MezzoforteOn',
|
|
12
|
+
'SlowCrescendoOn',
|
|
13
|
+
'SlowCrescendoOff',
|
|
14
|
+
'ForzandoOn',
|
|
15
|
+
'ForzandoOff',
|
|
16
|
+
'MotorOff',
|
|
17
|
+
'MotorOn',
|
|
18
|
+
'Rewind',
|
|
19
|
+
'ElectricCutOff'
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
|
|
23
|
+
*
|
|
24
|
+
* The note block spans 80 positions from C1 to g⁴, i.e. MIDI 24 to 103.
|
|
25
|
+
* The expression valves are duplicated, bass below the note block and
|
|
26
|
+
* treble above it, in mirrored order.
|
|
27
|
+
*/
|
|
28
|
+
export const welteT100 = describeTrackerBar({
|
|
29
|
+
id: 'welte-t100',
|
|
30
|
+
name: 'Welte-Mignon T100',
|
|
31
|
+
width: 328,
|
|
32
|
+
trackCount: 100,
|
|
33
|
+
notes: { from: 11, to: 90, lowestPitch: 24 },
|
|
34
|
+
expressions: new Map([
|
|
35
|
+
[1, 'MezzoforteOff'],
|
|
36
|
+
[2, 'MezzoforteOn'],
|
|
37
|
+
[3, 'SlowCrescendoOff'],
|
|
38
|
+
[4, 'SlowCrescendoOn'],
|
|
39
|
+
[5, 'ForzandoOff'],
|
|
40
|
+
[6, 'ForzandoOn'],
|
|
41
|
+
[7, 'SoftPedalOff'],
|
|
42
|
+
[8, 'SoftPedalOn'],
|
|
43
|
+
[9, 'MotorOff'],
|
|
44
|
+
[10, 'MotorOn'],
|
|
45
|
+
[91, 'Rewind'],
|
|
46
|
+
[92, 'ElectricCutOff'],
|
|
47
|
+
[93, 'SustainPedalOn'],
|
|
48
|
+
[94, 'SustainPedalOff'],
|
|
49
|
+
[95, 'ForzandoOn'],
|
|
50
|
+
[96, 'ForzandoOff'],
|
|
51
|
+
[97, 'SlowCrescendoOn'],
|
|
52
|
+
[98, 'SlowCrescendoOff'],
|
|
53
|
+
[99, 'MezzoforteOn'],
|
|
54
|
+
[100, 'MezzoforteOff']
|
|
55
|
+
])
|
|
56
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Half, Parameters, PedalMode, Spool } from "welte-t100-emulator";
|
|
2
|
-
import { ReproducingSystem } from "
|
|
2
|
+
import { ReproducingSystem } from "../../ReproducingSystem";
|
|
3
3
|
/**
|
|
4
4
|
* MIDI velocity at the open rail of the Nuancierbalg, at the Mezzoforte pin,
|
|
5
5
|
* and at the closed rail, joined linearly in between. Nothing in the
|
|
@@ -12,6 +12,21 @@ export type VelocityMap = {
|
|
|
12
12
|
mezzoforte: number;
|
|
13
13
|
forte: number;
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* The two readings of the pedal mechanism the emulator offers, by name.
|
|
17
|
+
* Under `damping` the dampers reach the strings within the shortest lift
|
|
18
|
+
* the rolls punch, so every lift damps. Under `brushing` their fall is
|
|
19
|
+
* slowed until the quick runs of latch changes in the SUPRA corpus dip
|
|
20
|
+
* without damping, at the price that lifts shorter than about 265 ms brush
|
|
21
|
+
* as well.
|
|
22
|
+
*/
|
|
23
|
+
export declare const pedalPresets: {
|
|
24
|
+
damping: Readonly<Record<string, number>>;
|
|
25
|
+
brushing: Readonly<Record<string, number>>;
|
|
26
|
+
};
|
|
27
|
+
export type PedalPreset = keyof typeof pedalPresets;
|
|
28
|
+
/** The preset a set of pedal constants is, if it is one. */
|
|
29
|
+
export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
|
|
15
30
|
export type WelteT100Options = {
|
|
16
31
|
/**
|
|
17
32
|
* The take-up spool, which sets the time axis: it is held at a constant
|
|
@@ -20,7 +35,7 @@ export type WelteT100Options = {
|
|
|
20
35
|
spool: Spool;
|
|
21
36
|
/** Constants of the nuancing mechanism, one set for each half of the keyboard. */
|
|
22
37
|
nuance: Record<Half, Parameters>;
|
|
23
|
-
/** Constants of the two pedal actions. */
|
|
38
|
+
/** Constants of the two pedal actions, one of `pedalPresets` or a set of one's own. */
|
|
24
39
|
pedals: Parameters;
|
|
25
40
|
velocity: VelocityMap;
|
|
26
41
|
/**
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import { aperturePorts, DEFAULT_PUNCH_MM, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, pedalDefaults, playbackParameters, pneumaticModel, ROWS_PER_MM, runPedals, TRACKER_BORE_MM, travelBetweenRails, WELTE_SPOOL, } from "welte-t100-emulator";
|
|
2
|
-
import { welteT100 } from "
|
|
1
|
+
import { aperturePorts, DEFAULT_PUNCH_MM, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, pedalBrushing, pedalDefaults, playbackParameters, pneumaticModel, ROWS_PER_MM, runPedals, TRACKER_BORE_MM, travelBetweenRails, WELTE_SPOOL, } from "welte-t100-emulator";
|
|
2
|
+
import { welteT100 } from "./bar";
|
|
3
|
+
/**
|
|
4
|
+
* The two readings of the pedal mechanism the emulator offers, by name.
|
|
5
|
+
* Under `damping` the dampers reach the strings within the shortest lift
|
|
6
|
+
* the rolls punch, so every lift damps. Under `brushing` their fall is
|
|
7
|
+
* slowed until the quick runs of latch changes in the SUPRA corpus dip
|
|
8
|
+
* without damping, at the price that lifts shorter than about 265 ms brush
|
|
9
|
+
* as well.
|
|
10
|
+
*/
|
|
11
|
+
export const pedalPresets = {
|
|
12
|
+
damping: pedalDefaults,
|
|
13
|
+
brushing: pedalBrushing
|
|
14
|
+
};
|
|
15
|
+
const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
|
|
16
|
+
&& Object.entries(a).every(([name, value]) => b[name] === value);
|
|
17
|
+
/** The preset a set of pedal constants is, if it is one. */
|
|
18
|
+
export const pedalPresetOf = (pedals) => Object.keys(pedalPresets).find(name => sameParameters(pedalPresets[name], pedals));
|
|
3
19
|
export const defaultWelteT100Options = {
|
|
4
20
|
spool: WELTE_SPOOL,
|
|
5
21
|
nuance: {
|
|
6
22
|
bass: playbackParameters('bass'),
|
|
7
23
|
treble: playbackParameters('treble')
|
|
8
24
|
},
|
|
9
|
-
pedals:
|
|
25
|
+
pedals: pedalPresets.damping,
|
|
10
26
|
velocity: { piano: 35, mezzoforte: 60, forte: 90 },
|
|
11
27
|
pedalMode: 'continuous',
|
|
12
28
|
trackerBore: TRACKER_BORE_MM,
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Person } from "./Edition";
|
|
2
1
|
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
3
2
|
export type WithType<T extends string> = {
|
|
4
3
|
/**
|
|
@@ -13,13 +12,6 @@ export type WithId = {
|
|
|
13
12
|
*/
|
|
14
13
|
readonly id: string;
|
|
15
14
|
};
|
|
16
|
-
export type WithActor = {
|
|
17
|
-
/**
|
|
18
|
-
* The person who carried out this activity.
|
|
19
|
-
* @see crm:P14 carried out by
|
|
20
|
-
*/
|
|
21
|
-
actor?: Person;
|
|
22
|
-
};
|
|
23
15
|
export type WithNote = {
|
|
24
16
|
/**
|
|
25
17
|
* A free-text note providing additional context.
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
1
|
import { Edition } from "./Edition";
|
|
2
|
-
import { EditionView } from "./EditionView";
|
|
3
|
-
import { TrackerBar } from "./TrackerBar";
|
|
4
2
|
declare const validate: import("ajv").ValidateFunction<Edition>;
|
|
5
3
|
export { validate };
|
|
6
|
-
export type ConstraintProblem = {
|
|
7
|
-
version: string;
|
|
8
|
-
symbol: string;
|
|
9
|
-
problem: 'alignment-reference-missing' | 'before-reference-missing' | 'after-reference-missing' | 'placed-relative-to-itself' | 'placed-several-ways' | 'partner-missing' | 'paired-with-itself' | 'in-several-pairs' | 'pair-placed-on-both-sides';
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Where the placements and pairings of the edition cannot hold as
|
|
13
|
-
* stated, version by version: a reference or partner absent from the
|
|
14
|
-
* version, a perforation placed relative to itself or in several ways
|
|
15
|
-
* at once, one claimed by several pairs, or a pair whose members are
|
|
16
|
-
* both placed and so cannot keep their distance and follow their
|
|
17
|
-
* references at once.
|
|
18
|
-
*/
|
|
19
|
-
export declare const constraintProblems: (view: EditionView) => ConstraintProblem[];
|
|
20
|
-
export type UnknownExpressionType = {
|
|
21
|
-
version: string;
|
|
22
|
-
symbol: string;
|
|
23
|
-
expressionType: string;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* The expressions of the edition whose type the tracker bar does not
|
|
27
|
-
* read, version by version. The roll names its system; a type from
|
|
28
|
-
* another system, or a misspelt one, means nothing on it.
|
|
29
|
-
*/
|
|
30
|
-
export declare const unknownExpressionTypes: (view: EditionView, bar: TrackerBar) => UnknownExpressionType[];
|