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.
Files changed (44) hide show
  1. package/README.md +7 -4
  2. package/lib/Agent.d.ts +59 -0
  3. package/lib/Agent.js +1 -0
  4. package/lib/Assumption.d.ts +19 -1
  5. package/lib/Collation.d.ts +19 -0
  6. package/lib/Collation.js +25 -1
  7. package/lib/Edit.d.ts +1 -8
  8. package/lib/Edition.d.ts +4 -77
  9. package/lib/Edition.js +1 -1
  10. package/lib/EditionView.d.ts +0 -7
  11. package/lib/EditionView.js +0 -50
  12. package/lib/Feature.d.ts +3 -1
  13. package/lib/ReproducingSystem.d.ts +23 -1
  14. package/lib/RollCopy.d.ts +2 -68
  15. package/lib/RollCopy.js +2 -303
  16. package/lib/TrackerBar.d.ts +16 -14
  17. package/lib/TrackerBar.js +1 -56
  18. package/lib/alignment.d.ts +21 -0
  19. package/lib/alignment.js +117 -0
  20. package/lib/constraints.d.ts +27 -0
  21. package/lib/constraints.js +61 -0
  22. package/lib/editionOps.js +7 -10
  23. package/lib/index.d.ts +18 -13
  24. package/lib/index.js +18 -13
  25. package/lib/migrate.js +2 -1
  26. package/lib/readers/spencerMidi.d.ts +26 -0
  27. package/lib/readers/spencerMidi.js +58 -0
  28. package/lib/readers/stanfordAton.d.ts +18 -0
  29. package/lib/readers/stanfordAton.js +158 -0
  30. package/lib/schema.json +2 -2
  31. package/lib/systems/welteT100/bar.d.ts +14 -0
  32. package/lib/systems/welteT100/bar.js +56 -0
  33. package/lib/systems/{welteT100.d.ts → welteT100/system.d.ts} +17 -2
  34. package/lib/systems/{welteT100.js → welteT100/system.js} +19 -3
  35. package/lib/utils.d.ts +0 -8
  36. package/lib/validate.d.ts +0 -27
  37. package/lib/validate.js +0 -61
  38. package/package.json +4 -4
  39. package/lib/alignFeatures.d.ts +0 -12
  40. package/lib/alignFeatures.js +0 -55
  41. /package/lib/{aton → readers}/AtonParser.d.ts +0 -0
  42. /package/lib/{aton → readers}/AtonParser.js +0 -0
  43. /package/lib/{asMIDISpans.d.ts → readers/midiSpans.d.ts} +0 -0
  44. /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 { applyShift, applyStretch, asSymbols, revertShift, revertStretch } from "./RollCopy";
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 = defaultTolerance) => {
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(view, own, inherited, tolerance);
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 = defaultTolerance) => {
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(view, own, view.snapshot(idOf(version.basedOn)), tolerance);
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 './context';
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 './importJsonLd';
12
- export * from './RollCopy';
7
+ export * from './Edit';
13
8
  export * from './Version';
14
- export * from './Symbol';
15
- export * from './TrackerBar';
9
+ export * from './Collation';
16
10
  export * from './TrackCalibration';
17
- export * from './alignFeatures';
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 './utils';
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 './context';
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 './importJsonLd';
12
- export * from './RollCopy';
7
+ export * from './Edit';
13
8
  export * from './Version';
14
- export * from './Symbol';
15
- export * from './TrackerBar';
9
+ export * from './Collation';
16
10
  export * from './TrackCalibration';
17
- export * from './alignFeatures';
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 './utils';
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, welteT100 } from "./TrackerBar";
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<alias-731470504-73739-73887-731470504-0-217694<def-interface-src_Symbol.ts-5758-6122-src_Symbol.ts-0-6378400334313,\"carriers\">>": {
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%3Calias-731470504-73739-73887-731470504-0-217694%3Cdef-interface-src_Symbol.ts-5758-6122-src_Symbol.ts-0-6378400334313%2C%22carriers%22%3E%3E",
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 "../ReproducingSystem";
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 "../TrackerBar";
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: pedalDefaults,
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[];