linked-rolls 0.57.0 → 0.59.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 (130) hide show
  1. package/README.md +54 -367
  2. package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
  3. package/lib/{constraints.js → analysis/constraints.js} +8 -7
  4. package/lib/analysis/ownPaper.d.ts +26 -0
  5. package/lib/analysis/ownPaper.js +41 -0
  6. package/lib/{reservations.d.ts → analysis/reservations.d.ts} +3 -3
  7. package/lib/{reservations.js → analysis/reservations.js} +3 -3
  8. package/lib/{sigla.d.ts → analysis/sigla.d.ts} +2 -2
  9. package/lib/{sigla.js → analysis/sigla.js} +4 -4
  10. package/lib/{witnesses.d.ts → analysis/witnesses.d.ts} +2 -2
  11. package/lib/{witnesses.js → analysis/witnesses.js} +2 -2
  12. package/lib/{Collation.d.ts → collation/Collation.d.ts} +4 -4
  13. package/lib/{Collation.js → collation/Collation.js} +4 -4
  14. package/lib/{alignment.d.ts → collation/alignment.d.ts} +4 -4
  15. package/lib/{alignment.js → collation/alignment.js} +10 -16
  16. package/lib/{scatter.d.ts → collation/scatter.d.ts} +4 -4
  17. package/lib/{scatter.js → collation/scatter.js} +3 -3
  18. package/lib/{statistics.d.ts → collation/statistics.d.ts} +1 -1
  19. package/lib/{statistics.js → collation/statistics.js} +2 -2
  20. package/lib/{substitution.d.ts → collation/substitution.d.ts} +9 -24
  21. package/lib/{substitution.js → collation/substitution.js} +13 -37
  22. package/lib/emulation/Emulation.d.ts +64 -0
  23. package/lib/emulation/Emulation.js +282 -0
  24. package/lib/emulation/negotiation.d.ts +14 -0
  25. package/lib/emulation/negotiation.js +20 -0
  26. package/lib/index.d.ts +43 -35
  27. package/lib/index.js +36 -35
  28. package/lib/{asJsonLd.d.ts → io/asJsonLd.d.ts} +1 -1
  29. package/lib/{asJsonLd.js → io/asJsonLd.js} +3 -3
  30. package/lib/{context.d.ts → io/context.d.ts} +4 -4
  31. package/lib/{context.js → io/context.js} +4 -4
  32. package/lib/{importJsonLd.d.ts → io/importJsonLd.d.ts} +1 -1
  33. package/lib/io/importJsonLd.js +128 -0
  34. package/lib/{migrate.js → io/migrate.js} +59 -55
  35. package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
  36. package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
  37. package/lib/{Edit.d.ts → model/Edit.d.ts} +1 -1
  38. package/lib/{Edition.d.ts → model/Edition.d.ts} +2 -2
  39. package/lib/{Feature.d.ts → model/Feature.d.ts} +1 -1
  40. package/lib/{FeatureSource.d.ts → model/FeatureSource.d.ts} +1 -1
  41. package/lib/{Perforator.d.ts → model/Perforator.d.ts} +1 -1
  42. package/lib/{RollCopy.d.ts → model/RollCopy.d.ts} +3 -10
  43. package/lib/{RollCopy.js → model/RollCopy.js} +3 -5
  44. package/lib/{Symbol.d.ts → model/Symbol.d.ts} +1 -1
  45. package/lib/{Version.d.ts → model/Version.d.ts} +10 -2
  46. package/lib/{Version.js → model/Version.js} +24 -1
  47. package/lib/{vocabulary.js → model/vocabulary.js} +2 -2
  48. package/lib/ops/beliefs.d.ts +9 -0
  49. package/lib/ops/beliefs.js +31 -0
  50. package/lib/ops/collation.d.ts +62 -0
  51. package/lib/ops/collation.js +228 -0
  52. package/lib/ops/commands.d.ts +9 -0
  53. package/lib/ops/commands.js +29 -0
  54. package/lib/ops/copies.d.ts +74 -0
  55. package/lib/ops/copies.js +135 -0
  56. package/lib/ops/draft.d.ts +59 -0
  57. package/lib/ops/draft.js +81 -0
  58. package/lib/ops/edits.d.ts +10 -0
  59. package/lib/ops/edits.js +82 -0
  60. package/lib/ops/features.d.ts +84 -0
  61. package/lib/ops/features.js +296 -0
  62. package/lib/ops/forget.d.ts +31 -0
  63. package/lib/ops/forget.js +110 -0
  64. package/lib/ops/immutable.d.ts +16 -0
  65. package/lib/ops/immutable.js +46 -0
  66. package/lib/ops/index.d.ts +13 -0
  67. package/lib/ops/index.js +7 -0
  68. package/lib/ops/versions.d.ts +33 -0
  69. package/lib/ops/versions.js +95 -0
  70. package/lib/readers/AtonParser.js +5 -7
  71. package/lib/readers/phillipsEroll.d.ts +3 -3
  72. package/lib/readers/phillipsEroll.js +4 -5
  73. package/lib/readers/spencerBar.d.ts +2 -2
  74. package/lib/readers/spencerBar.js +2 -3
  75. package/lib/readers/stanfordAton.d.ts +3 -3
  76. package/lib/readers/stanfordAton.js +2 -3
  77. package/lib/schema.json +0 -13
  78. package/lib/{ReproducingSystem.d.ts → systems/ReproducingSystem.d.ts} +3 -3
  79. package/lib/{TrackerBar.d.ts → systems/TrackerBar.d.ts} +48 -8
  80. package/lib/{TrackerBar.js → systems/TrackerBar.js} +11 -2
  81. package/lib/systems/index.d.ts +8 -2
  82. package/lib/systems/index.js +7 -1
  83. package/lib/systems/welte.d.ts +100 -0
  84. package/lib/systems/welte.js +119 -0
  85. package/lib/systems/welteLicensee/bar.d.ts +1 -1
  86. package/lib/systems/welteLicensee/bar.js +5 -2
  87. package/lib/systems/welteLicensee/system.d.ts +1 -1
  88. package/lib/systems/welteLicensee/system.js +1 -1
  89. package/lib/systems/welteT100/bar.d.ts +27 -1
  90. package/lib/systems/welteT100/bar.js +44 -2
  91. package/lib/systems/welteT100/system.d.ts +6 -39
  92. package/lib/systems/welteT100/system.js +19 -128
  93. package/lib/systems/welteT98/bar.d.ts +1 -1
  94. package/lib/systems/welteT98/bar.js +18 -2
  95. package/lib/systems/welteT98/system.d.ts +20 -38
  96. package/lib/systems/welteT98/system.js +20 -121
  97. package/lib/validate.d.ts +1 -1
  98. package/lib/{EditionView.d.ts → view/EditionView.d.ts} +17 -41
  99. package/lib/{EditionView.js → view/EditionView.js} +26 -75
  100. package/package.json +7 -2
  101. package/lib/Emulation.d.ts +0 -36
  102. package/lib/Emulation.js +0 -250
  103. package/lib/editionOps.d.ts +0 -260
  104. package/lib/editionOps.js +0 -1079
  105. package/lib/importJsonLd.js +0 -57
  106. /package/lib/{migrate.d.ts → io/migrate.d.ts} +0 -0
  107. /package/lib/{Agent.js → model/Agent.js} +0 -0
  108. /package/lib/{Assumption.js → model/Assumption.js} +0 -0
  109. /package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +0 -0
  110. /package/lib/{ConditionState.js → model/ConditionState.js} +0 -0
  111. /package/lib/{Edit.js → model/Edit.js} +0 -0
  112. /package/lib/{Edition.js → model/Edition.js} +0 -0
  113. /package/lib/{Feature.js → model/Feature.js} +0 -0
  114. /package/lib/{FeatureSource.js → model/FeatureSource.js} +0 -0
  115. /package/lib/{Perforator.js → model/Perforator.js} +0 -0
  116. /package/lib/{Quantity.d.ts → model/Quantity.d.ts} +0 -0
  117. /package/lib/{Quantity.js → model/Quantity.js} +0 -0
  118. /package/lib/{Symbol.js → model/Symbol.js} +0 -0
  119. /package/lib/{TrackCalibration.d.ts → model/TrackCalibration.d.ts} +0 -0
  120. /package/lib/{TrackCalibration.js → model/TrackCalibration.js} +0 -0
  121. /package/lib/{notes.d.ts → model/notes.d.ts} +0 -0
  122. /package/lib/{notes.js → model/notes.js} +0 -0
  123. /package/lib/{procedures.d.ts → model/procedures.d.ts} +0 -0
  124. /package/lib/{procedures.js → model/procedures.js} +0 -0
  125. /package/lib/{vocabulary.d.ts → model/vocabulary.d.ts} +0 -0
  126. /package/lib/{sorted.d.ts → shared/sorted.d.ts} +0 -0
  127. /package/lib/{sorted.js → shared/sorted.js} +0 -0
  128. /package/lib/{utils.d.ts → shared/utils.d.ts} +0 -0
  129. /package/lib/{utils.js → shared/utils.js} +0 -0
  130. /package/lib/{ReproducingSystem.js → systems/ReproducingSystem.js} +0 -0
