linked-rolls 0.65.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.
@@ -89,6 +89,10 @@
89
89
  "used": {
90
90
  "@id": "crm:P16_used_specific_object",
91
91
  "@type": "@id"
92
+ },
93
+ "software": {
94
+ "@id": "crmdig:L23_used_software_or_firmware",
95
+ "@context": { "version": "owl:versionInfo" }
92
96
  }
93
97
  }
94
98
  },
@@ -99,6 +103,13 @@
99
103
  },
100
104
  "creation": "lrmoo:R17i_was_created_by",
101
105
  "procedure": "crm:P33_used_specific_technique",
106
+ "lengthRatio": {
107
+ "@id": "reo:lengthRatio",
108
+ "@context": {
109
+ "value": "crm:P90_has_value",
110
+ "uncertainty": "reo:uncertainty"
111
+ }
112
+ },
102
113
  "editors": "crm:P14_carried_out_by",
103
114
  "publisher": "crm:P14_carried_out_by",
104
115
  "publicationDate": {
@@ -139,13 +150,6 @@
139
150
  "created": "lrmoo:R17_created",
140
151
  "copies": "reo:witness",
141
152
  "versions": "lrmoo:R75_incorporates",
142
- "tempoAdjustment": {
143
- "@id": "reo:tempo",
144
- "@context": {
145
- "startsWith": "reo:from",
146
- "endsWith": "reo:to"
147
- }
148
- },
149
153
  "name": "rdfs:label",
150
154
  "sameAs": {
151
155
  "@id": "owl:sameAs",
@@ -176,14 +180,7 @@
176
180
  "height": "reo:height",
177
181
  "holeSeparation": "reo:holeSeparation",
178
182
  "scanResolution": "reo:scanResolution",
179
- "value": "crm:P90_has_value",
180
- "measuredBy": "crmdig:L23_used_software_or_firmware",
181
- "software": "rdfs:label",
182
- "version": "owl:versionInfo",
183
- "date": {
184
- "@id": "dcterms:date",
185
- "@type": "xsd:date"
186
- }
183
+ "value": "crm:P90_has_value"
187
184
  }
188
185
  },
189
186
  "production": {
@@ -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. */
@@ -67,15 +70,33 @@ export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefi
67
70
  *
68
71
  * Rows are rows of the version's own paper, so the spool is asked in
69
72
  * `paperOfRow` and never in `placeOfRow`: one is the paper that passes the
70
- * tracker bar, the other is the coordinate the edition states.
73
+ * tracker bar, the other is the coordinate the edition states. A place is
74
+ * put to the spool through `paperOfPlace` for the same reason, so that a
75
+ * note sounds on the clock its pedal and bellows run on.
71
76
  */
72
77
  export type Paper = {
73
78
  readonly rowOf: (place: Millimeters) => number;
74
79
  readonly placeOfRow: (row: number) => Millimeters;
75
80
  readonly paperOfRow: (row: number) => Millimeters;
81
+ readonly paperOfPlace: (place: Millimeters) => Millimeters;
76
82
  };
77
83
  /** When the spool brings a place on the roll to the tracker bar. */
78
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;
79
100
  /** A perforation as the emulator reads it: the control it operates and the rows it spans. */
80
101
  export type WeltePunch = {
81
102
  readonly half: Half;
@@ -98,6 +119,12 @@ export type Mechanism<P extends WeltePunch> = {
98
119
  readonly portsOf: (grid: Grid, punches: readonly P[], geometry: PortGeometry) => Ports;
99
120
  readonly model: Model;
100
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;
101
128
  /** The name of the instrument the curves say they came from. */
102
129
  readonly instrument: string;
103
130
  };
@@ -108,10 +135,7 @@ export type WeltePerformance = Performance & {
108
135
  };
109
136
  /**
110
137
  * Plays the events on a Welte mechanism: notes at the velocity of their half's
111
- * bellows, and the two pedals as the travel of theirs.
112
- *
113
- * The edition's tempo adjustment is left aside: it is stated as a paper
114
- * speed, and what the spool holds constant is its rate of revolution, so
115
- * 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.
116
140
  */
117
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. */
@@ -28,10 +28,35 @@ const isExpression = (event) => event.type === 'expression';
28
28
  const paperOf = (toOwnPaper) => ({
29
29
  rowOf: place => place * toOwnPaper * ROWS_PER_MM,
30
30
  placeOfRow: row => mm(row / (toOwnPaper * ROWS_PER_MM)),
31
- paperOfRow: row => mm(row / ROWS_PER_MM)
31
+ paperOfRow: row => mm(row / ROWS_PER_MM),
32
+ paperOfPlace: place => mm(place * toOwnPaper)
32
33
  });
33
34
  /** When the spool brings a place on the roll to the tracker bar. */
34
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
+ };
35
60
  const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
36
61
  /**
37
62
  * One sample per row of the scan the constants were fitted on, from the
@@ -62,14 +87,15 @@ const nuanceCurves = (grid, ports, samples, options, model, instrument) => {
62
87
  };
63
88
  return { bass: curveOf('bass'), treble: curveOf('treble') };
64
89
  };
65
- const performNotes = (events, grid, nuance, options, paper) => events
90
+ const performNotes = (events, grid, nuance, options, paper, spool) => events
66
91
  .filter(isNote)
67
92
  .flatMap((note) => {
68
93
  const curve = nuance[halfOf(note, options.division)];
69
94
  const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
95
+ const at = (place) => secondsAt(spool, paper.paperOfPlace(place));
70
96
  return [
71
- { type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
72
- { type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
97
+ { type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: at(note.horizontal.from) },
98
+ { type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: at(note.horizontal.to) }
73
99
  ];
74
100
  });
75
101
  /**
@@ -93,11 +119,8 @@ const performPedal = (type, curve, grid, readings, mode) => {
93
119
  };
94
120
  /**
95
121
  * Plays the events on a Welte mechanism: notes at the velocity of their half's
96
- * bellows, and the two pedals as the travel of theirs.
97
- *
98
- * The edition's tempo adjustment is left aside: it is stated as a paper
99
- * speed, and what the spool holds constant is its rate of revolution, so
100
- * 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.
101
124
  */
102
125
  export const performWelte = (events, options, roll, mechanism) => {
103
126
  const paper = paperOf(roll.toOwnPaper ?? 1);
@@ -110,7 +133,8 @@ export const performWelte = (events, options, roll, mechanism) => {
110
133
  });
111
134
  return punch ? [{ event, punch }] : [];
112
135
  });
113
- const grid = gridOver(events, options.spool, paper);
136
+ const spool = spoolFor(options.spool, roll, mechanism.ownSpool);
137
+ const grid = gridOver(events, spool, paper);
114
138
  const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
115
139
  const ports = mechanism.portsOf(grid, readings.map(reading => reading.punch), geometry);
116
140
  const samples = {
@@ -126,7 +150,7 @@ export const performWelte = (events, options, roll, mechanism) => {
126
150
  grid,
127
151
  ports,
128
152
  events: [
129
- ...performNotes(events, grid, nuance, options, paper),
153
+ ...performNotes(events, grid, nuance, options, paper, spool),
130
154
  ...performPedal('damper', damper, grid, readingsOf('sustainPedal'), options.pedalMode),
131
155
  ...performPedal('hammerRail', hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
132
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.65.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": {