linked-rolls 0.7.0 → 0.9.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/lib/Agent.d.ts CHANGED
@@ -16,11 +16,17 @@ export interface Named {
16
16
  */
17
17
  sameAs: string[];
18
18
  }
19
- export declare const agentRoles: readonly ["pianist", "editor", "publisher"];
19
+ export declare const editorialRoles: readonly ["editor", "transcription", "collation", "encoding", "commentary", "proofreading"];
20
+ /**
21
+ * The part an editor took in the editorial work.
22
+ */
23
+ export type EditorialRole = typeof editorialRoles[number];
24
+ declare const nonEditorialRoles: readonly ["pianist", "publisher"];
20
25
  /**
21
26
  * The role an agent plays in the context of the edition.
22
27
  */
23
- export type AgentRole = typeof agentRoles[number];
28
+ export type AgentRole = EditorialRole | typeof nonEditorialRoles[number];
29
+ export declare const agentRoles: readonly AgentRole[];
24
30
  /**
25
31
  * A person or a group: a pianist, an editor, a publisher,
26
32
  * a manufacturer, a library.
@@ -37,6 +43,16 @@ export interface Agent extends Named, Partial<WithId> {
37
43
  * An agent that is a person.
38
44
  */
39
45
  export type Person = Agent;
46
+ /**
47
+ * A person who took part in preparing the edition.
48
+ */
49
+ export interface Editor extends Person {
50
+ /**
51
+ * The part this person took in the editorial work.
52
+ * @see crm:P2 has type
53
+ */
54
+ role: EditorialRole;
55
+ }
40
56
  export type WithActor = {
41
57
  /**
42
58
  * The person who carried out this activity.
@@ -57,3 +73,4 @@ export interface Place extends Named {
57
73
  */
58
74
  export interface Concept extends Named, Partial<WithId> {
59
75
  }
76
+ export {};
package/lib/Agent.js CHANGED
@@ -1 +1,10 @@
1
- export const agentRoles = ['pianist', 'editor', 'publisher'];
1
+ export const editorialRoles = [
2
+ 'editor',
3
+ 'transcription',
4
+ 'collation',
5
+ 'encoding',
6
+ 'commentary',
7
+ 'proofreading'
8
+ ];
9
+ const nonEditorialRoles = ['pianist', 'publisher'];
10
+ export const agentRoles = [...nonEditorialRoles, ...editorialRoles];
@@ -1,19 +1,15 @@
1
1
  import { HorizontalSpan } from "./Feature";
2
2
  import { AnySymbol } from "./Symbol";
3
+ import { Millimeters } from "./Quantity";
3
4
  /**
4
- * Tolerance used in collation of roll copies.
5
- * The start and end tolerances define the acceptable
6
- * deviation (in mm) when aligning features across copies.
5
+ * Tolerance used in collation of roll copies: the acceptable deviation
6
+ * at either end when aligning features across copies.
7
7
  */
8
8
  export interface CollationTolerance {
9
- /**
10
- * Tolerance at the start position of a feature (in mm).
11
- */
12
- toleranceStart: number;
13
- /**
14
- * Tolerance at the end position of a feature (in mm).
15
- */
16
- toleranceEnd: number;
9
+ /** Tolerance at the start position of a feature. */
10
+ toleranceStart: Millimeters;
11
+ /** Tolerance at the end position of a feature. */
12
+ toleranceEnd: Millimeters;
17
13
  }
18
14
  export declare const defaultCollationTolerance: CollationTolerance;
19
15
  /** Where a symbol lies along the roll, as its carriers put it, or nothing for a symbol without a place. */
package/lib/Collation.js CHANGED
@@ -1,4 +1,5 @@
1
- export const defaultCollationTolerance = { toleranceStart: 5, toleranceEnd: 5 };
1
+ import { distance, mm } from "./Quantity";
2
+ export const defaultCollationTolerance = { toleranceStart: mm(5), toleranceEnd: mm(5) };
2
3
  /**
3
4
  * Two symbols collate when they are of one kind, say the same thing
4
5
  * (pitch, or expression type and scope), and lie at about the same
@@ -16,8 +17,8 @@ export const isCollatable = (a, b, locate, tolerance = defaultCollationTolerance
16
17
  const there = locate(b)?.horizontal;
17
18
  if (!here || !there)
18
19
  return false;
19
- return Math.abs(here.from - there.from) <= tolerance.toleranceStart
20
- && Math.abs(here.to - there.to) <= tolerance.toleranceEnd;
20
+ return distance(here.from, there.from) <= tolerance.toleranceStart
21
+ && distance(here.to, there.to) <= tolerance.toleranceEnd;
21
22
  };
22
23
  /** Each of the own symbols with every inherited symbol it collates with. */
23
24
  export const collationsOf = (own, inherited, locate, tolerance = defaultCollationTolerance) => own.flatMap(symbol => inherited
package/lib/Edition.d.ts CHANGED
@@ -2,7 +2,7 @@ import { RollCopy } from "./RollCopy";
2
2
  import { Version } from "./Version";
3
3
  import { CollationTolerance } from "./Collation";
4
4
  import { DateAssignment, ObjectAssumption } from "./Assumption";
5
- import { Concept, Person, Place } from "./Agent";
5
+ import { Concept, Editor, Person, Place } from "./Agent";
6
6
  import { RollTempo } from "./ReproducingSystem";
7
7
  /**
8
8
  * This type describes the creation of an edition,
@@ -10,6 +10,13 @@ import { RollTempo } from "./ReproducingSystem";
10
10
  * @see lrmoo:F28 Expression Creation
11
11
  */
12
12
  export interface EditionCreation {
13
+ /**
14
+ * The persons who prepared the edition, each with the part
15
+ * they took in the editorial work. An edition written before
16
+ * this field existed carries none.
17
+ * @see crm:P14 carried out by
18
+ */
19
+ editors?: Editor[];
13
20
  /**
14
21
  * The person or institution responsible for publishing the edition.
15
22
  * @see crm:P14 carried out by
@@ -1,4 +1,5 @@
1
1
  import { idOf, idsOf } from "./Assumption";
2
+ import { mean } from "./Quantity";
2
3
  export const getAt = (path, obj) => {
3
4
  let node = obj;
4
5
  for (const key of path) {
@@ -150,18 +151,18 @@ export class EditionView {
150
151
  if (carriers.length === 0) {
151
152
  return;
152
153
  }
153
- const horizontalFrom = carriers.reduce((hAcc, h) => hAcc + h.horizontal.from, 0) / carriers.length;
154
- const horizontalTo = carriers.reduce((hAcc, h) => hAcc + h.horizontal.to, 0) / carriers.length;
155
- const verticalFrom = carriers.reduce((vAcc, v) => vAcc + v.vertical.from, 0) / carriers.length;
156
- const definedVerticalTo = carriers.filter(v => v.vertical.to !== undefined);
157
- let verticalTo = undefined;
158
- if (definedVerticalTo.length > 0) {
159
- verticalTo = definedVerticalTo
160
- .reduce((vAcc, v) => vAcc + v.vertical.to, 0) / definedVerticalTo.length;
161
- }
154
+ const farEnds = carriers.flatMap(carrier => carrier.vertical.to === undefined ? [] : [carrier.vertical.to]);
162
155
  return {
163
- horizontal: { unit: 'mm', from: horizontalFrom, to: horizontalTo },
164
- vertical: { unit: 'track', from: verticalFrom, to: verticalTo }
156
+ horizontal: {
157
+ unit: 'mm',
158
+ from: mean(carriers.map(carrier => carrier.horizontal.from)),
159
+ to: mean(carriers.map(carrier => carrier.horizontal.to))
160
+ },
161
+ vertical: {
162
+ unit: 'track',
163
+ from: mean(carriers.map(carrier => carrier.vertical.from)),
164
+ ...(farEnds.length > 0 && { to: mean(farEnds) })
165
+ }
165
166
  };
166
167
  }
167
168
  snapshot(versionId) {
@@ -2,9 +2,10 @@ import { MidiFile } from "midifile-ts";
2
2
  import { EditionView } from "./EditionView";
3
3
  import { Version } from "./Version";
4
4
  import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "./ReproducingSystem";
5
+ import { Millimeters } from "./Quantity";
5
6
  export type EmulationScope = {
6
- /** Only notes whose onset lies within this span of the roll, in mm, are played. */
7
- range?: [number, number];
7
+ /** Only notes whose onset lies within this span of the roll are played. */
8
+ range?: [Millimeters, Millimeters];
8
9
  /** Start the clock at the first note rather than at the beginning of the roll. */
9
10
  skipToFirstNote?: boolean;
10
11
  };
package/lib/Emulation.js CHANGED
@@ -1,20 +1,18 @@
1
1
  import { MIDIControlEvents } from "midifile-ts";
2
2
  import { idOf } from "./Assumption";
3
3
  import { pairsAmong, placementsOf } from "./Symbol";
4
+ import { add, mean, mm, seconds, subtract } from "./Quantity";
4
5
  /** The punch diameter the edition's copies report, where any of them does. */
5
6
  const punchDiameterOf = (view) => {
6
7
  const measured = view.edition.copies
7
8
  .map(copy => copy.measurements.punchDiameter?.value)
8
9
  .filter((value) => value !== undefined && value > 0);
9
- if (measured.length === 0)
10
- return undefined;
11
- return measured.reduce((sum, value) => sum + value, 0) / measured.length;
10
+ return measured.length > 0 ? mean(measured) : undefined;
12
11
  };
13
12
  const propertiesOf = (view) => ({
14
13
  punchDiameter: punchDiameterOf(view),
15
14
  tempo: view.edition.tempoAdjustment
16
15
  });
17
- const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
18
16
  /** The onset a symbol has on each copy carrying it, by the copy's index, as the mean of its holes there. */
19
17
  const onsetsByCopy = (view, symbolId) => {
20
18
  const symbol = view.get(symbolId);
@@ -36,7 +34,7 @@ const offsetsBetween = (view, followerId, referenceId) => {
36
34
  const references = onsetsByCopy(view, referenceId);
37
35
  return [...onsetsByCopy(view, followerId)].flatMap(([copy, onset]) => {
38
36
  const reference = references.get(copy);
39
- return reference === undefined ? [] : [onset - reference];
37
+ return reference === undefined ? [] : [subtract(onset, reference)];
40
38
  });
41
39
  };
42
40
  /**
@@ -45,13 +43,11 @@ const offsetsBetween = (view, followerId, referenceId) => {
45
43
  * that agree with the statement put it, and one gap where none does.
46
44
  */
47
45
  const distanceOnSide = (side, offsets, gap) => {
48
- const agreeing = offsets
49
- .filter(offset => side === 'before' ? offset < 0 : offset > 0)
50
- .map(Math.abs);
51
- return agreeing.length > 0 ? mean(agreeing) : gap;
46
+ const agreeing = offsets.filter(offset => side === 'before' ? offset < 0 : offset > 0);
47
+ return agreeing.length > 0 ? mm(Math.abs(mean(agreeing))) : gap;
52
48
  };
53
49
  /**
54
- * How far each event has to move, in mm, for the placements to hold:
50
+ * How far each event has to move for the placements to hold:
55
51
  * an aligned event takes the onset of its reference, one placed before
56
52
  * or after its reference keeps its place where the measurement has it
57
53
  * on that side and is moved there where it does not, and a paired
@@ -71,12 +67,12 @@ const displacementsOf = (events, offsetsBetween, gap) => {
71
67
  const distance = (side) => distanceOnSide(side, offsetsBetween(event.id, reference.id), gap);
72
68
  switch (placement.relation) {
73
69
  case 'alignedWith': return referenceOnset;
74
- case 'before': return measured < referenceOnset ? measured : referenceOnset - distance('before');
75
- case 'after': return measured > referenceOnset ? measured : referenceOnset + distance('after');
70
+ case 'before': return measured < referenceOnset ? measured : subtract(referenceOnset, distance('before'));
71
+ case 'after': return measured > referenceOnset ? measured : add(referenceOnset, distance('after'));
76
72
  }
77
73
  };
78
74
  const displacements = new Map(events
79
- .map((event) => [event, placedOnsetOf(event) - event.horizontal.from])
75
+ .map((event) => [event, subtract(placedOnsetOf(event), event.horizontal.from)])
80
76
  .filter(([, distance]) => distance !== 0));
81
77
  pairsAmong(events).forEach(([one, other]) => {
82
78
  const shared = displacements.get(one) ?? displacements.get(other);
@@ -142,11 +138,11 @@ export class Emulation {
142
138
  * diameter, or a millimetre, where no copy agrees with a statement.
143
139
  */
144
140
  applyConstraints(view) {
145
- const gap = punchDiameterOf(view) ?? 1;
141
+ const gap = punchDiameterOf(view) ?? mm(1);
146
142
  const offsets = (follower, reference) => offsetsBetween(view, follower, reference);
147
143
  displacementsOf(this.negotiatedEvents, offsets, gap).forEach((distance, event) => {
148
- event.horizontal.from += distance;
149
- event.horizontal.to += distance;
144
+ event.horizontal.from = add(event.horizontal.from, distance);
145
+ event.horizontal.to = add(event.horizontal.to, distance);
150
146
  });
151
147
  }
152
148
  emulateVersion(version, view, { range, skipToFirstNote = false } = {}) {
@@ -176,9 +172,9 @@ export class Emulation {
176
172
  const performance = this.system.perform(this.negotiatedEvents, this.options, propertiesOf(view));
177
173
  this.curves = performance.curves;
178
174
  const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
179
- const origin = skipToFirstNote && onsets.length > 0 ? Math.min(...onsets) : 0;
175
+ const origin = seconds(skipToFirstNote && onsets.length > 0 ? Math.min(...onsets) : 0);
180
176
  this.midiEvents = performance.events
181
- .map(event => ({ ...event, at: event.at - origin }))
177
+ .map(event => ({ ...event, at: subtract(event.at, origin) }))
182
178
  .filter(event => event.at >= 0)
183
179
  .sort((a, b) => a.at - b.at);
184
180
  return this.midiEvents;
package/lib/Feature.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ObjectAssumption } from "./Assumption";
2
2
  import { ConditionState } from "./ConditionState";
3
3
  import { Text } from "./Symbol";
4
4
  import { PartialBy, WithId, WithType } from "./utils";
5
+ import { Millimeters, Track } from "./Quantity";
5
6
  /**
6
7
  * Describes the horizontal extent of a feature on the roll,
7
8
  * measured in millimeters from the beginning of the roll.
@@ -20,13 +21,13 @@ export interface HorizontalSpan {
20
21
  * from the beginning of the roll.
21
22
  * @see reo:from
22
23
  */
23
- from: number;
24
+ from: Millimeters;
24
25
  /**
25
26
  * The end position of the feature in millimeters
26
27
  * from the beginning of the roll.
27
28
  * @see reo:to
28
29
  */
29
- to: number;
30
+ to: Millimeters;
30
31
  }
31
32
  /**
32
33
  * Describes the vertical extent of a feature on the roll,
@@ -44,13 +45,13 @@ export interface VerticalSpan {
44
45
  * The start track number of the feature.
45
46
  * @see reo:from
46
47
  */
47
- from: number;
48
+ from: Track;
48
49
  /**
49
50
  * The end track number, if the feature spans multiple tracks.
50
51
  * If omitted, the feature occupies a single track.
51
52
  * @see reo:to
52
53
  */
53
- to?: number;
54
+ to?: Track;
54
55
  }
55
56
  export declare const featureTypes: readonly ["Hole", "Writing", "Mark", "GluedOn"];
56
57
  export type FeatureType = typeof featureTypes[number];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Numbers that carry their unit in the type. At runtime a quantity is
3
+ * the plain number it was made from, so it compares, sorts and prints
4
+ * as one and goes wherever a number is wanted. The other direction is
5
+ * closed: a number becomes a quantity only through the constructors
6
+ * here, and a quantity in one unit cannot stand in for one in another.
7
+ * The unit names are the ones the records state in their `unit` field.
8
+ */
9
+ declare const unit: unique symbol;
10
+ export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min';
11
+ export type Quantity<U extends Unit> = number & {
12
+ readonly [unit]: U;
13
+ };
14
+ /** A place along the roll, or any length on the paper. */
15
+ export type Millimeters = Quantity<'mm'>;
16
+ export type Centimeters = Quantity<'cm'>;
17
+ /** A place in a scan. Comparable within one scan only, since the resolution is the scan's. */
18
+ export type Pixels = Quantity<'px'>;
19
+ /** A position across the roll, numbered as the tracker bar does. */
20
+ export type Track = Quantity<'track'>;
21
+ export type Seconds = Quantity<'s'>;
22
+ export type Milliseconds = Quantity<'ms'>;
23
+ export type FeetPerMinute = Quantity<'ft/min'>;
24
+ export type MetersPerMinute = Quantity<'m/min'>;
25
+ export declare const mm: (value: number) => Quantity<"mm">;
26
+ export declare const cm: (value: number) => Quantity<"cm">;
27
+ export declare const px: (value: number) => Quantity<"px">;
28
+ export declare const track: (value: number) => Quantity<"track">;
29
+ export declare const seconds: (value: number) => Quantity<"s">;
30
+ export declare const milliseconds: (value: number) => Quantity<"ms">;
31
+ export declare const feetPerMinute: (value: number) => Quantity<"ft/min">;
32
+ export declare const metersPerMinute: (value: number) => Quantity<"m/min">;
33
+ /**
34
+ * A value together with the unit it was measured in, as a record
35
+ * states it.
36
+ * @see crm:E54 Dimension
37
+ */
38
+ export interface Measure<U extends Unit> {
39
+ /**
40
+ * The measured value.
41
+ * @see crm:P90 has value
42
+ */
43
+ value: Quantity<U>;
44
+ /**
45
+ * The unit of measurement.
46
+ * @see crm:P91 has unit
47
+ */
48
+ unit: U;
49
+ }
50
+ export declare const add: <U extends Unit>(a: Quantity<U>, b: Quantity<NoInfer<U>>) => Quantity<U>;
51
+ export declare const subtract: <U extends Unit>(a: Quantity<U>, b: Quantity<NoInfer<U>>) => Quantity<U>;
52
+ export declare const distance: <U extends Unit>(a: Quantity<U>, b: Quantity<NoInfer<U>>) => Quantity<U>;
53
+ /** A quantity times a plain factor, such as a stretch. */
54
+ export declare const scale: <U extends Unit>(a: Quantity<U>, factor: number) => Quantity<U>;
55
+ export declare const sum: <U extends Unit>(values: readonly Quantity<U>[]) => Quantity<U>;
56
+ export declare const mean: <U extends Unit>(values: readonly Quantity<U>[]) => Quantity<U>;
57
+ /** A place in a scan taken at `dpi` dots per inch, on the paper. */
58
+ export declare const inMillimeters: (place: Pixels, dpi: number) => Millimeters;
59
+ export declare const inCentimeters: (length: Millimeters) => Centimeters;
60
+ export declare const inSeconds: (time: Milliseconds) => Seconds;
61
+ export {};
@@ -0,0 +1,25 @@
1
+ const quantity = (value) => value;
2
+ export const mm = (quantity);
3
+ export const cm = (quantity);
4
+ export const px = (quantity);
5
+ export const track = (quantity);
6
+ export const seconds = (quantity);
7
+ export const milliseconds = (quantity);
8
+ export const feetPerMinute = (quantity);
9
+ export const metersPerMinute = (quantity);
10
+ // The second operand takes its unit from the first. Inferred from both,
11
+ // TypeScript would unite two units rather than reject them.
12
+ export const add = (a, b) => quantity(a + b);
13
+ export const subtract = (a, b) => quantity(a - b);
14
+ export const distance = (a, b) => quantity(Math.abs(a - b));
15
+ /** A quantity times a plain factor, such as a stretch. */
16
+ export const scale = (a, factor) => quantity(a * factor);
17
+ // A list is taken at its element type, so one that mixes units yields a
18
+ // union unit rather than an error; only the binary operations reject a mix.
19
+ export const sum = (values) => quantity(values.reduce((total, value) => total + value, 0));
20
+ export const mean = (values) => quantity(sum(values) / values.length);
21
+ const MM_PER_INCH = 25.4;
22
+ /** A place in a scan taken at `dpi` dots per inch, on the paper. */
23
+ export const inMillimeters = (place, dpi) => mm(place / dpi * MM_PER_INCH);
24
+ export const inCentimeters = (length) => cm(length / 10);
25
+ export const inSeconds = (time) => seconds(time / 1000);
@@ -1,29 +1,32 @@
1
1
  import { Hole } from "./Feature";
2
2
  import { Expression, Note } from "./Symbol";
3
3
  import { TrackerBar } from "./TrackerBar";
4
+ import { Millimeters, Quantity, Seconds } from "./Quantity";
5
+ export type SpeedUnit = 'ft/min' | 'm/min';
4
6
  /**
5
- * The playback tempo of the roll, specified as a starting
6
- * and ending speed. The tempo may change over the course
7
- * of the roll due to acceleration effects.
7
+ * The playback tempo of the roll as a paper speed, stated at the
8
+ * beginning and at the end, since the speed may change over the
9
+ * course of the roll through acceleration.
8
10
  * @see crm:E54 Dimension
9
11
  */
10
- export interface RollTempo {
12
+ export interface RollTempoIn<U extends SpeedUnit> {
11
13
  /**
12
14
  * The tempo at the beginning of the roll.
13
15
  * @see reo:from
14
16
  */
15
- startsWith: number;
17
+ startsWith: Quantity<U>;
16
18
  /**
17
19
  * The tempo at the end of the roll.
18
20
  * @see reo:to
19
21
  */
20
- endsWith: number;
22
+ endsWith: Quantity<U>;
21
23
  /**
22
- * The unit of the tempo measurement (e.g. 'ft/min', 'm/min').
24
+ * The unit of the tempo measurement.
23
25
  * @see crm:P91 has unit
24
26
  */
25
- unit: string;
27
+ unit: U;
26
28
  }
29
+ export type RollTempo = RollTempoIn<'ft/min'> | RollTempoIn<'m/min'>;
27
30
  /**
28
31
  * A note or expression of a version with the dimensions of its carriers
29
32
  * averaged in and the editorial assumptions applied, which is all a
@@ -33,8 +36,8 @@ export type NegotiatedEvent = Omit<Note | Expression, 'carriers'> & Pick<Hole, '
33
36
  interface PerformedRollFeature<T> {
34
37
  type: T;
35
38
  performs: NegotiatedEvent;
36
- /** Seconds from the beginning of the roll. */
37
- at: number;
39
+ /** Time from the beginning of the roll. */
40
+ at: Seconds;
38
41
  }
39
42
  interface PerformedNoteEvent<T> extends PerformedRollFeature<T> {
40
43
  pitch: number;
@@ -76,8 +79,8 @@ export type PedalCurve = CurveSamples & {
76
79
  export type EmulatedCurve = DynamicsCurve | PedalCurve;
77
80
  /** What the edition records about the roll that a mechanism may want to know. */
78
81
  export type RollProperties = {
79
- /** Diameter of the punches in mm, where the copies record it. */
80
- punchDiameter?: number;
82
+ /** Diameter of the punches, where the copies record it. */
83
+ punchDiameter?: Millimeters;
81
84
  /** The tempo the edition adjusts the roll to, where it states one. */
82
85
  tempo?: RollTempo;
83
86
  };
package/lib/RollCopy.d.ts CHANGED
@@ -6,6 +6,7 @@ import { AnyFeature } from "./Feature";
6
6
  import { ActorAssignment, DateAssignment, ObjectAssumption } from "./Assumption";
7
7
  import { WithId, WithType } from "./utils";
8
8
  import { Agent, Concept } from "./Agent";
9
+ import { Measure, Millimeters, Quantity, Track } from "./Quantity";
9
10
  /**
10
11
  * This condition state is used to describe the roll's
11
12
  * paper shrinkage or stretching. It might be calculated
@@ -39,14 +40,16 @@ export declare const rollConditions: readonly ["general", "paper-stretch"];
39
40
  * (along the roll length, in mm) and vertical (across tracks).
40
41
  */
41
42
  export interface Shift {
42
- /**
43
- * Horizontal shift in millimeters (along the roll length).
44
- */
45
- horizontal: number;
46
- /**
47
- * Vertical shift in track numbers (across the tracker bar).
48
- */
49
- vertical: number;
43
+ /** Along the roll. */
44
+ horizontal: Millimeters;
45
+ /** Across the tracker bar. */
46
+ vertical: Track;
47
+ }
48
+ /** The margins on the treble and bass sides of the roll, in the unit the scan was measured in. */
49
+ export interface Margins<U extends 'px' | 'mm'> {
50
+ treble: Quantity<U>;
51
+ bass: Quantity<U>;
52
+ unit: U;
50
53
  }
51
54
  /**
52
55
  * Describes the production of a roll copy: the manufacturer,
@@ -140,71 +143,37 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
140
143
  */
141
144
  dimensions: {
142
145
  /**
143
- * The width of the roll in the given unit.
146
+ * The width of the roll.
144
147
  * @see reo:width
145
148
  */
146
- width: number;
149
+ width: Millimeters;
147
150
  /**
148
- * The total height (length) of the roll in the given unit.
151
+ * The total height (length) of the roll.
149
152
  * @see reo:height
150
153
  */
151
- height: number;
154
+ height: Millimeters;
152
155
  /**
153
- * The unit of measurement (e.g. 'mm').
156
+ * The unit of measurement.
154
157
  * @see crm:P91 has unit
155
158
  */
156
- unit: string;
159
+ unit: 'mm';
157
160
  };
158
161
  /**
159
162
  * The average diameter of punched holes.
160
163
  * @see reo:punchDiameter
161
164
  */
162
- punchDiameter: {
163
- /**
164
- * The measured punch diameter value.
165
- * @see crm:P90 has value
166
- */
167
- value: number;
168
- /**
169
- * The unit of measurement (e.g. 'mm').
170
- * @see crm:P91 has unit
171
- */
172
- unit: string;
173
- };
165
+ punchDiameter: Measure<'mm'>;
174
166
  /**
175
- * The distance between adjacent tracker bar holes.
167
+ * The distance between adjacent tracker bar holes, in the
168
+ * unit the scan was measured in.
176
169
  * @see reo:holeSeparation
177
170
  */
178
- holeSeparation: {
179
- /**
180
- * The measured hole separation value.
181
- * @see crm:P90 has value
182
- */
183
- value: number;
184
- /**
185
- * The unit of measurement (e.g. 'px', 'mm').
186
- * @see crm:P91 has unit
187
- */
188
- unit: string;
189
- };
171
+ holeSeparation: Measure<'px'> | Measure<'mm'>;
190
172
  /**
191
173
  * The margins on the treble and bass sides of the roll.
192
174
  * Not exported to RDF.
193
175
  */
194
- margins: {
195
- /**
196
- * The margin on the treble side.
197
- */
198
- treble: number;
199
- /**
200
- * The margin on the bass side.
201
- */
202
- bass: number;
203
- /**
204
- * The unit of measurement (e.g. 'px', 'mm').
205
- */
206
- unit: string;
207
- };
176
+ margins: Margins<'px'> | Margins<'mm'>;
208
177
  /**
209
178
  * The shift applied to align this copy with the others.
210
179
  * Not exported to RDF.
@@ -282,7 +251,7 @@ export declare function asSymbols(features: AnyFeature[], bar?: TrackerBar): Any
282
251
  * The tracks a copy carries holes on that the tracker bar does not read.
283
252
  * A non-empty result usually means the scan is calibrated wrongly.
284
253
  */
285
- export declare function unreadTracks(features: AnyFeature[], bar?: TrackerBar): Map<number, number>;
254
+ export declare function unreadTracks(features: AnyFeature[], bar?: TrackerBar): Map<Track, number>;
286
255
  /**
287
256
  * Falls back to the way the scan geometry was reconstructed before the
288
257
  * calibration was recorded: the bass hard margin stood in for the phase
package/lib/RollCopy.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { v4 } from "uuid";
2
2
  import { welteT100 } from "./systems/welteT100/bar";
3
3
  import { assignReference } from "./Assumption";
4
+ import { px, track } from "./Quantity";
4
5
  export const rollConditions = [
5
6
  'general',
6
7
  'paper-stretch'
@@ -34,8 +35,8 @@ export function unreadTracks(features, bar = welteT100) {
34
35
  .filter(feature => feature.type === 'Hole')
35
36
  .filter(feature => !bar.meaningOf(feature.vertical.from))
36
37
  .forEach(feature => {
37
- const track = feature.vertical.from;
38
- counts.set(track, (counts.get(track) || 0) + 1);
38
+ const position = feature.vertical.from;
39
+ counts.set(position, (counts.get(position) || 0) + 1);
39
40
  });
40
41
  return counts;
41
42
  }
@@ -53,14 +54,13 @@ export const calibrationOf = (copy) => {
53
54
  if (copy.measurements.trackCalibration) {
54
55
  return copy.measurements.trackCalibration;
55
56
  }
56
- const separation = copy.measurements.holeSeparation?.value;
57
- const bassMargin = copy.measurements.margins?.bass;
58
- if (separation === undefined || bassMargin === undefined)
57
+ const { holeSeparation, margins } = copy.measurements;
58
+ if (holeSeparation?.unit !== 'px' || margins?.unit !== 'px')
59
59
  return undefined;
60
60
  return {
61
61
  unit: 'px',
62
- offset: bassMargin + 1.5 * separation,
63
- separation,
64
- shift: 0
62
+ offset: px(margins.bass + 1.5 * holeSeparation.value),
63
+ separation: holeSeparation.value,
64
+ shift: track(0)
65
65
  };
66
66
  };
@@ -1,3 +1,4 @@
1
+ import { Pixels, Track } from "./Quantity";
1
2
  /**
2
3
  * Relates a scan to the tracker bar it was read with.
3
4
  *
@@ -12,22 +13,22 @@
12
13
  export interface TrackCalibration {
13
14
  unit: 'px';
14
15
  /** Image column of the scanning software's track 0. */
15
- offset: number;
16
+ offset: Pixels;
16
17
  /** Distance between the centres of adjacent tracks. */
17
- separation: number;
18
+ separation: Pixels;
18
19
  /** Added to the scanning software's numbering to reach the tracker bar. */
19
- shift: number;
20
+ shift: Track;
20
21
  }
21
22
  /** Image column at the centre of a tracker bar track. */
22
- export declare const columnOf: (track: number, calibration: TrackCalibration) => number;
23
+ export declare const columnOf: (position: Track, calibration: TrackCalibration) => Pixels;
23
24
  /** Tracker bar track covering an image column, unrounded. */
24
- export declare const trackAt: (column: number, calibration: TrackCalibration) => number;
25
+ export declare const trackAt: (column: Pixels, calibration: TrackCalibration) => Track;
25
26
  /**
26
27
  * The columns covered by a run of tracks, from the outer edge of the
27
28
  * first to the outer edge of the last.
28
29
  */
29
- export declare const columnsOf: (from: number, to: number, calibration: TrackCalibration) => {
30
- from: number;
31
- to: number;
32
- width: number;
30
+ export declare const columnsOf: (from: Track, to: Track, calibration: TrackCalibration) => {
31
+ from: Pixels;
32
+ to: Pixels;
33
+ width: Pixels;
33
34
  };