@@ -70,13 +70,13 @@ export class AtonParser {
70
70
  // Ignore unassociated text.
71
71
  return;
72
72
  }
73
- else if (matches = line.match(/^@@[^@ ]/)) {
73
+ else if ((matches = line.match(/^@@[^@ ]/))) {
74
74
  // Control message.
75
75
  // End current property.
76
76
  this.cleanParameter(state);
77
77
  state.curkey = undefined;
78
78
  state.ocurkey = undefined;
79
- if (matches = line.match(/^@@(BEGIN|START)\s*:\s*(.*)\s*$/i)) {
79
+ if ((matches = line.match(/^@@(BEGIN|START)\s*:\s*(.*)\s*$/i))) {
80
80
  state.label = matches[2];
81
81
  //console.log(state.label)
82
82
  if (typeof state.curobj[state.label] === 'undefined') {
@@ -107,7 +107,7 @@ export class AtonParser {
107
107
  state.curobj = state.curobj[state.label][state.curobj[state.label].length - 1];
108
108
  }
109
109
  }
110
- else if (matches = line.match(/^@@(END|STOP)\s*:?\s*(.*)\s*$/i)) {
110
+ else if ((matches = line.match(/^@@(END|STOP)\s*:?\s*(.*)\s*$/i))) {
111
111
  // End an object, so go back to the parent.
112
112
  if (typeof state.curkey !== 'undefined') {
113
113
  // clean whitespace of last read property:
@@ -121,8 +121,6 @@ export class AtonParser {
121
121
  if (typeof state.node[state.node.length - 1].startline === 'undefined') {
122
122
  throw new Error('No start for ' + state.action + ' tag on line '
123
123
  + state.node[state.node.length - 1].startline + ': ' + line);
124
- state.output = {};
125
- // return v.output;
126
124
  }
127
125
  if (typeof state.labelend !== 'undefined') {
128
126
  // ensure that the v.label begin/end tags match
@@ -144,12 +142,12 @@ export class AtonParser {
144
142
  obj[x.label][x.index] : obj[x.label];
145
143
  }, state.output);
146
144
  }
147
- else if (matches = line.match(/^@@TYPE\s*:\s*([^:]+)\s*:\s*(.*)\s*$/i)) {
145
+ else if ((matches = line.match(/^@@TYPE\s*:\s*([^:]+)\s*:\s*(.*)\s*$/i))) {
148
146
  // Automatic property value conversion.
149
147
  state.typer[matches[1]] = matches[2];
150
148
  }
151
149
  }
152
- else if (matches = line.match(/^@([^\s:@][^:]*)\s*:\s*(.*)\s*$/)) {
150
+ else if ((matches = line.match(/^@([^\s:@][^:]*)\s*:\s*(.*)\s*$/))) {
153
151
  // New property
154
152
  state.newkey = matches[1];
155
153
  state.onewkey = state.newkey;
@@ -1,6 +1,6 @@
1
- import { RollCopy } from "../RollCopy.js";
2
- import { TrackerBar } from "../TrackerBar.js";
3
- import { Millimeters, Seconds } from "../Quantity.js";
1
+ import { RollCopy } from "../model/RollCopy.js";
2
+ import { TrackerBar } from "../systems/TrackerBar.js";
3
+ import { Millimeters, Seconds } from "../model/Quantity.js";
4
4
  /**
5
5
  * Peter Phillips's "e-roll" file, the unprocessed output of his
6
6
  * pneumatic roll reader (thesis pp. 182–196): the roll runs over a
@@ -1,11 +1,11 @@
1
1
  import { read } from "midifile-ts";
2
2
  import { v4 } from "uuid";
3
- import { assignObject } from "../Assumption.js";
4
- import { systemOf } from "../TrackerBar.js";
5
- import { shortenChains } from "../alignment.js";
3
+ import { assignObject } from "../model/Assumption.js";
4
+ import { systemOf } from "../systems/TrackerBar.js";
5
+ import { shortenChains } from "../collation/alignment.js";
6
6
  import { welteT100 } from "../systems/welteT100/bar.js";
7
7
  import { welteLicensee } from "../systems/welteLicensee/bar.js";
8
- import { inMetersPerMinute, mm, seconds, track } from "../Quantity.js";
8
+ import { inMetersPerMinute, mm, seconds, track } from "../model/Quantity.js";
9
9
  /**
10
10
  * Peter Phillips's "e-roll" file, the unprocessed output of his
11
11
  * pneumatic roll reader (thesis pp. 182–196): the roll runs over a
@@ -166,7 +166,6 @@ export function readFromPhillipsEroll(buffer, { system = welteT100, controlOffse
166
166
  const copy = {
167
167
  type: 'RollCopy',
168
168
  id: v4(),
169
- ops: [],
170
169
  conditions: [],
171
170
  measurements: {},
172
171
  production: { system: systemOf(system), produced: features },
@@ -1,5 +1,5 @@
1
- import { PaperSpeed, RollCopy } from "../RollCopy.js";
2
- import { TrackerBar } from "../TrackerBar.js";
1
+ import { PaperSpeed, RollCopy } from "../model/RollCopy.js";
2
+ import { TrackerBar } from "../systems/TrackerBar.js";
3
3
  /**
4
4
  * Rows of the image on an inch of paper. The player reads eight rows a
5
5
  * second per unit of roll tempo (tempo 80 comes with a sample rate of
@@ -1,7 +1,7 @@
1
1
  import { v4 } from "uuid";
2
- import { systemOf } from "../TrackerBar.js";
2
+ import { systemOf } from "../systems/TrackerBar.js";
3
3
  import { welteLicensee } from "../systems/welteLicensee/bar.js";
4
- import { feetPerMinute, inMillimeters, px, track } from "../Quantity.js";
4
+ import { feetPerMinute, inMillimeters, px, track } from "../model/Quantity.js";
5
5
  /**
6
6
  * Spencer Chase's e-roll file (`.bar`, "eRoll Tracker Bar Image") holds
7
7
  * a roll as a list of events: a distance in rows of the scanned image,
@@ -111,7 +111,6 @@ export function readFromSpencerBar(buffer, { rowsPerInch = SPENCER_ROWS_PER_INCH
111
111
  return {
112
112
  type: 'RollCopy',
113
113
  id: v4(),
114
- ops: [],
115
114
  conditions: [],
116
115
  measurements: {},
117
116
  production: { system: systemOf(system), produced: features },
@@ -1,6 +1,6 @@
1
- import { RollCopy } from "../RollCopy.js";
2
- import { TrackerBar } from "../TrackerBar.js";
3
- import { Track } from "../Quantity.js";
1
+ import { RollCopy } from "../model/RollCopy.js";
2
+ import { TrackerBar } from "../systems/TrackerBar.js";
3
+ import { Track } from "../model/Quantity.js";
4
4
  export interface StanfordAtonOptions {
5
5
  /**
6
6
  * Added to the scanning software's hole numbering to reach the
@@ -1,8 +1,8 @@
1
1
  import { v4 } from "uuid";
2
2
  import { AtonParser } from "./AtonParser.js";
3
- import { systemOf } from "../TrackerBar.js";
3
+ import { systemOf } from "../systems/TrackerBar.js";
4
4
  import { welteT100 } from "../systems/welteT100/bar.js";
5
- import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../Quantity.js";
5
+ import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../model/Quantity.js";
6
6
  /** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
7
7
  const readPx = (value) => px(parseFloat(value));
8
8
  /** The parser writes one record as an object and several as an array. */
@@ -155,7 +155,6 @@ export function readFromStanfordAton(atonString, { trackShift, system = welteT10
155
155
  return {
156
156
  type: 'RollCopy',
157
157
  id: v4(),
158
- ops: [],
159
158
  conditions: [],
160
159
  production: {
161
160
  system: systemOf(system),
package/lib/schema.json CHANGED
@@ -3023,18 +3023,6 @@
3023
3023
  "type": "array",
3024
3024
  "ontology": "crm:P31i was modified by"
3025
3025
  },
3026
- "ops": {
3027
- "description": "A list of operations that have been applied to this copy's features (e.g. 'shifted', 'stretched') to normalize measurements for comparison with other copies. Not exported to RDF.",
3028
- "items": {
3029
- "enum": [
3030
- "shifted",
3031
- "stretched",
3032
- "shortened"
3033
- ],
3034
- "type": "string"
3035
- },
3036
- "type": "array"
3037
- },
3038
3026
  "production": {
3039
3027
  "$ref": "#/definitions/ProductionEvent",
3040
3028
  "description": "The production event that created this roll copy.",
@@ -3071,7 +3059,6 @@
3071
3059
  "@id",
3072
3060
  "measurements",
3073
3061
  "modifications",
3074
- "ops",
3075
3062
  "@type"
3076
3063
  ],
3077
3064
  "type": "object",
@@ -1,7 +1,7 @@
1
- import { HoleChain } from "./Feature.js";
2
- import { Expression, Note } from "./Symbol.js";
1
+ import { HoleChain } from "../model/Feature.js";
2
+ import { Expression, Note } from "../model/Symbol.js";
3
3
  import { TrackerBar } from "./TrackerBar.js";
4
- import { Millimeters, Quantity, Seconds } from "./Quantity.js";
4
+ import { Millimeters, Quantity, Seconds } from "../model/Quantity.js";
5
5
  export type SpeedUnit = 'ft/min' | 'm/min';
6
6
  /**
7
7
  * The playback tempo of the roll as a paper speed, stated at the
@@ -1,6 +1,6 @@
1
- import type { Concept } from "./Agent.js";
2
- import { Expression, Note } from "./Symbol.js";
3
- import { Millimeters, SpeedMeasure, Track } from "./Quantity.js";
1
+ import type { Concept } from "../model/Agent.js";
2
+ import { Expression, Note } from "../model/Symbol.js";
3
+ import { Millimeters, SpeedMeasure, Track } from "../model/Quantity.js";
4
4
  /**
5
5
  * What a tracker bar position does: sound a note, or operate one of
6
6
  * the expression valves on the bass or the treble side.
@@ -15,6 +15,27 @@ export interface TrackArea {
15
15
  readonly from: Track;
16
16
  readonly to: Track;
17
17
  }
18
+ /**
19
+ * How a scale words a function: as a command that turns it on, one that
20
+ * cancels it, or one that holds it for as long as it lasts.
21
+ */
22
+ export type Spelling = 'on' | 'off' | 'held';
23
+ /**
24
+ * What a word of a scale operates, rather than the word itself, so that
25
+ * two scales can be compared at all.
26
+ */
27
+ export interface Operation {
28
+ /** The function operated, named the same wherever a scale has it. */
29
+ operates: string;
30
+ spelling: Spelling;
31
+ /**
32
+ * Whether the half of the keyboard the valve serves is part of the
33
+ * function operated. The dynamics are per half; the pedals are not,
34
+ * and the two Welte scales put them on opposite edges of the paper,
35
+ * so a rule that compared sides would leave every pedal unpaired.
36
+ */
37
+ sided: boolean;
38
+ }
18
39
  export type NoteMeaning = Pick<Note, 'type' | 'pitch'>;
19
40
  export type ExpressionMeaning = Pick<Expression, 'type' | 'expressionType' | 'scope'>;
20
41
  export type TrackMeaning = NoteMeaning | ExpressionMeaning;
@@ -44,6 +65,12 @@ export interface TrackerBar {
44
65
  readonly areas: readonly TrackArea[];
45
66
  /** The expression types the bar reads, each a term of the system. */
46
67
  readonly expressionTypes: readonly string[];
68
+ /**
69
+ * How the system spells a single added accent, each spelling a
70
+ * sequence of its expression types: a latched system turns a valve on
71
+ * and off again, a held one holds one perforation for the accent.
72
+ */
73
+ readonly accents: readonly (readonly string[])[];
47
74
  /**
48
75
  * The position carrying the rewind perforation, which runs at
49
76
  * the very end of a roll and is the usual landmark for calibrating
@@ -90,6 +117,11 @@ export interface TrackerBar {
90
117
  positionOf(meaning: TrackMeaning): Track | undefined;
91
118
  /** `undefined` for a position the bar does not read; snapped as `meaningOf` is. */
92
119
  roleOf(position: Track): TrackRole | undefined;
120
+ /**
121
+ * What the expression type operates, or `undefined` for one the bar
122
+ * does not read or whose function has no counterpart in another scale.
123
+ */
124
+ operationOf(expressionType: string): Operation | undefined;
93
125
  /** The positions a feature lies across, snapped to the grid. */
94
126
  positionsIn(span: OnBar): readonly Track[];
95
127
  }
@@ -105,11 +137,6 @@ export declare const systemOf: (bar: TrackerBar) => Concept;
105
137
  export declare const systemIdIn: (id: string | undefined) => string | undefined;
106
138
  /** The identifier of a system the type vocabulary knows, from its concept. */
107
139
  export declare const systemIdOf: (system: Concept | undefined) => string | undefined;
108
- /**
109
- * A tracker bar as written down, with its positions as plain numbers
110
- * in the bar's own 1-based numbering; `describeTrackerBar` gives them
111
- * their type.
112
- */
113
140
  /**
114
141
  * Where a roll's own content ends, as far as its perforations say.
115
142
  *
@@ -141,6 +168,11 @@ export type OnBar = {
141
168
  readonly from: Track;
142
169
  readonly to?: Track;
143
170
  };
171
+ /**
172
+ * A tracker bar as written down, with its positions as plain numbers
173
+ * in the bar's own 1-based numbering; `describeTrackerBar` gives them
174
+ * their type.
175
+ */
144
176
  export interface TrackerBarSpec {
145
177
  id: string;
146
178
  name: string;
@@ -170,6 +202,14 @@ export interface TrackerBarSpec {
170
202
  * its own needs no threshold, since anything there is the rewind.
171
203
  */
172
204
  rewindHold?: Millimeters;
205
+ /**
206
+ * What each expression type operates, keyed by type. A type left out
207
+ * operates a function no other scale has a word for, and a transfer
208
+ * leaves it a plain deletion.
209
+ */
210
+ operations?: Readonly<Record<string, Operation>>;
211
+ /** How the system spells a single added accent; see `TrackerBar.accents`. */
212
+ accents?: readonly (readonly string[])[];
173
213
  }
174
214
  export declare const describeTrackerBar: (spec: TrackerBarSpec) => TrackerBar;
175
215
  /**
@@ -1,4 +1,4 @@
1
- import { mm, track } from "./Quantity.js";
1
+ import { mm, track } from "../model/Quantity.js";
2
2
  /**
3
3
  * What a position says, as a key. Two bars read the same thing exactly
4
4
  * where their keys agree, which is what lets a symbol cross from one
@@ -66,6 +66,13 @@ export const describeTrackerBar = (spec) => {
66
66
  if (rewind === undefined) {
67
67
  throw new Error(`${spec.name} declares no rewind track`);
68
68
  }
69
+ const expressionTypes = [...new Set(spec.expressions.values())];
70
+ const operations = spec.operations ?? {};
71
+ const accents = spec.accents ?? [];
72
+ const unread = [...Object.keys(operations), ...accents.flat()].filter(type => !expressionTypes.includes(type));
73
+ if (unread.length > 0) {
74
+ throw new Error(`${spec.name} names expression types its bar does not read: ${[...new Set(unread)].join(', ')}`);
75
+ }
69
76
  // A rewind on a line of its own is unambiguous; one sharing a line is only
70
77
  // the rewind when it is far longer than that line's usual command.
71
78
  const shared = spec.rewindTrack !== undefined;
@@ -84,13 +91,15 @@ export const describeTrackerBar = (spec) => {
84
91
  trackCount: spec.trackCount,
85
92
  endsAt,
86
93
  areas,
87
- expressionTypes: [...new Set(spec.expressions.values())],
94
+ expressionTypes,
95
+ accents,
88
96
  rewindTrack: track(rewind),
89
97
  ...(spec.paperSpeed && { paperSpeed: spec.paperSpeed }),
90
98
  meaningOf,
91
99
  meaningsOf,
92
100
  positionOf: meaning => positions.get(keyOf(meaning)),
93
101
  roleOf,
102
+ operationOf: expressionType => Object.hasOwn(operations, expressionType) ? operations[expressionType] : undefined,
94
103
  positionsIn: positionsBetween
95
104
  };
96
105
  };
@@ -1,6 +1,12 @@
1
- import { Concept } from "../Agent.js";
2
- import { TrackerBar } from "../TrackerBar.js";
1
+ import { Concept } from "../model/Agent.js";
2
+ import { TrackerBar } from "./TrackerBar.js";
3
3
  /** The tracker bars the library knows, the T-100 first as the usual one. */
4
4
  export declare const trackerBars: readonly TrackerBar[];
5
5
  /** The bar of a system the type vocabulary knows, from its concept. */
6
6
  export declare const trackerBarOf: (system: Concept | undefined) => TrackerBar | undefined;
7
+ /**
8
+ * The bar a copy is read by where it names no system of its own: every
9
+ * copy was read by the T-100's before the systems were told apart, and
10
+ * `reservationsAbout` reports a copy that still relies on this.
11
+ */
12
+ export declare const defaultTrackerBar: TrackerBar;
@@ -1,4 +1,4 @@
1
- import { systemIdOf } from "../TrackerBar.js";
1
+ import { systemIdOf } from "./TrackerBar.js";
2
2
  import { welteT100 } from "./welteT100/bar.js";
3
3
  import { welteLicensee } from "./welteLicensee/bar.js";
4
4
  import { welteT98 } from "./welteT98/bar.js";
@@ -9,3 +9,9 @@ export const trackerBarOf = (system) => {
9
9
  const id = systemIdOf(system);
10
10
  return trackerBars.find(bar => bar.id === id);
11
11
  };
12
+ /**
13
+ * The bar a copy is read by where it names no system of its own: every
14
+ * copy was read by the T-100's before the systems were told apart, and
15
+ * `reservationsAbout` reports a copy that still relies on this.
16
+ */
17
+ export const defaultTrackerBar = welteT100;
@@ -0,0 +1,100 @@
1
+ import { Grid, Half, Model, ModelInput, Parameters, PedalInput, PedalMode, PedalTravel, PortGeometry, Spool } from "welte-mignon-emulator";
2
+ import { Expression } from "../model/Symbol.js";
3
+ import { NegotiatedEvent, Performance, RollProperties } from "./ReproducingSystem.js";
4
+ import { Millimeters, Seconds, Track } from "../model/Quantity.js";
5
+ import { type VelocityMap } from "./velocity.js";
6
+ /**
7
+ * What the Welte systems share downstream of the relay. Hagmann has the
8
+ * nuancing unit built the same for both tracker scales (p. 96) and the valves
9
+ * and bellows that move the pedals the same in both (p. 106), so the way a
10
+ * performance is read off the emulator is one; what differs is in front of it,
11
+ * and each system says that in the `Mechanism` it hands to `performWelte`.
12
+ */
13
+ /** The options every Welte system takes. */
14
+ export type WelteOptions = {
15
+ /**
16
+ * The take-up spool, which sets the time axis: it is held at a constant
17
+ * rate of revolution, so the paper runs faster as the spool fills.
18
+ */
19
+ spool: Spool;
20
+ /** Constants of the nuancing mechanism, one set for each half of the keyboard. */
21
+ nuance: Record<Half, Parameters>;
22
+ /** Constants of the two pedal actions, one of `pedalPresets` or a set of one's own. */
23
+ pedals: Parameters;
24
+ velocity: VelocityMap;
25
+ /**
26
+ * How the pedals go out: as a continuous controller carrying the travel
27
+ * of the bellows, or thresholded to the two values a switching renderer
28
+ * understands.
29
+ */
30
+ pedalMode: PedalMode;
31
+ /** Diameter of the tracker-bar bore. */
32
+ trackerBore: Millimeters;
33
+ /** Punch diameter for an edition whose copies record none. */
34
+ punchDiameter: Millimeters;
35
+ /**
36
+ * The track at which the keyboard is divided, so that notes from here
37
+ * upwards follow the treble expression and the ones below it the bass.
38
+ * Which side an expression perforation itself belongs to is not decided
39
+ * here but read off the tracker bar.
40
+ */
41
+ division: Track;
42
+ };
43
+ /** Whether two sets of constants are the same, value for value. */
44
+ export declare const sameParameters: (a: Parameters, b: Parameters) => boolean;
45
+ /**
46
+ * Between the edition's shared place axis and this version's own paper.
47
+ * `toOwnPaper` is 1 for a version on the axis it was measured on, and about
48
+ * 0.775 for a green version whose places are in the red axis; see
49
+ * `RollProperties`.
50
+ *
51
+ * Rows are rows of the version's own paper, so the spool is asked in
52
+ * `paperOfRow` and never in `placeOfRow`: one is the paper that passes the
53
+ * tracker bar, the other is the coordinate the edition states.
54
+ */
55
+ export type Paper = {
56
+ readonly rowOf: (place: Millimeters) => number;
57
+ readonly placeOfRow: (row: number) => Millimeters;
58
+ readonly paperOfRow: (row: number) => Millimeters;
59
+ };
60
+ /** When the spool brings a place on the roll to the tracker bar. */
61
+ export declare const secondsAt: (spool: Spool, place: Millimeters) => Seconds;
62
+ /** A perforation as the emulator reads it: the control it operates and the rows it spans. */
63
+ export type WeltePunch = {
64
+ readonly half: Half;
65
+ readonly control: string;
66
+ readonly rowOn: number;
67
+ readonly rowOff: number;
68
+ };
69
+ export type Ports = ModelInput['ports'];
70
+ /**
71
+ * What a Welte system puts in front of the shared nuancing unit and pedals:
72
+ * how it reads a perforation, how it opens the ports, which pneumatic model
73
+ * drives the bellows, and which the pedals.
74
+ */
75
+ export type Mechanism<P extends WeltePunch> = {
76
+ /** The punch the expression is read as, or nothing for one the mechanism does not read. */
77
+ readonly punchOf: (event: NegotiatedEvent & Expression, rows: {
78
+ rowOn: number;
79
+ rowOff: number;
80
+ }) => P | undefined;
81
+ readonly portsOf: (grid: Grid, punches: readonly P[], geometry: PortGeometry) => Ports;
82
+ readonly model: Model;
83
+ readonly runPedals: (input: PedalInput, params: Parameters) => PedalTravel;
84
+ /** The name of the instrument the curves say they came from. */
85
+ readonly instrument: string;
86
+ };
87
+ /** A performance, with the grid and ports it was read off, for a system that has more to ask of them. */
88
+ export type WeltePerformance = Performance & {
89
+ readonly grid: Grid;
90
+ readonly ports: Ports;
91
+ };
92
+ /**
93
+ * Plays the events on a Welte mechanism: notes at the velocity of their half's
94
+ * bellows, and the two pedals as the travel of theirs.
95
+ *
96
+ * The edition's tempo adjustment is left aside: it is stated as a paper
97
+ * speed, and what the spool holds constant is its rate of revolution, so
98
+ * the two are not the same quantity. The spool in the options sets the speed.
99
+ */
100
+ export declare const performWelte: <P extends WeltePunch>(events: readonly NegotiatedEvent[], options: WelteOptions, roll: RollProperties, mechanism: Mechanism<P>) => WeltePerformance;
@@ -0,0 +1,119 @@
1
+ import { clamp, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, ROWS_PER_MM, travelBetweenRails, } from "welte-mignon-emulator";
2
+ import { inCentimeters, mm, seconds } from "../model/Quantity.js";
3
+ import { partitionPoint } from "../shared/sorted.js";
4
+ import { velocityOf } from "./velocity.js";
5
+ /** Whether two sets of constants are the same, value for value. */
6
+ export const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
7
+ && Object.entries(a).every(([name, value]) => b[name] === value);
8
+ /** Paper the grid runs on past the last hole, so that a final pedal release completes. */
9
+ const RUN_OUT = mm(100);
10
+ const isNote = (event) => event.type === 'note';
11
+ const isExpression = (event) => event.type === 'expression';
12
+ const paperOf = (toOwnPaper) => ({
13
+ rowOf: place => place * toOwnPaper * ROWS_PER_MM,
14
+ placeOfRow: row => mm(row / (toOwnPaper * ROWS_PER_MM)),
15
+ paperOfRow: row => mm(row / ROWS_PER_MM)
16
+ });
17
+ /** When the spool brings a place on the roll to the tracker bar. */
18
+ export const secondsAt = (spool, place) => seconds(paperSeconds(spool, inCentimeters(place)));
19
+ const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
20
+ /**
21
+ * One sample per row of the scan the constants were fitted on, from the
22
+ * beginning of the roll to a little past the last hole. The rows are
23
+ * equally spaced on the paper and not in time, which is what the
24
+ * emulator expects.
25
+ */
26
+ const gridOver = (events, spool, paper) => {
27
+ const last = mm(events.reduce((furthest, event) => Math.max(furthest, event.horizontal.to), 0));
28
+ const length = Math.ceil(paper.rowOf(last) + RUN_OUT * ROWS_PER_MM) + 1;
29
+ const times = new Float64Array(length).map((_, row) => secondsAt(spool, paper.paperOfRow(row)));
30
+ return new Grid(0, times);
31
+ };
32
+ const nuanceCurves = (grid, ports, samples, options, model, instrument) => {
33
+ const curveOf = (half) => {
34
+ const params = options.nuance[half];
35
+ const output = model.run({ grid, half, ports }, params);
36
+ const travel = travelBetweenRails(output, params);
37
+ const hook = clamp(mezzoforteTravel(params), 0.01, 0.99);
38
+ return {
39
+ ...samples,
40
+ name: half,
41
+ kind: 'dynamics',
42
+ instrument,
43
+ travel,
44
+ velocity: travel.map(value => velocityOf(value, hook, options.velocity))
45
+ };
46
+ };
47
+ return { bass: curveOf('bass'), treble: curveOf('treble') };
48
+ };
49
+ const performNotes = (events, grid, nuance, options, paper) => events
50
+ .filter(isNote)
51
+ .flatMap((note) => {
52
+ const curve = nuance[halfOf(note, options.division)];
53
+ const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
54
+ return [
55
+ { type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
56
+ { type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
57
+ ];
58
+ });
59
+ /**
60
+ * The travel of one pedal as controller steps. Each step is attributed to
61
+ * the last perforation of that pedal the tracker bar has reached, which is
62
+ * the one whose reading it follows from.
63
+ */
64
+ const performPedal = (type, curve, grid, readings, mode) => {
65
+ if (readings.length === 0)
66
+ return [];
67
+ const ordered = readings.toSorted((a, b) => a.punch.rowOn - b.punch.rowOn);
68
+ const causeOf = (row) => ordered[Math.max(partitionPoint(ordered, reading => reading.punch.rowOn <= row) - 1, 0)].event;
69
+ return levelChanges(curve.travel, { mode })
70
+ .filter(change => change.index > 0)
71
+ .map(change => ({
72
+ type,
73
+ performs: causeOf(grid.rowAt(change.index)),
74
+ value: change.value,
75
+ at: seconds(curve.seconds[change.index])
76
+ }));
77
+ };
78
+ /**
79
+ * Plays the events on a Welte mechanism: notes at the velocity of their half's
80
+ * bellows, and the two pedals as the travel of theirs.
81
+ *
82
+ * The edition's tempo adjustment is left aside: it is stated as a paper
83
+ * speed, and what the spool holds constant is its rate of revolution, so
84
+ * the two are not the same quantity. The spool in the options sets the speed.
85
+ */
86
+ export const performWelte = (events, options, roll, mechanism) => {
87
+ const paper = paperOf(roll.toOwnPaper ?? 1);
88
+ const readings = events
89
+ .filter(isExpression)
90
+ .flatMap((event) => {
91
+ const punch = mechanism.punchOf(event, {
92
+ rowOn: paper.rowOf(event.horizontal.from),
93
+ rowOff: paper.rowOf(event.horizontal.to)
94
+ });
95
+ return punch ? [{ event, punch }] : [];
96
+ });
97
+ const grid = gridOver(events, options.spool, paper);
98
+ const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
99
+ const ports = mechanism.portsOf(grid, readings.map(reading => reading.punch), geometry);
100
+ const samples = {
101
+ place: grid.seconds.map((_, row) => paper.placeOfRow(row)),
102
+ seconds: grid.seconds
103
+ };
104
+ const nuance = nuanceCurves(grid, ports, samples, options, mechanism.model, mechanism.instrument);
105
+ const travel = mechanism.runPedals({ grid, ports }, options.pedals);
106
+ const damper = { ...samples, name: 'damper', kind: 'pedal', travel: travel.damper };
107
+ const hammerRail = { ...samples, name: 'hammerRail', kind: 'pedal', travel: travel.hammerRail };
108
+ const readingsOf = (control) => readings.filter(reading => reading.punch.control === control);
109
+ return {
110
+ grid,
111
+ ports,
112
+ events: [
113
+ ...performNotes(events, grid, nuance, options, paper),
114
+ ...performPedal('damper', damper, grid, readingsOf('sustainPedal'), options.pedalMode),
115
+ ...performPedal('hammerRail', hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
116
+ ],
117
+ curves: [nuance.bass, nuance.treble, damper, hammerRail]
118
+ };
119
+ };
@@ -1,4 +1,4 @@
1
- import { TrackerBar } from "../../TrackerBar.js";
1
+ import { TrackerBar } from "../TrackerBar.js";
2
2
  /**
3
3
  * Welte-Mignon (Licensee), the American re-cut of the T-100 rolls on
4
4
  * a roll 11¼ inches wide with 98 positions at nine to the inch, cf.
@@ -1,5 +1,6 @@
1
- import { describeTrackerBar } from "../../TrackerBar.js";
2
- import { mm } from "../../Quantity.js";
1
+ import { describeTrackerBar } from "../TrackerBar.js";
2
+ import { mm } from "../../model/Quantity.js";
3
+ import { welteT100Accents, welteT100Operations } from "../welteT100/bar.js";
3
4
  /**
4
5
  * Welte-Mignon (Licensee), the American re-cut of the T-100 rolls on
5
6
  * a roll 11¼ inches wide with 98 positions at nine to the inch, cf.
@@ -15,6 +16,8 @@ export const welteLicensee = describeTrackerBar({
15
16
  width: mm(285.75),
16
17
  trackCount: 98,
17
18
  notes: { from: 9, to: 88, lowestPitch: 24 },
19
+ operations: welteT100Operations,
20
+ accents: welteT100Accents,
18
21
  expressions: new Map([
19
22
  [1, 'MezzoforteOff'],
20
23
  [2, 'MezzoforteOn'],
@@ -1,4 +1,4 @@
1
- import { ReproducingSystem } from "../../ReproducingSystem.js";
1
+ import { ReproducingSystem } from "../ReproducingSystem.js";
2
2
  import { type WelteT100Options } from "../welteT100/system.js";
3
3
  export type WelteLicenseeOptions = WelteT100Options;
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { CONSENSUS } from "welte-mignon-emulator/t100";
2
- import { track } from "../../Quantity.js";
2
+ import { track } from "../../model/Quantity.js";
3
3
  import { welteLicensee } from "./bar.js";
4
4
  import { defaultWelteT100Options, instrumentNameOf, nuanceOf, performAs } from "../welteT100/system.js";
5
5
  /**
@@ -1,9 +1,35 @@
1
- import { TrackerBar } from "../../TrackerBar.js";
1
+ import { Operation, TrackerBar } from "../TrackerBar.js";
2
2
  /**
3
3
  * The commands of the Welte-Mignon T-100, as its tracker bar reads them.
4
4
  */
5
5
  export declare const welteT100ExpressionTypes: readonly ['SustainPedalOn', 'SustainPedalOff', 'SoftPedalOn', 'SoftPedalOff', 'MezzoforteOff', 'MezzoforteOn', 'SlowCrescendoOn', 'SlowCrescendoOff', 'ForzandoOn', 'ForzandoOff', 'MotorOff', 'MotorOn', 'Rewind', 'ElectricCutOff'];
6
6
  export type WelteT100ExpressionType = typeof welteT100ExpressionTypes[number];
7
+ /**
8
+ * What each T-100 command operates, so that it can be set against the
9
+ * T-98's words for the same function.
10
+ *
11
+ * This cannot be read off the names. The T-100 latches a function on
12
+ * with one perforation and cancels it with a second, while the T-98
13
+ * holds it for as long as one perforation lasts (Hagmann pp. 89 f. and
14
+ * 100–103; Phillips p. 121), and Welte renamed two of the functions
15
+ * between the scales: the red `SlowCrescendo` is the green `Crescendo`,
16
+ * and the red's single `Forzando` valve answers to two green ones that
17
+ * name the end they pull towards. So the correspondence is stated here
18
+ * rather than derived, and only two types that operate one function can
19
+ * stand for each other.
20
+ *
21
+ * `MotorOn`/`MotorOff`, `Rewind` and `ElectricCutOff` are left out on
22
+ * purpose: the green scale has no word for any of them, its motor switch
23
+ * being an automatic mercury contact (Skala-Rolle 98 §12) and its rewind
24
+ * riding on the bass sforzando-piano line. A red command of those kinds
25
+ * has no counterpart and stays a plain deletion.
26
+ */
27
+ export declare const welteT100Operations: Readonly<Partial<Record<WelteT100ExpressionType, Operation>>>;
28
+ /**
29
+ * A single added accent on the T-100: a valve latched on and off again
30
+ * straight away.
31
+ */
32
+ export declare const welteT100Accents: readonly (readonly WelteT100ExpressionType[])[];
7
33
  /**
8
34
  * Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
9
35
  *