linked-rolls 0.0.1 → 0.1.1

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 (67) hide show
  1. package/README.md +48 -1
  2. package/lib/Assumption.d.ts +124 -0
  3. package/lib/Assumption.js +34 -0
  4. package/lib/Collation.d.ts +11 -0
  5. package/lib/ConditionState.d.ts +8 -3
  6. package/lib/ConditionState.js +0 -5
  7. package/lib/Edit.d.ts +44 -14
  8. package/lib/Edit.js +2 -95
  9. package/lib/Edition.d.ts +144 -4
  10. package/lib/EditionView.d.ts +51 -0
  11. package/lib/EditionView.js +294 -0
  12. package/lib/Emulation.d.ts +21 -68
  13. package/lib/Emulation.js +94 -386
  14. package/lib/Feature.d.ts +137 -16
  15. package/lib/Feature.js +9 -1
  16. package/lib/Plan.d.ts +190 -0
  17. package/lib/Plan.js +555 -0
  18. package/lib/ReproducingSystem.d.ts +80 -0
  19. package/lib/RollCopy.d.ts +265 -24
  20. package/lib/RollCopy.js +276 -215
  21. package/lib/Symbol.d.ts +70 -37
  22. package/lib/Symbol.js +2 -27
  23. package/lib/TrackCalibration.d.ts +33 -0
  24. package/lib/TrackCalibration.js +14 -0
  25. package/lib/TrackerBar.d.ts +52 -5
  26. package/lib/TrackerBar.js +70 -49
  27. package/lib/Version.d.ts +32 -33
  28. package/lib/Version.js +3 -209
  29. package/lib/alignFeatures.d.ts +3 -2
  30. package/lib/alignFeatures.js +5 -4
  31. package/lib/asJsonLd.d.ts +1 -1
  32. package/lib/asJsonLd.js +13 -26
  33. package/lib/importJsonLd.d.ts +0 -1
  34. package/lib/importJsonLd.js +9 -72
  35. package/lib/index.d.ts +7 -3
  36. package/lib/index.js +7 -3
  37. package/lib/schema.json +1739 -0
  38. package/lib/spec/context.json +125 -144
  39. package/lib/systems/welteT100.d.ts +55 -0
  40. package/lib/systems/welteT100.js +191 -0
  41. package/lib/utils.d.ts +29 -0
  42. package/lib/validate.d.ts +3 -0
  43. package/lib/validate.js +10 -0
  44. package/package.json +41 -8
  45. package/lib/Condition.d.ts +0 -10
  46. package/lib/EditorialAssumption.d.ts +0 -67
  47. package/lib/EditorialAssumption.js +0 -26
  48. package/lib/Measurement.d.ts +0 -9
  49. package/lib/PlaceTimeConversion.d.ts +0 -65
  50. package/lib/PlaceTimeConversion.js +0 -175
  51. package/lib/RollEvent.d.ts +0 -76
  52. package/lib/RollEvent.js +0 -3
  53. package/lib/Stage.d.ts +0 -37
  54. package/lib/Stage.js +0 -165
  55. package/lib/Transcription.d.ts +0 -7
  56. package/lib/Transcription.js +0 -9
  57. package/lib/WithId.d.ts +0 -3
  58. package/lib/WithId.js +0 -1
  59. package/lib/alignRolls.d.ts +0 -7
  60. package/lib/alignRolls.js +0 -49
  61. package/lib/alignSymbols.d.ts +0 -7
  62. package/lib/alignSymbols.js +0 -49
  63. package/lib/aton/AtonParser.test.d.ts +0 -1
  64. package/lib/aton/AtonParser.test.js +0 -16
  65. package/lib/build-schema.cjs +0 -113
  66. /package/lib/{Condition.js → ReproducingSystem.js} +0 -0
  67. /package/lib/{Measurement.js → utils.js} +0 -0
