linked-rolls 0.66.0 → 0.67.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.
@@ -1,7 +1,7 @@
1
1
  import { Edition } from "../model/Edition.js";
2
2
  import { Strain } from "../model/RollCopy.js";
3
3
  import { LengthRatio } from "../model/Version.js";
4
- import { Percent } from "../model/Quantity.js";
4
+ import { MetersPerMinute, Percent } from "../model/Quantity.js";
5
5
  /**
6
6
  * How far the paper of one copy is taken to have stretched or shrunk
7
7
  * along the roll, as a standard deviation, where the edition has too few
@@ -121,6 +121,16 @@ export type AlignmentProblem = {
121
121
  */
122
122
  by?: number;
123
123
  };
124
+ /**
125
+ * The speed the version's paper starts at, as the copies of its own system
126
+ * that bear witness to it at first hand state they were cut for: the mean
127
+ * of the speeds the edition holds true or likely. A stated speed is the
128
+ * speed at the beginning of the roll, as a tempo marking gives it. Nothing
129
+ * where no such copy states one; in particular, a copy that reaches the
130
+ * version only through a later one says nothing about it, since the later
131
+ * one may have been cut for another speed.
132
+ */
133
+ export declare const paperSpeedOf: (edition: Edition, versionId: string) => MetersPerMinute | undefined;
124
134
  /**
125
135
  * Where the alignments of the edition's copies, or what they say about
126
136
  * the paper, want looking into. None of it is recomputed, so it is cheap
@@ -4,8 +4,8 @@ import { certaintyOf, idOf, isAsserted } from "../model/Assumption.js";
4
4
  import { principalDerivationOf } from "../model/Version.js";
5
5
  import { versionIn } from "../lookup/lookup.js";
6
6
  import { witnessesOf } from "./witnesses.js";
7
- import { inMetersPerMinute, percent } from "../model/Quantity.js";
8
- import { trackerBarOf } from "../systems/index.js";
7
+ import { inMetersPerMinute, mean, percent } from "../model/Quantity.js";
8
+ import { defaultTrackerBar, trackerBarOf } from "../systems/index.js";
9
9
  /**
10
10
  * How far the paper of one copy is taken to have stretched or shrunk
11
11
  * along the roll, as a standard deviation, where the edition has too few
@@ -217,6 +217,27 @@ const statedSpeed = (copy) => {
217
217
  const speed = copy.production?.speed;
218
218
  return speed && isAsserted(certaintyOf(speed)) ? inMetersPerMinute(speed) : undefined;
219
219
  };
220
+ /**
221
+ * The speed the version's paper starts at, as the copies of its own system
222
+ * that bear witness to it at first hand state they were cut for: the mean
223
+ * of the speeds the edition holds true or likely. A stated speed is the
224
+ * speed at the beginning of the roll, as a tempo marking gives it. Nothing
225
+ * where no such copy states one; in particular, a copy that reaches the
226
+ * version only through a later one says nothing about it, since the later
227
+ * one may have been cut for another speed.
228
+ */
229
+ export const paperSpeedOf = (edition, versionId) => {
230
+ const version = versionIn(edition, versionId);
231
+ if (!version)
232
+ return undefined;
233
+ const system = (trackerBarOf(version.system) ?? defaultTrackerBar).id;
234
+ const speeds = witnessesOf(edition, versionId)
235
+ .filter(witness => witness.through === undefined)
236
+ .flatMap(witness => edition.copies.filter(copy => copy.id === witness.copy && barOf(copy).id === system))
237
+ .map(statedSpeed)
238
+ .filter(speed => speed !== undefined);
239
+ return speeds.length > 0 ? mean(speeds) : undefined;
240
+ };
220
241
  /** The speed the reference copy was cut at: as stated for it, or as its system runs. */