@@ -1,192 +1,173 @@
1
1
  {
2
2
  "@context": {
3
- "crm": "http://www.cidoc-crm.org/cidoc-crm/",
4
- "lrmoo": "http://iflastandards.info/ns/lrm/lrmoo/",
3
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
5
4
  "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
6
5
  "owl": "http://www.w3.org/2002/07/owl#",
6
+ "dcterms": "http://purl.org/dc/terms/",
7
+ "crm": "http://www.cidoc-crm.org/cidoc-crm/",
8
+ "crmdig": "http://www.ics.forth.gr/isl/CRMdig/",
9
+ "crminf": "http://www.cidoc-crm.org/cidoc-crm/CRMinf/",
10
+ "lrmoo": "http://iflastandards.info/ns/lrm/lrmoo/",
7
11
  "reo": "http://linked-rolls.org/reo/",
8
12
  "@vocab": "http://linked-rolls.org/reo/",
9
- "C11_Derivation": {
10
- "@id": "reo:C11_Derivation",
11
- "@context": {
12
- "original": {
13
- "@id": "reo:P10_used",
14
- "@type": "@id"
15
- }
16
- }
13
+ "Edition": "lrmoo:F2_Expression",
14
+ "RollCopy": "reo:C1_Witness",
15
+ "Addition": "crm:E79_Part_Addition",
16
+ "Removal": "crm:E80_Part_Removal",
17
+ "title": "crm:P102_has_title",
18
+ "license": {
19
+ "@id": "dcterms:license",
20
+ "@type": "@id"
17
21
  },
18
- "C12_Version": {
19
- "@id": "reo:C12_Version",
20
- "@context": {
21
- "siglum": {
22
- "@id": "reo:P22_is_identified_by",
23
- "@type": "xsd:string"
24
- },
25
- "witnesses": {
26
- "@id": "reo:P2i_is_witnessed_by",
27
- "@type": "@id"
28
- }
29
- }
22
+ "name": "rdfs:label",
23
+ "sameAs": {
24
+ "@id": "owl:sameAs",
25
+ "@type": "@id"
30
26
  },
31
- "C13_Version_Creation": {
32
- "@id": "reo:C13_Version_Creation",
33
- "@context": {
34
- "created": {
35
- "@id": "reo:P15_created_version",
36
- "@type": "reo:C12_Version"
37
- },
38
- "basedOn": {
39
- "@id": "reo:P10i_is_based_on",
40
- "@type": "reo:C11_Derivation"
41
- },
42
- "edits": {
43
- "@id": "reo:P16_involved_edit",
44
- "@type": "reo:C8_Edit"
45
- },
46
- "intentions": {
47
- "@id": "reo:P18_was_motivated_by",
48
- "@type": "reo:C9_Assumed_Intention"
49
- }
50
- }
27
+ "role": "crm:P2_has_type",
28
+ "date": "crm:P4_has_time-span",
29
+ "actor": "crm:P14_carried_out_by",
30
+ "description": "crm:P3_has_note",
31
+ "depiction": {
32
+ "@id": "crm:P62i_is_depicted_by",
33
+ "@type": "@id"
51
34
  },
52
- "C9_Assument_Intention": {
53
- "@id": "reo:C9_Assumed_Intention",
35
+ "horizontal": "crm:P43_has_dimension",
36
+ "vertical": "crm:P43_has_dimension",
37
+ "unit": "crm:P91_has_unit",
38
+ "scan": {
39
+ "@id": "crm:P138i_has_representation",
40
+ "@type": "@id"
41
+ },
42
+ "carriers": {
43
+ "@id": "crm:P128i_is_carried_by",
44
+ "@type": "@id"
45
+ },
46
+ "pitch": "reo:P5_has_pitch",
47
+ "scope": "reo:P6_has_scope",
48
+ "expressionType": "crm:P2_has_type",
49
+ "alignedWith": "reo:P3_aligned_with",
50
+ "material": "crm:P45_consists_of",
51
+ "condition": "crm:P44_has_condition",
52
+ "creation": {
53
+ "@id": "lrmoo:R17i_was_created_by",
54
54
  "@context": {
55
- "description": {
56
- "@id": "reo:P23_has_description",
57
- "@type": "xsd:string"
55
+ "publisher": "crm:P14_carried_out_by",
56
+ "publicationDate": {
57
+ "@id": "crm:P4_has_time-span",
58
+ "@type": "xsd:date"
58
59
  },
59
- "belief": {
60
- "@id": "crminf:J4i_is_subject_to",
61
- "@type": "crminf:I2_Belief"
62
- }
60
+ "collationTolerance": "crmdig:L13_used_parameters"
63
61
  }
64
62
  },
65
- "crminf:I2_Belief": {
66
- "@id": "crminf:I2_Belief",
63
+ "roll": {
64
+ "@id": "lrmoo:R3i_realises",
67
65
  "@context": {
68
- "certainty": {
69
- "@id": "crminf:J5_holds_to_be",
70
- "@type": "xsd:string"
71
- },
72
- "reasons": {
73
- "@id": "crminf:J2i_was_concluded_by",
74
- "@type": "crminf:Argumentation"
66
+ "catalogueNumber": "crm:P1_is_identified_by",
67
+ "recordingEvent": {
68
+ "@id": "lrmoo:R19i_was_realised_through",
69
+ "@context": {
70
+ "recorded": {
71
+ "@id": "lrmoo:R81_recorded",
72
+ "@context": {
73
+ "pianist": "crm:P14_carried_out_by",
74
+ "playing": {
75
+ "@type": "@id"
76
+ }
77
+ }
78
+ },
79
+ "place": "crm:P7_took_place_at",
80
+ "created": "lrmoo:R17_created"
81
+ }
75
82
  }
76
83
  }
77
84
  },
78
- "C1_Witness": {
85
+ "copies": {
86
+ "@id": "lrmoo:R2_is_derivative_of",
79
87
  "@context": {
80
- "@base": "copy/",
81
- "siglum": {
82
- "@id": "reo:P21_is_identified_by",
83
- "@type": "xsd:string"
84
- },
85
- "productionEvent": {
88
+ "location": "crm:P55_has_current_location",
89
+ "production": {
86
90
  "@id": "lrmoo:R28i_was_produced_by",
87
- "@type": "lrmoo:F33_Reproduction_Event",
88
91
  "@context": {
89
- "date": {
90
- "@id": "crm:P4_has_time-span",
91
- "@type": "xsd:dateTime"
92
- },
93
- "paper": {
94
- "@id": "crm:P126_employed",
95
- "@type": "@id"
96
- },
97
- "company": {
98
- "@id": "crm:P14_was_carried_out_by",
99
- "@type": "@id"
100
- }
92
+ "company": "crm:P14_carried_out_by",
93
+ "paper": "crm:P126_employed"
101
94
  }
102
95
  },
103
- "location": {
104
- "@id": "crm:P53_has_current_or_former_location",
105
- "@type": "xsd:string"
96
+ "conditions": "crm:P44_has_condition",
97
+ "modifications": {
98
+ "@id": "crm:P31i_was_modified_by",
99
+ "@context": {
100
+ "added": "crm:P111_added",
101
+ "removed": "crm:P113_removed",
102
+ "purpose": "crm:P21_had_general_purpose"
103
+ }
106
104
  },
107
105
  "measurements": {
108
106
  "@id": "crm:P39i_was_measured_by",
109
- "@type": "reo:RollMeasurement",
110
107
  "@context": {
111
- "date": {
112
- "@id": "crm:P4_has_time-span",
113
- "@type": "xsd:dateTime"
114
- },
115
- "software": {
116
- "@id": "crmdig:L23_used_software_or_firmware",
117
- "@type": "@id"
108
+ "measuredBy": {
109
+ "@context": {
110
+ "software": "crmdig:L23_used_software_or_firmware"
111
+ }
118
112
  }
119
113
  }
120
114
  },
121
- "events": {
122
- "@id": "crm:P128_carries",
115
+ "features": {
116
+ "@id": "crm:P56_bears_feature",
123
117
  "@context": {
124
- "measurement": {
125
- "@id": "crmdig:L20i_was_created_by",
126
- "@type": "@id"
127
- },
128
- "annotates": {
129
- "@id": "crm:P62i_is_depicted_by",
130
- "@type": "@id"
131
- },
132
- "horizontal": {
133
- "@id": "crm:P43_has_dimension",
134
- "@type": "reo:C5_Event_Span"
135
- },
136
- "vertical": {
137
- "@id": "crm:P43_has_dimension",
138
- "@type": "reo:C5_Event_Span"
139
- }
118
+ "method": "crm:P2_has_type",
119
+ "transcription": "crm:P128_carries",
120
+ "pattern": "crm:P2_has_type",
121
+ "features": "crm:P56_bears_feature"
140
122
  }
141
123
  }
142
124
  }
143
125
  },
144
- "stamp": {
145
- "@type": "reo:C7_Stamp",
126
+ "versions": {
127
+ "@id": "lrmoo:R76_is_derivative_of",
146
128
  "@context": {
147
- "text": {
148
- "@id": "reo:has_text"
129
+ "siglum": "reo:P22_is_identified_by",
130
+ "basedOn": {
131
+ "@id": "reo:P10i_is_based_on",
132
+ "@type": "@id"
133
+ },
134
+ "motivations": "@included",
135
+ "edits": {
136
+ "@id": "reo:P16_involved_edit",
137
+ "@context": {
138
+ "editType": "crm:P2_has_type",
139
+ "motivation": {
140
+ "@id": "crm:P17_was_motivated_by",
141
+ "@type": "@id"
142
+ },
143
+ "insert": "reo:P7_added",
144
+ "delete": {
145
+ "@id": "reo:P8_removed",
146
+ "@type": "@id"
147
+ }
148
+ }
149
149
  }
150
150
  }
151
151
  },
152
- "publicationEvent": {
153
- "@id": "lrmoo:R17i_was_created_by",
154
- "@type": "lrmoo:F28_Expression_Creation",
152
+ "belief": {
153
+ "@id": "crminf:J4i_is_subject_to",
155
154
  "@context": {
156
- "publicationDate": {
157
- "@id": "crm:P4_has_time-span",
158
- "@type": "xsd:dateTime"
159
- },
160
- "publisher": {
161
- "@id": "crm:P14_carried_out_by",
162
- "@type": "crm:E21_Person",
155
+ "belief": "crminf:I2_Belief",
156
+ "certainty": "crminf:J5_holds_to_be",
157
+ "reasons": {
158
+ "@id": "crminf:J2i_was_concluded_by",
163
159
  "@context": {
164
- "name": {
165
- "@id": "rdfs:label",
166
- "@type": "xsd:string"
160
+ "comprehends": {
161
+ "@id": "crminf:J22_interpreted_meaning_of",
162
+ "@type": "@id"
167
163
  },
168
- "sameAs": {
169
- "@id": "owl:sameAs",
164
+ "premises": {
165
+ "@id": "crminf:J1_used_as_premise",
170
166
  "@type": "@id"
171
167
  }
172
168
  }
173
169
  }
174
170
  }
175
- },
176
- "title": "crm:P102_has_title",
177
- "license": {
178
- "@id": "crm:P104_is_subject_to",
179
- "@type": "@id"
180
- },
181
- "copies": {
182
- "@id": "lrmoo:R2_is_derivative_of",
183
- "@type": "reo:C1_Witness"
184
- },
185
- "collation": "crm:P9_consists_of",
186
- "versions": {
187
- "@id": "crm:P9_consists_of",
188
- "@type": "reo:C13_Version_Creation"
189
- },
190
- "questions": "crm:P9_consists_of"
171
+ }
191
172
  }
192
173
  }
@@ -0,0 +1,55 @@
1
+ import { Half, Parameters, PedalMode, Spool } from "welte-t100-emulator";
2
+ import { ReproducingSystem } from "../ReproducingSystem";
3
+ /**
4
+ * MIDI velocity at the open rail of the Nuancierbalg, at the Mezzoforte pin,
5
+ * and at the closed rail, joined linearly in between. Nothing in the
6
+ * mechanism determines how bellows travel maps onto hammer velocity, so
7
+ * these are anchors rather than measurements; the three values are the
8
+ * ones midi2exp publishes.
9
+ */
10
+ export type VelocityMap = {
11
+ piano: number;
12
+ mezzoforte: number;
13
+ forte: number;
14
+ };
15
+ export type WelteT100Options = {
16
+ /**
17
+ * The take-up spool, which sets the time axis: it is held at a constant
18
+ * rate of revolution, so the paper runs faster as the spool fills.
19
+ */
20
+ spool: Spool;
21
+ /** Constants of the nuancing mechanism, one set for each half of the keyboard. */
22
+ nuance: Record<Half, Parameters>;
23
+ /** Constants of the two pedal actions. */
24
+ pedals: Parameters;
25
+ velocity: VelocityMap;
26
+ /**
27
+ * How the pedals go out: as a continuous controller carrying the travel
28
+ * of the bellows, or thresholded to the two values a switching renderer
29
+ * understands.
30
+ */
31
+ pedalMode: PedalMode;
32
+ /** Diameter of the tracker-bar bore, in mm. */
33
+ trackerBore: number;
34
+ /** Punch diameter in mm, for an edition whose copies record none. */
35
+ punchDiameter: number;
36
+ /**
37
+ * The track at which the keyboard is divided, so that notes from
38
+ * here upwards follow the treble expression and the ones below it
39
+ * the bass. Which side an expression perforation itself belongs to
40
+ * is not decided here but read off the tracker bar.
41
+ */
42
+ division: number;
43
+ };
44
+ export declare const defaultWelteT100Options: WelteT100Options;
45
+ /** When the spool brings a place on the roll, in mm, to the tracker bar. */
46
+ export declare const secondsAt: (spool: Spool, mm: number) => number;
47
+ /**
48
+ * The red Welte, as welte-t100-emulator models it: the take-up spool sets
49
+ * the time axis, the Nuancierbälge fill through their conduits and are
50
+ * arrested by the Mezzoforte pin, and the two pedals travel rather than
51
+ * switch. The constants are those fitted against the hand-drawn nuance
52
+ * lines of roll 3309, with the terms that describe the drawing apparatus
53
+ * switched off.
54
+ */
55
+ export declare const welteT100System: ReproducingSystem<WelteT100Options>;
@@ -0,0 +1,191 @@
1
+ import { aperturePorts, DEFAULT_PUNCH_MM, geometryInMm, Grid, levelChanges, mezzoforteTravel, noteDensity, paperSeconds, pedalDefaults, playbackParameters, pneumaticModel, ROWS_PER_MM, runPedals, TRACKER_BORE_MM, travelBetweenRails, WELTE_SPOOL, } from "welte-t100-emulator";
2
+ import { welteT100 } from "../TrackerBar";
3
+ export const defaultWelteT100Options = {
4
+ spool: WELTE_SPOOL,
5
+ nuance: {
6
+ bass: playbackParameters('bass'),
7
+ treble: playbackParameters('treble')
8
+ },
9
+ pedals: pedalDefaults,
10
+ velocity: { piano: 35, mezzoforte: 60, forte: 90 },
11
+ pedalMode: 'continuous',
12
+ trackerBore: TRACKER_BORE_MM,
13
+ punchDiameter: DEFAULT_PUNCH_MM,
14
+ division: 54
15
+ };
16
+ /** What each expression code operates, in the emulator's terms. */
17
+ const CODES = new Map([
18
+ ['MezzoforteOn', ['mezzoforte', 'on']],
19
+ ['MezzoforteOff', ['mezzoforte', 'off']],
20
+ ['SlowCrescendoOn', ['crescendo', 'on']],
21
+ ['SlowCrescendoOff', ['crescendo', 'off']],
22
+ ['ForzandoOn', ['sforzando', 'on']],
23
+ ['ForzandoOff', ['sforzando', 'off']],
24
+ ['SustainPedalOn', ['sustainPedal', 'on']],
25
+ ['SustainPedalOff', ['sustainPedal', 'off']],
26
+ ['SoftPedalOn', ['hammerRail', 'on']],
27
+ ['SoftPedalOff', ['hammerRail', 'off']],
28
+ ['MotorOn', ['windResistance', 'on']],
29
+ ['MotorOff', ['windResistance', 'off']],
30
+ ['Rewind', ['rewind', 'on']],
31
+ ['ElectricCutOff', ['electricCutoff', 'on']]
32
+ ]);
33
+ /** Paper the grid runs on past the last hole, so that a final pedal release completes. */
34
+ const RUN_OUT_MM = 100;
35
+ /**
36
+ * Which stack of valves an expression perforation belongs to. The symbol
37
+ * usually says so, having been read off the tracker bar already; where it
38
+ * does not, the bar is asked again, since sending a perforation to neither
39
+ * side would quietly flatten the dynamics.
40
+ */
41
+ const scopeOf = (event) => {
42
+ if (event.scope)
43
+ return event.scope;
44
+ const meaning = welteT100.meaningOf(event.vertical.from);
45
+ return meaning?.type === 'expression' ? meaning.scope : undefined;
46
+ };
47
+ const isNote = (event) => event.type === 'note';
48
+ const isExpression = (event) => event.type === 'expression';
49
+ const rowOf = (mm) => mm * ROWS_PER_MM;
50
+ /** When the spool brings a place on the roll, in mm, to the tracker bar. */
51
+ export const secondsAt = (spool, mm) => paperSeconds(spool, mm / 10);
52
+ const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
53
+ const readingOf = (event) => {
54
+ const code = CODES.get(event.expressionType);
55
+ const half = scopeOf(event);
56
+ if (!code || !half)
57
+ return undefined;
58
+ const [control, action] = code;
59
+ return {
60
+ event,
61
+ punch: { half, control, action, rowOn: rowOf(event.horizontal.from), rowOff: rowOf(event.horizontal.to) }
62
+ };
63
+ };
64
+ const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
65
+ /**
66
+ * The velocity map joined linearly through its three anchors, with the
67
+ * middle one at the Mezzoforte pin of the half in question.
68
+ */
69
+ const velocityOf = (travel, hook, map) => {
70
+ const position = clamp(travel, 0, 1);
71
+ if (position <= hook) {
72
+ return map.piano + (position / hook) * (map.mezzoforte - map.piano);
73
+ }
74
+ return map.mezzoforte + ((position - hook) / (1 - hook)) * (map.forte - map.mezzoforte);
75
+ };
76
+ /**
77
+ * One sample per row of the scan the constants were fitted on, from the
78
+ * beginning of the roll to a little past the last hole. The rows are
79
+ * equally spaced on the paper and not in time, which is what the
80
+ * emulator expects.
81
+ */
82
+ const gridOver = (events, spool) => {
83
+ const lastMm = events.reduce((furthest, event) => Math.max(furthest, event.horizontal.to), 0);
84
+ const length = Math.ceil(rowOf(lastMm + RUN_OUT_MM)) + 1;
85
+ const seconds = Float64Array.from({ length }, (_, row) => secondsAt(spool, row / ROWS_PER_MM));
86
+ return new Grid(0, seconds);
87
+ };
88
+ const nuanceCurves = (grid, ports, samples, events, options) => {
89
+ const onsetRows = (half) => events
90
+ .filter(isNote)
91
+ .filter(note => halfOf(note, options.division) === half)
92
+ .map(note => rowOf(note.horizontal.from));
93
+ const density = {
94
+ bass: noteDensity(grid, onsetRows('bass')),
95
+ treble: noteDensity(grid, onsetRows('treble')),
96
+ };
97
+ const totalNoteDensity = Float64Array.from(density.bass, (value, index) => value + density.treble[index]);
98
+ const curveOf = (half) => {
99
+ const params = options.nuance[half];
100
+ const output = pneumaticModel.run({ grid, half, ports, noteDensity: density[half], totalNoteDensity }, params);
101
+ const travel = travelBetweenRails(output, params);
102
+ const hook = clamp(mezzoforteTravel(params), 0.01, 0.99);
103
+ return {
104
+ ...samples,
105
+ name: half,
106
+ kind: 'dynamics',
107
+ travel,
108
+ velocity: Float64Array.from(travel, value => velocityOf(value, hook, options.velocity))
109
+ };
110
+ };
111
+ return { bass: curveOf('bass'), treble: curveOf('treble') };
112
+ };
113
+ const pedalCurves = (grid, ports, samples, options) => {
114
+ const travel = runPedals({ grid, ports }, options.pedals);
115
+ return {
116
+ damper: { ...samples, name: 'damper', kind: 'pedal', travel: travel.damper },
117
+ hammerRail: { ...samples, name: 'hammerRail', kind: 'pedal', travel: travel.hammerRail }
118
+ };
119
+ };
120
+ const performNotes = (events, grid, nuance, options) => events
121
+ .filter(isNote)
122
+ .flatMap((note) => {
123
+ const curve = nuance[halfOf(note, options.division)];
124
+ const velocity = curve.velocity[grid.indexOfRow(rowOf(note.horizontal.from))];
125
+ return [
126
+ { type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
127
+ { type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
128
+ ];
129
+ });
130
+ /**
131
+ * The travel of one pedal as controller steps. Each step is attributed to
132
+ * the last perforation of that pedal the tracker bar has reached, which is
133
+ * the one whose reading it follows from.
134
+ */
135
+ const performPedal = (type, curve, grid, readings, mode) => {
136
+ if (readings.length === 0)
137
+ return [];
138
+ const ordered = readings.toSorted((a, b) => a.punch.rowOn - b.punch.rowOn);
139
+ const causeOf = (row) => ordered[Math.max(ordered.findLastIndex(reading => reading.punch.rowOn <= row), 0)].event;
140
+ return levelChanges(curve.travel, { mode })
141
+ .filter(change => change.index > 0)
142
+ .map(change => ({
143
+ type,
144
+ performs: causeOf(grid.rowAt(change.index)),
145
+ value: change.value,
146
+ at: curve.seconds[change.index]
147
+ }));
148
+ };
149
+ /**
150
+ * The edition's tempo adjustment is left aside: it is stated as a paper
151
+ * speed, and what the spool holds constant is its rate of revolution, so
152
+ * the two are not the same quantity. The spool in the options sets the speed.
153
+ */
154
+ const perform = (events, options, roll) => {
155
+ const readings = events
156
+ .filter(isExpression)
157
+ .map(readingOf)
158
+ .filter((reading) => reading !== undefined);
159
+ const grid = gridOver(events, options.spool);
160
+ const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
161
+ const ports = aperturePorts(grid, readings.map(reading => reading.punch), geometry);
162
+ const samples = {
163
+ place: Float64Array.from(grid.seconds, (_, row) => row / ROWS_PER_MM),
164
+ seconds: grid.seconds
165
+ };
166
+ const nuance = nuanceCurves(grid, ports, samples, events, options);
167
+ const pedals = pedalCurves(grid, ports, samples, options);
168
+ const readingsOf = (control) => readings.filter(reading => reading.punch.control === control);
169
+ return {
170
+ events: [
171
+ ...performNotes(events, grid, nuance, options),
172
+ ...performPedal('damper', pedals.damper, grid, readingsOf('sustainPedal'), options.pedalMode),
173
+ ...performPedal('hammerRail', pedals.hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
174
+ ],
175
+ curves: [nuance.bass, nuance.treble, pedals.damper, pedals.hammerRail]
176
+ };
177
+ };
178
+ /**
179
+ * The red Welte, as welte-t100-emulator models it: the take-up spool sets
180
+ * the time axis, the Nuancierbälge fill through their conduits and are
181
+ * arrested by the Mezzoforte pin, and the two pedals travel rather than
182
+ * switch. The constants are those fitted against the hand-drawn nuance
183
+ * lines of roll 3309, with the terms that describe the drawing apparatus
184
+ * switched off.
185
+ */
186
+ export const welteT100System = {
187
+ name: 'Welte-Mignon T100',
188
+ trackerBar: welteT100,
189
+ defaultOptions: defaultWelteT100Options,
190
+ perform
191
+ };
package/lib/utils.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Person } from "./Edition";
2
+ export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
3
+ export type WithType<T extends string> = {
4
+ /**
5
+ * The type discriminator for this object.
6
+ * @see rdf:type
7
+ */
8
+ readonly type: T;
9
+ };
10
+ export type WithId = {
11
+ /**
12
+ * A unique identifier for this object.
13
+ */
14
+ readonly id: string;
15
+ };
16
+ export type WithActor = {
17
+ /**
18
+ * The person who carried out this activity.
19
+ * @see crm:P14 carried out by
20
+ */
21
+ actor?: Person;
22
+ };
23
+ export type WithNote = {
24
+ /**
25
+ * A free-text note providing additional context.
26
+ * @see crm:P3 has note
27
+ */
28
+ note?: string;
29
+ };
@@ -0,0 +1,3 @@
1
+ import { Edition } from "./Edition";
2
+ declare const validate: import("ajv").ValidateFunction<Edition>;
3
+ export { validate };
@@ -0,0 +1,10 @@
1
+ import Ajv from "ajv";
2
+ import * as schema from "./schema.json";
3
+ const ajv = new Ajv({
4
+ strict: false,
5
+ formats: {
6
+ "date": true
7
+ }
8
+ });
9
+ const validate = ajv.compile(schema);
10
+ export { validate };