221
242
  const referenceSpeed = (reference) => {
222
243
  const system = barOf(reference).paperSpeed;
@@ -1,12 +1,15 @@
1
1
  import { MIDIControlEvents } from "midifile-ts";
2
- import { idOf } from "../model/Assumption.js";
2
+ import { certaintyOf, idOf, isAsserted } from "../model/Assumption.js";
3
3
  import { isCommand, pairsAmong, placementsOf } from "../model/Symbol.js";
4
+ import { principalDerivationOf } from "../model/Version.js";
4
5
  import { add, mean, mm, seconds, subtract } from "../model/Quantity.js";
5
6
  import { punchDiameterOf } from "../model/RollCopy.js";
6
7
  import { toOwnPaperOf } from "../analysis/ownPaper.js";
8
+ import { paperSpeedOf } from "../analysis/paper.js";
9
+ import { defaultTrackerBar, trackerBarOf } from "../systems/index.js";
7
10
  import { negotiatedEventOf } from "./negotiation.js";
8
11
  import { placedCarriersOf, snapshotOf } from "../analysis/text.js";
9
- import { copyOfFeature, symbolIn } from "../lookup/lookup.js";
12
+ import { copyOfFeature, symbolIn, versionIn } from "../lookup/lookup.js";
10
13
  /** The punch diameter the edition's copies report, where any of them does. */
11
14
  const meanPunchDiameterOf = (edition) => {
12
15
  const measured = edition.copies
@@ -14,10 +17,62 @@ const meanPunchDiameterOf = (edition) => {
14
17
  .filter(value => value !== undefined);
15
18
  return measured.length > 0 ? mean(measured) : undefined;
16
19
  };
20
+ /** The version this one derives from, where it names one the edition has. */
21
+ const baseOf = (edition, version) => {
22
+ const derivation = principalDerivationOf(version);
23
+ return derivation && versionIn(edition, idOf(derivation));
24
+ };
25
+ const onOneSystem = (one, other) => (trackerBarOf(one.system) ?? defaultTrackerBar).id === (trackerBarOf(other.system) ?? defaultTrackerBar).id;
26
+ /**
27
+ * The speed the version starts at, as far as the edition states one: as
28
+ * its own copies state it (`paperSpeedOf`), or else as stated for the
29
+ * version it derives from on the same system, whose paper it keeps.
30
+ */
31
+ const statedSpeedOf = (edition, version, seen = new Set()) => {
32
+ const own = paperSpeedOf(edition, version.id);
33
+ if (own !== undefined || seen.has(version.id))
34
+ return own;
35
+ const base = baseOf(edition, version);
36
+ return base && onOneSystem(base, version)
37
+ ? statedSpeedOf(edition, base, new Set([...seen, version.id]))
38
+ : undefined;
39
+ };
40
+ /**
41
+ * How long the version's paper runs for a length of the paper of the
42
+ * version it derives from: as its creation states it where the edition
43
+ * holds to that, else as the papers the two are performed on relate. That
44
+ * is what the alignments give for the two systems (`toOwnPaperOf`), which
45
+ * a performance credits every version of a system with, whether or not its
46
+ * own copies measure the paper (cf. `lengthRatioOf`).
47
+ */
48
+ const lengthRatioIn = (edition, version, base) => {
49
+ const stated = version.creation?.lengthRatio;
50
+ if (stated && isAsserted(certaintyOf(stated)))
51
+ return stated.value;
52
+ const own = toOwnPaperOf(edition, version);
53
+ const theirs = toOwnPaperOf(edition, base);
54
+ return own !== undefined && theirs !== undefined ? own / theirs : undefined;
55
+ };
56
+ /**
57
+ * The roll a re-cut was made from, as far as its timing goes: the length
58
+ * ratio, and the speed stated for the original. Nothing for a version that
59
+ * derives from none, or from one on its own system.
60
+ */
61
+ const recutFromOf = (edition, version) => {
62
+ const base = baseOf(edition, version);
63
+ if (!base || onOneSystem(base, version))
64
+ return undefined;
65
+ const lengthRatio = lengthRatioIn(edition, version, base);
66
+ if (lengthRatio === undefined)
67
+ return undefined;
68
+ const paperSpeed = statedSpeedOf(edition, base);
69
+ return { lengthRatio, ...(paperSpeed !== undefined && { paperSpeed }) };
70
+ };
17
71
  const propertiesOf = (edition, version) => ({
18
72
  punchDiameter: meanPunchDiameterOf(edition),
19
- tempo: edition.tempoAdjustment,
20
- toOwnPaper: toOwnPaperOf(edition, version)
73
+ toOwnPaper: toOwnPaperOf(edition, version),
74
+ paperSpeed: statedSpeedOf(edition, version),
75
+ recutFrom: recutFromOf(edition, version)
21
76
  });
22
77
  /** The onset a symbol has on each copy carrying it, by the copy's id, as the mean of its chains there. */
23
78
  const onsetsByCopy = (edition, symbolId) => {
package/lib/io/migrate.js CHANGED
@@ -644,6 +644,18 @@ const withoutFormatVersion = (edition) => {
644
644
  const { formatVersion: _stated, ...rest } = edition;
645
645
  return rest;
646
646
  };
647
+ /**
648
+ * An edition once stated one tempo for the roll, which cannot hold where the
649
+ * roll was issued for systems whose papers run at different speeds. A copy
650
+ * states the speed it was cut for, and a re-cut's follows from its original's
651
+ * and the length ratio, so the edition's goes.
652
+ */
653
+ const withoutTempoAdjustment = (edition) => {
654
+ if (!Object.hasOwn(edition, 'tempoAdjustment'))
655
+ return edition;
656
+ const { tempoAdjustment: _stated, ...rest } = edition;
657
+ return rest;
658
+ };
647
659
  /** Whether the copy states features, in either shape. */
648
660
  const hasFeatures = (copy) => [copy?.features, copy?.production?.produced, copy?.productionEvent?.produced]
649
661
  .some(features => Array.isArray(features) && features.length > 0);
@@ -664,5 +676,5 @@ const withReferenceCopy = (edition) => {
664
676
  ? { ...edition, referenceCopy: plainCopyId(reference['@id']) }
665
677
  : edition;
666
678
  };
667
- const editionSteps = [withoutFormatVersion, withoutEditionType, withSystems, withEditors, withDerivationTolerance, withReferenceCopy];
679
+ const editionSteps = [withoutFormatVersion, withoutTempoAdjustment, withoutEditionType, withSystems, withEditors, withDerivationTolerance, withReferenceCopy];
668
680
  export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
@@ -1,8 +1,7 @@
1
1
  import { RollCopy } from "./RollCopy.js";
2
2
  import { Version } from "./Version.js";
3
- import { DateAssignment, ObjectAssumption } from "./Assumption.js";
3
+ import { DateAssignment } from "./Assumption.js";
4
4
  import { Editor, Person, Place } from "./Agent.js";
5
- import { RollTempo } from "../systems/ReproducingSystem.js";
6
5
  /**
7
6
  * This type describes the creation of an edition,
8
7
  * i.e. the editor, publisher, and publication date.
@@ -145,12 +144,6 @@ export interface Edition {
145
144
  * @see lrmoo:R75 incorporates
146
145
  */
147
146
  versions: Version[];
148
- /**
149
- * An optional tempo adjustment for playback of the roll,
150
- * annotatable with a belief about its correctness.
151
- * @see reo:tempo
152
- */
153
- tempoAdjustment?: ObjectAssumption<RollTempo>;
154
147
  }
155
148
  export type EditionMetadata = Pick<Edition, 'base' | 'title' | 'license' | 'creation' | 'roll'>;
156
149
  /**
@@ -231,11 +231,14 @@ export interface ProductionEvent {
231
231
  */
232
232
  system?: Concept;
233
233
  /**
234
- * The paper speed the copy was cut for. A copy cut from the same
235
- * master for another speed comes out longer or shorter than the
236
- * roll it is aligned with by the ratio of the speeds. The alignment
237
- * measures that ratio far more closely than a label states a speed,
238
- * so the speed is held against what the alignments give
234
+ * The paper speed the copy was cut for, as the speed at the
235
+ * beginning of the roll: a tempo marking states it so, and the
236
+ * take-up spool accelerates from there. A performance of a version
237
+ * the copy bears witness to starts at it (`paperSpeedOf`). A copy cut
238
+ * from the same master for another speed comes out longer or shorter
239
+ * than the roll it is aligned with by the ratio of the speeds. The
240
+ * alignment measures that ratio far more closely than a label states
241
+ * a speed, so the speed is held against what the alignments give
239
242
  * (`alignmentProblems`) rather than used to find it.
240
243
  * @see reo:paperSpeed
241
244
  */
package/lib/schema.json CHANGED
@@ -609,11 +609,6 @@
609
609
  "description": "The roll which is edited in this edition.",
610
610
  "ontology": "lrmoo:R3i realises"
611
611
  },
612
- "tempoAdjustment": {
613
- "$ref": "#/definitions/ObjectAssumption%3CRollTempo%3E",
614
- "description": "An optional tempo adjustment for playback of the roll, annotatable with a belief about its correctness.",
615
- "ontology": "reo:tempo"
616
- },
617
612
  "title": {
618
613
  "description": "The title of the edition.",
619
614
  "examples": [
@@ -2815,101 +2810,6 @@
2815
2810
  ],
2816
2811
  "type": "object"
2817
2812
  },
2818
- "ObjectAssumption<RollTempo>": {
2819
- "anyOf": [
2820
- {
2821
- "properties": {
2822
- "@annotation": {
2823
- "description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
2824
- "properties": {
2825
- "belief": {
2826
- "$ref": "#/definitions/Belief",
2827
- "description": "The belief held about the annotated statement.",
2828
- "ontology": "crminf:J4i is subject of"
2829
- },
2830
- "@id": {
2831
- "description": "A unique identifier for this object.",
2832
- "type": "string"
2833
- }
2834
- },
2835
- "required": [
2836
- "belief",
2837
- "@id"
2838
- ],
2839
- "type": "object"
2840
- },
2841
- "endsWith": {
2842
- "$ref": "#/definitions/Quantity%3C%22ft%2Fmin%22%3E",
2843
- "description": "The tempo at the end of the roll.",
2844
- "ontology": "reo:to"
2845
- },
2846
- "startsWith": {
2847
- "$ref": "#/definitions/Quantity%3C%22ft%2Fmin%22%3E",
2848
- "description": "The tempo at the beginning of the roll.",
2849
- "ontology": "reo:from"
2850
- },
2851
- "unit": {
2852
- "const": "ft/min",
2853
- "description": "The unit of the tempo measurement.",
2854
- "type": "string",
2855
- "ontology": "crm:P91 has unit"
2856
- }
2857
- },
2858
- "required": [
2859
- "endsWith",
2860
- "startsWith",
2861
- "unit"
2862
- ],
2863
- "type": "object"
2864
- },
2865
- {
2866
- "properties": {
2867
- "@annotation": {
2868
- "description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
2869
- "properties": {
2870
- "belief": {
2871
- "$ref": "#/definitions/Belief",
2872
- "description": "The belief held about the annotated statement.",
2873
- "ontology": "crminf:J4i is subject of"
2874
- },
2875
- "@id": {
2876
- "description": "A unique identifier for this object.",
2877
- "type": "string"
2878
- }
2879
- },
2880
- "required": [
2881
- "belief",
2882
- "@id"
2883
- ],
2884
- "type": "object"
2885
- },
2886
- "endsWith": {
2887
- "$ref": "#/definitions/Quantity%3C%22m%2Fmin%22%3E",
2888
- "description": "The tempo at the end of the roll.",
2889
- "ontology": "reo:to"
2890
- },
2891
- "startsWith": {
2892
- "$ref": "#/definitions/Quantity%3C%22m%2Fmin%22%3E",
2893
- "description": "The tempo at the beginning of the roll.",
2894
- "ontology": "reo:from"
2895
- },
2896
- "unit": {
2897
- "const": "m/min",
2898
- "description": "The unit of the tempo measurement.",
2899
- "type": "string",
2900
- "ontology": "crm:P91 has unit"
2901
- }
2902
- },
2903
- "required": [
2904
- "endsWith",
2905
- "startsWith",
2906
- "unit"
2907
- ],
2908
- "type": "object"
2909
- }
2910
- ],
2911
- "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."
2912
- },
2913
2813
  "ObjectAssumption<Transcription>": {
2914
2814
  "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.",
2915
2815
  "properties": {
@@ -3165,7 +3065,7 @@
3165
3065
  },
3166
3066
  "speed": {
3167
3067
  "$ref": "#/definitions/ObjectAssumption%3CPaperSpeed%3E",
3168
- "description": "The paper speed the copy was cut for. A copy cut from the same master for another speed comes out longer or shorter than the roll it is aligned with by the ratio of the speeds. The alignment measures that ratio far more closely than a label states a speed, so the speed is held against what the alignments give (`alignmentProblems`) rather than used to find it.",
3068
+ "description": "The paper speed the copy was cut for, as the speed at the beginning of the roll: a tempo marking states it so, and the take-up spool accelerates from there. A performance of a version the copy bears witness to starts at it (`paperSpeedOf`). A copy cut from the same master for another speed comes out longer or shorter than the roll it is aligned with by the ratio of the speeds. The alignment measures that ratio far more closely than a label states a speed, so the speed is held against what the alignments give (`alignmentProblems`) rather than used to find it.",
3169
3069
  "ontology": "reo:paperSpeed"
3170
3070
  },
3171
3071
  "system": {
@@ -150,13 +150,6 @@
150
150
  "created": "lrmoo:R17_created",
151
151
  "copies": "reo:witness",
152
152
  "versions": "lrmoo:R75_incorporates",
153
- "tempoAdjustment": {
154
- "@id": "reo:tempo",
155
- "@context": {
156
- "startsWith": "reo:from",
157
- "endsWith": "reo:to"
158
- }
159
- },
160
153
  "name": "rdfs:label",
161
154
  "sameAs": {
162
155
  "@id": "owl:sameAs",
@@ -1,32 +1,7 @@
1
1
  import { HoleChain } from "../model/Feature.js";
2
2
  import { Expression, Note } from "../model/Symbol.js";
3
3
  import { TrackerBar } from "./TrackerBar.js";
4
- import { Millimeters, Quantity, Seconds } from "../model/Quantity.js";
5
- export type SpeedUnit = 'ft/min' | 'm/min';
6
- /**
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.
10
- * @see crm:E54 Dimension
11
- */
12
- export interface RollTempoIn<U extends SpeedUnit> {
13
- /**
14
- * The tempo at the beginning of the roll.
15
- * @see reo:from
16
- */
17
- startsWith: Quantity<U>;
18
- /**
19
- * The tempo at the end of the roll.
20
- * @see reo:to
21
- */
22
- endsWith: Quantity<U>;
23
- /**
24
- * The unit of the tempo measurement.
25
- * @see crm:P91 has unit
26
- */
27
- unit: U;
28
- }
29
- export type RollTempo = RollTempoIn<'ft/min'> | RollTempoIn<'m/min'>;
4
+ import { MetersPerMinute, Millimeters, Seconds } from "../model/Quantity.js";
30
5
  /**
31
6
  * A note or expression of a version with the dimensions of its carriers
32
7
  * averaged in and the editorial assumptions applied, which is all a
@@ -99,8 +74,26 @@ export type EmulatedCurve = DynamicsCurve | PedalCurve;
99
74
  export type RollProperties = {
100
75
  /** Diameter of the punches, where the copies record it. */
101
76
  punchDiameter?: Millimeters;
102
- /** The tempo the edition adjusts the roll to, where it states one. */
103
- tempo?: RollTempo;
77
+ /**
78
+ * The speed the version's paper starts at, where the copies that bear
79
+ * witness to it state the speed they were cut for (`paperSpeedOf`), or
80
+ * failing them those of the version it derives from on the same
81
+ * system, whose paper it keeps. A stated speed is the speed at the
82
+ * beginning of the roll, as a tempo marking gives it, and the spool
83
+ * accelerates from there.
84
+ */
85
+ paperSpeed?: MetersPerMinute;
86
+ /**
87
+ * Where the version is a re-cut for another system: how long its paper
88
+ * runs for a length of the paper of the version it derives from, and
89
+ * the speed stated for that version, if any. A system with no spool of
90
+ * its own times such a version by these, so that it sounds at the
91
+ * tempo of the roll it was re-cut from.
92
+ */
93
+ recutFrom?: {
94
+ readonly lengthRatio: number;
95
+ readonly paperSpeed?: MetersPerMinute;
96
+ };
104
97
  /**
105
98
  * Place on the edition's shared axis × this = millimetres of the version's
106
99
  * own paper, unstretched. About 0.775 for a green issue of a red
@@ -1,7 +1,7 @@
1
1
  import { Grid, Half, Model, ModelInput, Parameters, PedalInput, PedalMode, PedalTravel, PortGeometry, Spool } from "welte-mignon-emulator";
2
2
  import { Expression } from "../model/Symbol.js";
3
3
  import { NegotiatedEvent, Performance, RollProperties } from "./ReproducingSystem.js";
4
- import { Millimeters, Seconds, Track } from "../model/Quantity.js";
4
+ import { MetersPerMinute, Millimeters, Seconds, Track } from "../model/Quantity.js";
5
5
  import { type VelocityMap } from "./velocity.js";
6
6
  /**
7
7
  * What the Welte systems share downstream of the relay. Hagmann has the
@@ -15,6 +15,9 @@ export type WelteOptions = {
15
15
  /**
16
16
  * The take-up spool, which sets the time axis: it is held at a constant
17
17
  * rate of revolution, so the paper runs faster as the spool fills.
18
+ * Where a speed is stated for the version's copies, or a re-cut is timed
19
+ * by the roll it was made from, the spool is started at that speed and
20
+ * only its geometry is taken from here; see `spoolFor`.
18
21
  */
19
22
  spool: Spool;
20
23
  /** Constants of the nuancing mechanism, one set for each half of the keyboard. */
@@ -79,6 +82,21 @@ export type Paper = {
79
82
  };
80
83
  /** When the spool brings a place on the roll to the tracker bar. */
81
84
  export declare const secondsAt: (spool: Spool, place: Millimeters) => Seconds;
85
+ /** The speed the spool starts the paper at. */
86
+ export declare const startingSpeedOf: (spool: Spool) => MetersPerMinute;
87
+ /**
88
+ * The spool a version runs on.
89
+ *
90
+ * A speed stated for the version's copies is where it starts, as a tempo
91
+ * marking is the speed at the beginning of the roll. Failing that, a system
92
+ * whose spool is a reading of its own instruments runs on it as it stands:
93
+ * the red and the green spool are both calibrated at Welte's Tempo 70. A
94
+ * system with no spool of its own, as the Licensee has none, times a re-cut
95
+ * by the roll it was re-cut from, at that roll's stated speed or else the
96
+ * spool's own, shortened by the length ratio, so that it sounds at the tempo
97
+ * of the original. Where none of this applies the spool stands as given.
98
+ */
99
+ export declare const spoolFor: (spool: Spool, roll: RollProperties, ownSpool: boolean) => Spool;
82
100
  /** A perforation as the emulator reads it: the control it operates and the rows it spans. */
83
101
  export type WeltePunch = {
84
102
  readonly half: Half;
@@ -101,6 +119,12 @@ export type Mechanism<P extends WeltePunch> = {
101
119
  readonly portsOf: (grid: Grid, punches: readonly P[], geometry: PortGeometry) => Ports;
102
120
  readonly model: Model;
103
121
  readonly runPedals: (input: PedalInput, params: Parameters) => PedalTravel;
122
+ /**
123
+ * Whether the spool in the options is a reading of this system's own
124
+ * instruments. Where it is not, a re-cut is timed by the roll it was
125
+ * re-cut from (`spoolFor`).
126
+ */
127
+ readonly ownSpool: boolean;
104
128
  /** The name of the instrument the curves say they came from. */
105
129
  readonly instrument: string;
106
130
  };
@@ -111,10 +135,7 @@ export type WeltePerformance = Performance & {
111
135
  };
112
136
  /**
113
137
  * Plays the events on a Welte mechanism: notes at the velocity of their half's
114
- * bellows, and the two pedals as the travel of theirs.
115
- *
116
- * The edition's tempo adjustment is left aside: it is stated as a paper
117
- * speed, and what the spool holds constant is its rate of revolution, so
118
- * the two are not the same quantity. The spool in the options sets the speed.
138
+ * bellows, and the two pedals as the travel of theirs, on the spool
139
+ * `spoolFor` gives the version.
119
140
  */
120
141
  export declare const performWelte: <P extends WeltePunch>(events: readonly NegotiatedEvent[], options: WelteOptions, roll: RollProperties, mechanism: Mechanism<P>) => WeltePerformance;
@@ -1,5 +1,5 @@
1
- import { clamp, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, pedalBrushing, pedalDefaults, ROWS_PER_MM, travelBetweenRails, } from "welte-mignon-emulator";
2
- import { inCentimeters, mm, seconds } from "../model/Quantity.js";
1
+ import { clamp, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, paperSpeed, pedalBrushing, pedalDefaults, ROWS_PER_MM, travelBetweenRails, } from "welte-mignon-emulator";
2
+ import { inCentimeters, metersPerMinute, mm, seconds } from "../model/Quantity.js";
3
3
  import { partitionPoint } from "../shared/sorted.js";
4
4
  import { velocityOf } from "./velocity.js";
5
5
  /** Whether two sets of constants are the same, value for value. */
@@ -33,6 +33,30 @@ const paperOf = (toOwnPaper) => ({
33
33
  });
34
34
  /** When the spool brings a place on the roll to the tracker bar. */
35
35
  export const secondsAt = (spool, place) => seconds(paperSeconds(spool, inCentimeters(place)));
36
+ /** The speed the spool starts the paper at. */
37
+ export const startingSpeedOf = (spool) => metersPerMinute(paperSpeed(spool, 0) * 60 / 100);
38
+ /** The spool turned so that it starts the paper at the speed, its geometry and acceleration kept. */
39
+ const startingAt = (spool, speed) => ({ ...spool, revolutionSeconds: spool.circumferenceCm / (speed * 100 / 60) });
40
+ /**
41
+ * The spool a version runs on.
42
+ *
43
+ * A speed stated for the version's copies is where it starts, as a tempo
44
+ * marking is the speed at the beginning of the roll. Failing that, a system
45
+ * whose spool is a reading of its own instruments runs on it as it stands:
46
+ * the red and the green spool are both calibrated at Welte's Tempo 70. A
47
+ * system with no spool of its own, as the Licensee has none, times a re-cut
48
+ * by the roll it was re-cut from, at that roll's stated speed or else the
49
+ * spool's own, shortened by the length ratio, so that it sounds at the tempo
50
+ * of the original. Where none of this applies the spool stands as given.
51
+ */
52
+ export const spoolFor = (spool, roll, ownSpool) => {
53
+ if (roll.paperSpeed !== undefined)
54
+ return startingAt(spool, roll.paperSpeed);
55
+ if (ownSpool || !roll.recutFrom)
56
+ return spool;
57
+ const { lengthRatio, paperSpeed: original = startingSpeedOf(spool) } = roll.recutFrom;
58
+ return startingAt(spool, metersPerMinute(original * lengthRatio));
59
+ };
36
60
  const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
37
61
  /**
38
62
  * One sample per row of the scan the constants were fitted on, from the
@@ -63,12 +87,12 @@ const nuanceCurves = (grid, ports, samples, options, model, instrument) => {
63
87
  };
64
88
  return { bass: curveOf('bass'), treble: curveOf('treble') };
65
89
  };
66
- const performNotes = (events, grid, nuance, options, paper) => events
90
+ const performNotes = (events, grid, nuance, options, paper, spool) => events
67
91
  .filter(isNote)
68
92
  .flatMap((note) => {
69
93
  const curve = nuance[halfOf(note, options.division)];
70
94
  const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
71
- const at = (place) => secondsAt(options.spool, paper.paperOfPlace(place));
95
+ const at = (place) => secondsAt(spool, paper.paperOfPlace(place));
72
96
  return [
73
97
  { type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: at(note.horizontal.from) },
74
98
  { type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: at(note.horizontal.to) }
@@ -95,11 +119,8 @@ const performPedal = (type, curve, grid, readings, mode) => {
95
119
  };
96
120
  /**
97
121
  * Plays the events on a Welte mechanism: notes at the velocity of their half's
98
- * bellows, and the two pedals as the travel of theirs.
99
- *
100
- * The edition's tempo adjustment is left aside: it is stated as a paper
101
- * speed, and what the spool holds constant is its rate of revolution, so
102
- * the two are not the same quantity. The spool in the options sets the speed.
122
+ * bellows, and the two pedals as the travel of theirs, on the spool
123
+ * `spoolFor` gives the version.
103
124
  */
104
125
  export const performWelte = (events, options, roll, mechanism) => {
105
126
  const paper = paperOf(roll.toOwnPaper ?? 1);
@@ -112,7 +133,8 @@ export const performWelte = (events, options, roll, mechanism) => {
112
133
  });
113
134
  return punch ? [{ event, punch }] : [];
114
135
  });
115
- const grid = gridOver(events, options.spool, paper);
136
+ const spool = spoolFor(options.spool, roll, mechanism.ownSpool);
137
+ const grid = gridOver(events, spool, paper);
116
138
  const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
117
139
  const ports = mechanism.portsOf(grid, readings.map(reading => reading.punch), geometry);
118
140
  const samples = {
@@ -128,7 +150,7 @@ export const performWelte = (events, options, roll, mechanism) => {
128
150
  grid,
129
151
  ports,
130
152
  events: [
131
- ...performNotes(events, grid, nuance, options, paper),
153
+ ...performNotes(events, grid, nuance, options, paper, spool),
132
154
  ...performPedal('damper', damper, grid, readingsOf('sustainPedal'), options.pedalMode),
133
155
  ...performPedal('hammerRail', hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
134
156
  ],
@@ -27,9 +27,13 @@ export declare const instrumentNames: readonly WelteLicenseeInstrumentName[];
27
27
  * Welte's own spool is carried over with the constants. Phillips (p. 181) says
28
28
  * Licensee rolls "play at a range of paper speeds", so there is no one figure
29
29
  * to put here and the red geometry is a placeholder rather than a reading.
30
- * Where an edition states a tempo of its own it is the better authority, and
31
- * the spool is better varied than trusted: it scales every conductance by k
32
- * and every time constant by 1/k, and touches nothing dimensionless.
30
+ * It is therefore never played as it stands where anything better is known:
31
+ * a speed stated for the version's copies, as a Licensee label states its
32
+ * tempo, sets where it starts, and failing that a re-cut runs at the speed of
33
+ * the roll it was re-cut from times the length ratio, so that it sounds at
34
+ * that roll's tempo (`spoolFor`). Either way only its geometry is used, and
35
+ * that is better varied than trusted: it scales every conductance by k and
36
+ * every time constant by 1/k, and touches nothing dimensionless.
33
37
  */
34
38
  export declare const defaultWelteLicenseeOptions: WelteLicenseeOptions;
35
39
  /**
@@ -41,6 +45,8 @@ export declare const defaultWelteLicenseeOptions: WelteLicenseeOptions;
41
45
  * What it does not have is an instrument of its own. The constants are the
42
46
  * T-100's, the spool is the T-100's, and neither has ever been checked against
43
47
  * a Licensee. `instruments` therefore offers a single `unfitted` arm, and every
44
- * curve produced here names itself as such.
48
+ * curve produced here names itself as such. The spool sets no tempo of its
49
+ * own: a version runs at the speed stated for its copies, or at that of the
50
+ * roll it was re-cut from times the length ratio.
45
51
  */
46
52
  export declare const welteLicenseeSystem: ReproducingSystem<WelteLicenseeOptions>;
@@ -23,9 +23,13 @@ export const instrumentNames = [
23
23
  * Welte's own spool is carried over with the constants. Phillips (p. 181) says
24
24
  * Licensee rolls "play at a range of paper speeds", so there is no one figure
25
25
  * to put here and the red geometry is a placeholder rather than a reading.
26
- * Where an edition states a tempo of its own it is the better authority, and
27
- * the spool is better varied than trusted: it scales every conductance by k
28
- * and every time constant by 1/k, and touches nothing dimensionless.
26
+ * It is therefore never played as it stands where anything better is known:
27
+ * a speed stated for the version's copies, as a Licensee label states its
28
+ * tempo, sets where it starts, and failing that a re-cut runs at the speed of
29
+ * the roll it was re-cut from times the length ratio, so that it sounds at
30
+ * that roll's tempo (`spoolFor`). Either way only its geometry is used, and
31
+ * that is better varied than trusted: it scales every conductance by k and
32
+ * every time constant by 1/k, and touches nothing dimensionless.
29
33
  */
30
34
  export const defaultWelteLicenseeOptions = {
31
35
  ...defaultWelteT100Options,
@@ -50,11 +54,13 @@ const licenseeInstrument = (nuance) => instrumentNameOf(nuance) === 'consensus'
50
54
  * What it does not have is an instrument of its own. The constants are the
51
55
  * T-100's, the spool is the T-100's, and neither has ever been checked against
52
56
  * a Licensee. `instruments` therefore offers a single `unfitted` arm, and every
53
- * curve produced here names itself as such.
57
+ * curve produced here names itself as such. The spool sets no tempo of its
58
+ * own: a version runs at the speed stated for its copies, or at that of the
59
+ * roll it was re-cut from times the length ratio.
54
60
  */
55
61
  export const welteLicenseeSystem = {
56
62
  name: 'Welte-Mignon (Licensee)',
57
63
  trackerBar: welteLicensee,
58
64
  defaultOptions: defaultWelteLicenseeOptions,
59
- perform: performAs(licenseeInstrument)
65
+ perform: performAs(licenseeInstrument, false)
60
66
  };
@@ -24,12 +24,13 @@ export type WelteT100Options = WelteOptions;
24
24
  export declare const defaultWelteT100Options: WelteT100Options;
25
25
  /**
26
26
  * The mechanism, given a way of naming the instrument on the curves it
27
- * produces. The Licensee reads the same commands and is played by the same
28
- * valves, so it shares this; what it may not share is the name, since a
29
- * Licensee playback runs on constants fitted to Freiburg instruments and the
30
- * curve has to say so.
27
+ * produces and whether the spool it is given is that instrument's own. The
28
+ * Licensee reads the same commands and is played by the same valves, so it
29
+ * shares this; what it may not share is the name, since a Licensee playback
30
+ * runs on constants fitted to Freiburg instruments and the curve has to say
31
+ * so, nor the spool, which is the red one it carries over.
31
32
  */
32
- export declare const performAs: (instrumentOf: (nuance: Record<Half, Parameters>) => string) => (events: readonly NegotiatedEvent[], options: WelteT100Options, roll: RollProperties) => Performance;
33
+ export declare const performAs: (instrumentOf: (nuance: Record<Half, Parameters>) => string, ownSpool?: boolean) => (events: readonly NegotiatedEvent[], options: WelteT100Options, roll: RollProperties) => Performance;
33
34
  /**
34
35
  * The red Welte, as welte-mignon-emulator models it: the take-up spool sets
35
36
  * the time axis, the Nuancierbälge fill through their conduits and are
@@ -55,17 +55,19 @@ const punchOf = (event, rows) => {
55
55
  };
56
56
  /**
57
57
  * The mechanism, given a way of naming the instrument on the curves it
58
- * produces. The Licensee reads the same commands and is played by the same
59
- * valves, so it shares this; what it may not share is the name, since a
60
- * Licensee playback runs on constants fitted to Freiburg instruments and the
61
- * curve has to say so.
58
+ * produces and whether the spool it is given is that instrument's own. The
59
+ * Licensee reads the same commands and is played by the same valves, so it
60
+ * shares this; what it may not share is the name, since a Licensee playback
61
+ * runs on constants fitted to Freiburg instruments and the curve has to say
62
+ * so, nor the spool, which is the red one it carries over.
62
63
  */
63
- export const performAs = (instrumentOf) => (events, options, roll) => {
64
+ export const performAs = (instrumentOf, ownSpool = true) => (events, options, roll) => {
64
65
  const { events: performed, curves } = performWelte(events, options, roll, {
65
66
  punchOf,
66
67
  portsOf: aperturePorts,
67
68
  model: pneumaticModel,
68
69
  runPedals,
70
+ ownSpool,
69
71
  instrument: instrumentOf(options.nuance)
70
72
  });
71
73
  return { events: performed, curves };
@@ -110,6 +110,7 @@ const perform = (events, options, roll) => {
110
110
  portsOf: (grid, punches, geometry) => aperturePorts(grid, punches, geometry, gap),
111
111
  model: pneumaticT98Model,
112
112
  runPedals,
113
+ ownSpool: true,
113
114
  instrument: `Welte-Mignon T-98, ${labelOf(options.instrument)}`
114
115
  });
115
116
  const stops = rewindRow(grid, ports, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linked-rolls",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Digital editions of piano rolls: import, collation, editorial assumptions, JSON-LD export, and emulation through a reproducing system",
5
5
  "license": "MIT",
6
6
  "repository": {