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.
- package/README.md +54 -367
- package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
- package/lib/{constraints.js → analysis/constraints.js} +8 -7
- package/lib/analysis/ownPaper.d.ts +26 -0
- package/lib/analysis/ownPaper.js +41 -0
- package/lib/{reservations.d.ts → analysis/reservations.d.ts} +3 -3
- package/lib/{reservations.js → analysis/reservations.js} +3 -3
- package/lib/{sigla.d.ts → analysis/sigla.d.ts} +2 -2
- package/lib/{sigla.js → analysis/sigla.js} +4 -4
- package/lib/{witnesses.d.ts → analysis/witnesses.d.ts} +2 -2
- package/lib/{witnesses.js → analysis/witnesses.js} +2 -2
- package/lib/{Collation.d.ts → collation/Collation.d.ts} +4 -4
- package/lib/{Collation.js → collation/Collation.js} +4 -4
- package/lib/{alignment.d.ts → collation/alignment.d.ts} +4 -4
- package/lib/{alignment.js → collation/alignment.js} +10 -16
- package/lib/{scatter.d.ts → collation/scatter.d.ts} +4 -4
- package/lib/{scatter.js → collation/scatter.js} +3 -3
- package/lib/{statistics.d.ts → collation/statistics.d.ts} +1 -1
- package/lib/{statistics.js → collation/statistics.js} +2 -2
- package/lib/{substitution.d.ts → collation/substitution.d.ts} +9 -24
- package/lib/{substitution.js → collation/substitution.js} +13 -37
- package/lib/emulation/Emulation.d.ts +64 -0
- package/lib/emulation/Emulation.js +282 -0
- package/lib/emulation/negotiation.d.ts +14 -0
- package/lib/emulation/negotiation.js +20 -0
- package/lib/index.d.ts +43 -35
- package/lib/index.js +36 -35
- package/lib/{asJsonLd.d.ts → io/asJsonLd.d.ts} +1 -1
- package/lib/{asJsonLd.js → io/asJsonLd.js} +3 -3
- package/lib/{context.d.ts → io/context.d.ts} +4 -4
- package/lib/{context.js → io/context.js} +4 -4
- package/lib/{importJsonLd.d.ts → io/importJsonLd.d.ts} +1 -1
- package/lib/io/importJsonLd.js +128 -0
- package/lib/{migrate.js → io/migrate.js} +59 -55
- package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
- package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
- package/lib/{Edit.d.ts → model/Edit.d.ts} +1 -1
- package/lib/{Edition.d.ts → model/Edition.d.ts} +2 -2
- package/lib/{Feature.d.ts → model/Feature.d.ts} +1 -1
- package/lib/{FeatureSource.d.ts → model/FeatureSource.d.ts} +1 -1
- package/lib/{Perforator.d.ts → model/Perforator.d.ts} +1 -1
- package/lib/{RollCopy.d.ts → model/RollCopy.d.ts} +3 -10
- package/lib/{RollCopy.js → model/RollCopy.js} +3 -5
- package/lib/{Symbol.d.ts → model/Symbol.d.ts} +1 -1
- package/lib/{Version.d.ts → model/Version.d.ts} +10 -2
- package/lib/{Version.js → model/Version.js} +24 -1
- package/lib/{vocabulary.js → model/vocabulary.js} +2 -2
- package/lib/ops/beliefs.d.ts +9 -0
- package/lib/ops/beliefs.js +31 -0
- package/lib/ops/collation.d.ts +62 -0
- package/lib/ops/collation.js +228 -0
- package/lib/ops/commands.d.ts +9 -0
- package/lib/ops/commands.js +29 -0
- package/lib/ops/copies.d.ts +74 -0
- package/lib/ops/copies.js +135 -0
- package/lib/ops/draft.d.ts +59 -0
- package/lib/ops/draft.js +81 -0
- package/lib/ops/edits.d.ts +10 -0
- package/lib/ops/edits.js +82 -0
- package/lib/ops/features.d.ts +84 -0
- package/lib/ops/features.js +296 -0
- package/lib/ops/forget.d.ts +31 -0
- package/lib/ops/forget.js +110 -0
- package/lib/ops/immutable.d.ts +16 -0
- package/lib/ops/immutable.js +46 -0
- package/lib/ops/index.d.ts +13 -0
- package/lib/ops/index.js +7 -0
- package/lib/ops/versions.d.ts +33 -0
- package/lib/ops/versions.js +95 -0
- package/lib/readers/AtonParser.js +5 -7
- package/lib/readers/phillipsEroll.d.ts +3 -3
- package/lib/readers/phillipsEroll.js +4 -5
- package/lib/readers/spencerBar.d.ts +2 -2
- package/lib/readers/spencerBar.js +2 -3
- package/lib/readers/stanfordAton.d.ts +3 -3
- package/lib/readers/stanfordAton.js +2 -3
- package/lib/schema.json +0 -13
- package/lib/{ReproducingSystem.d.ts → systems/ReproducingSystem.d.ts} +3 -3
- package/lib/{TrackerBar.d.ts → systems/TrackerBar.d.ts} +48 -8
- package/lib/{TrackerBar.js → systems/TrackerBar.js} +11 -2
- package/lib/systems/index.d.ts +8 -2
- package/lib/systems/index.js +7 -1
- package/lib/systems/welte.d.ts +100 -0
- package/lib/systems/welte.js +119 -0
- package/lib/systems/welteLicensee/bar.d.ts +1 -1
- package/lib/systems/welteLicensee/bar.js +5 -2
- package/lib/systems/welteLicensee/system.d.ts +1 -1
- package/lib/systems/welteLicensee/system.js +1 -1
- package/lib/systems/welteT100/bar.d.ts +27 -1
- package/lib/systems/welteT100/bar.js +44 -2
- package/lib/systems/welteT100/system.d.ts +6 -39
- package/lib/systems/welteT100/system.js +19 -128
- package/lib/systems/welteT98/bar.d.ts +1 -1
- package/lib/systems/welteT98/bar.js +18 -2
- package/lib/systems/welteT98/system.d.ts +20 -38
- package/lib/systems/welteT98/system.js +20 -121
- package/lib/validate.d.ts +1 -1
- package/lib/{EditionView.d.ts → view/EditionView.d.ts} +17 -41
- package/lib/{EditionView.js → view/EditionView.js} +26 -75
- package/package.json +7 -2
- package/lib/Emulation.d.ts +0 -36
- package/lib/Emulation.js +0 -250
- package/lib/editionOps.d.ts +0 -260
- package/lib/editionOps.js +0 -1079
- package/lib/importJsonLd.js +0 -57
- /package/lib/{migrate.d.ts → io/migrate.d.ts} +0 -0
- /package/lib/{Agent.js → model/Agent.js} +0 -0
- /package/lib/{Assumption.js → model/Assumption.js} +0 -0
- /package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +0 -0
- /package/lib/{ConditionState.js → model/ConditionState.js} +0 -0
- /package/lib/{Edit.js → model/Edit.js} +0 -0
- /package/lib/{Edition.js → model/Edition.js} +0 -0
- /package/lib/{Feature.js → model/Feature.js} +0 -0
- /package/lib/{FeatureSource.js → model/FeatureSource.js} +0 -0
- /package/lib/{Perforator.js → model/Perforator.js} +0 -0
- /package/lib/{Quantity.d.ts → model/Quantity.d.ts} +0 -0
- /package/lib/{Quantity.js → model/Quantity.js} +0 -0
- /package/lib/{Symbol.js → model/Symbol.js} +0 -0
- /package/lib/{TrackCalibration.d.ts → model/TrackCalibration.d.ts} +0 -0
- /package/lib/{TrackCalibration.js → model/TrackCalibration.js} +0 -0
- /package/lib/{notes.d.ts → model/notes.d.ts} +0 -0
- /package/lib/{notes.js → model/notes.js} +0 -0
- /package/lib/{procedures.d.ts → model/procedures.d.ts} +0 -0
- /package/lib/{procedures.js → model/procedures.js} +0 -0
- /package/lib/{vocabulary.d.ts → model/vocabulary.d.ts} +0 -0
- /package/lib/{sorted.d.ts → shared/sorted.d.ts} +0 -0
- /package/lib/{sorted.js → shared/sorted.js} +0 -0
- /package/lib/{utils.d.ts → shared/utils.d.ts} +0 -0
- /package/lib/{utils.js → shared/utils.js} +0 -0
- /package/lib/{ReproducingSystem.js → systems/ReproducingSystem.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describeTrackerBar } from "
|
|
2
|
-
import { metersPerMinute, mm } from "../../Quantity.js";
|
|
1
|
+
import { describeTrackerBar } from "../TrackerBar.js";
|
|
2
|
+
import { metersPerMinute, mm } from "../../model/Quantity.js";
|
|
3
3
|
/**
|
|
4
4
|
* The commands of the Welte-Mignon T-100, as its tracker bar reads them.
|
|
5
5
|
*/
|
|
@@ -19,6 +19,46 @@ export const welteT100ExpressionTypes = [
|
|
|
19
19
|
'Rewind',
|
|
20
20
|
'ElectricCutOff'
|
|
21
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* What each T-100 command operates, so that it can be set against the
|
|
24
|
+
* T-98's words for the same function.
|
|
25
|
+
*
|
|
26
|
+
* This cannot be read off the names. The T-100 latches a function on
|
|
27
|
+
* with one perforation and cancels it with a second, while the T-98
|
|
28
|
+
* holds it for as long as one perforation lasts (Hagmann pp. 89 f. and
|
|
29
|
+
* 100–103; Phillips p. 121), and Welte renamed two of the functions
|
|
30
|
+
* between the scales: the red `SlowCrescendo` is the green `Crescendo`,
|
|
31
|
+
* and the red's single `Forzando` valve answers to two green ones that
|
|
32
|
+
* name the end they pull towards. So the correspondence is stated here
|
|
33
|
+
* rather than derived, and only two types that operate one function can
|
|
34
|
+
* stand for each other.
|
|
35
|
+
*
|
|
36
|
+
* `MotorOn`/`MotorOff`, `Rewind` and `ElectricCutOff` are left out on
|
|
37
|
+
* purpose: the green scale has no word for any of them, its motor switch
|
|
38
|
+
* being an automatic mercury contact (Skala-Rolle 98 §12) and its rewind
|
|
39
|
+
* riding on the bass sforzando-piano line. A red command of those kinds
|
|
40
|
+
* has no counterpart and stays a plain deletion.
|
|
41
|
+
*/
|
|
42
|
+
export const welteT100Operations = {
|
|
43
|
+
MezzoforteOn: { operates: 'mezzoforte', spelling: 'on', sided: true },
|
|
44
|
+
MezzoforteOff: { operates: 'mezzoforte', spelling: 'off', sided: true },
|
|
45
|
+
SlowCrescendoOn: { operates: 'crescendo', spelling: 'on', sided: true },
|
|
46
|
+
SlowCrescendoOff: { operates: 'crescendo', spelling: 'off', sided: true },
|
|
47
|
+
ForzandoOn: { operates: 'sforzando', spelling: 'on', sided: true },
|
|
48
|
+
ForzandoOff: { operates: 'sforzando', spelling: 'off', sided: true },
|
|
49
|
+
SustainPedalOn: { operates: 'sustainPedal', spelling: 'on', sided: false },
|
|
50
|
+
SustainPedalOff: { operates: 'sustainPedal', spelling: 'off', sided: false },
|
|
51
|
+
SoftPedalOn: { operates: 'softPedal', spelling: 'on', sided: false },
|
|
52
|
+
SoftPedalOff: { operates: 'softPedal', spelling: 'off', sided: false }
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A single added accent on the T-100: a valve latched on and off again
|
|
56
|
+
* straight away.
|
|
57
|
+
*/
|
|
58
|
+
export const welteT100Accents = [
|
|
59
|
+
['SlowCrescendoOn', 'SlowCrescendoOff'],
|
|
60
|
+
['ForzandoOn', 'ForzandoOff']
|
|
61
|
+
];
|
|
22
62
|
/**
|
|
23
63
|
* Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
|
|
24
64
|
*
|
|
@@ -34,6 +74,8 @@ export const welteT100 = describeTrackerBar({
|
|
|
34
74
|
trackCount: 100,
|
|
35
75
|
notes: { from: 11, to: 90, lowestPitch: 24 },
|
|
36
76
|
paperSpeed: { value: metersPerMinute(3), unit: 'm/min' },
|
|
77
|
+
operations: welteT100Operations,
|
|
78
|
+
accents: welteT100Accents,
|
|
37
79
|
expressions: new Map([
|
|
38
80
|
[1, 'MezzoforteOff'],
|
|
39
81
|
[2, 'MezzoforteOn'],
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Half, Parameters
|
|
1
|
+
import { Half, Parameters } from "welte-mignon-emulator";
|
|
2
2
|
import { Instrument, RollNumber } from "welte-mignon-emulator/t100";
|
|
3
|
-
import { NegotiatedEvent, Performance, ReproducingSystem, RollProperties } from "
|
|
4
|
-
import {
|
|
5
|
-
import { type VelocityMap } from "../velocity.js";
|
|
3
|
+
import { NegotiatedEvent, Performance, ReproducingSystem, RollProperties } from "../ReproducingSystem.js";
|
|
4
|
+
import { type WelteOptions } from "../welte.js";
|
|
6
5
|
export type { VelocityMap } from "../velocity.js";
|
|
6
|
+
export { secondsAt } from "../welte.js";
|
|
7
7
|
export type { Instrument } from "welte-mignon-emulator/t100";
|
|
8
8
|
export type InstrumentName = 'consensus' | RollNumber;
|
|
9
9
|
/**
|
|
@@ -34,48 +34,15 @@ export declare const pedalPresets: {
|
|
|
34
34
|
export type PedalPreset = keyof typeof pedalPresets;
|
|
35
35
|
/** The preset a set of pedal constants is, if it is one. */
|
|
36
36
|
export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* The take-up spool, which sets the time axis: it is held at a constant
|
|
40
|
-
* rate of revolution, so the paper runs faster as the spool fills.
|
|
41
|
-
*/
|
|
42
|
-
spool: Spool;
|
|
43
|
-
/** Constants of the nuancing mechanism, one set for each half of the keyboard: `nuanceOf` an instrument, or a set of one's own. */
|
|
44
|
-
nuance: Record<Half, Parameters>;
|
|
45
|
-
/** Constants of the two pedal actions, one of `pedalPresets` or a set of one's own. */
|
|
46
|
-
pedals: Parameters;
|
|
47
|
-
velocity: VelocityMap;
|
|
48
|
-
/**
|
|
49
|
-
* How the pedals go out: as a continuous controller carrying the travel
|
|
50
|
-
* of the bellows, or thresholded to the two values a switching renderer
|
|
51
|
-
* understands.
|
|
52
|
-
*/
|
|
53
|
-
pedalMode: PedalMode;
|
|
54
|
-
/** Diameter of the tracker-bar bore. */
|
|
55
|
-
trackerBore: Millimeters;
|
|
56
|
-
/** Punch diameter for an edition whose copies record none. */
|
|
57
|
-
punchDiameter: Millimeters;
|
|
58
|
-
/**
|
|
59
|
-
* The track at which the keyboard is divided, so that notes from
|
|
60
|
-
* here upwards follow the treble expression and the ones below it
|
|
61
|
-
* the bass. Which side an expression perforation itself belongs to
|
|
62
|
-
* is not decided here but read off the tracker bar.
|
|
63
|
-
*/
|
|
64
|
-
division: Track;
|
|
65
|
-
};
|
|
37
|
+
/** The T-100 takes the options every Welte system does; see `WelteOptions`. */
|
|
38
|
+
export type WelteT100Options = WelteOptions;
|
|
66
39
|
export declare const defaultWelteT100Options: WelteT100Options;
|
|
67
|
-
/** When the spool brings a place on the roll to the tracker bar. */
|
|
68
|
-
export declare const secondsAt: (spool: Spool, place: Millimeters) => Seconds;
|
|
69
40
|
/**
|
|
70
41
|
* The mechanism, given a way of naming the instrument on the curves it
|
|
71
42
|
* produces. The Licensee reads the same commands and is played by the same
|
|
72
43
|
* valves, so it shares this; what it may not share is the name, since a
|
|
73
44
|
* Licensee playback runs on constants fitted to Freiburg instruments and the
|
|
74
45
|
* curve has to say so.
|
|
75
|
-
*
|
|
76
|
-
* The edition's tempo adjustment is left aside: it is stated as a paper
|
|
77
|
-
* speed, and what the spool holds constant is its rate of revolution, so
|
|
78
|
-
* the two are not the same quantity. The spool in the options sets the speed.
|
|
79
46
|
*/
|
|
80
47
|
export declare const performAs: (instrumentOf: (nuance: Record<Half, Parameters>) => string) => (events: readonly NegotiatedEvent[], options: WelteT100Options, roll: RollProperties) => Performance;
|
|
81
48
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DEFAULT_PUNCH_MM,
|
|
2
|
-
import { aperturePorts, CONSENSUS,
|
|
1
|
+
import { DEFAULT_PUNCH_MM, pedalBrushing, pedalDefaults, TRACKER_BORE_MM, WELTE_SPOOL, } from "welte-mignon-emulator";
|
|
2
|
+
import { aperturePorts, CONSENSUS, pneumaticModel, PRESETS, runPedals, } from "welte-mignon-emulator/t100";
|
|
3
3
|
import { welteT100 } from "./bar.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { mm, track } from "../../model/Quantity.js";
|
|
5
|
+
import { defaultVelocityMap } from "../velocity.js";
|
|
6
|
+
import { performWelte, sameParameters } from "../welte.js";
|
|
7
|
+
export { secondsAt } from "../welte.js";
|
|
7
8
|
/**
|
|
8
9
|
* The instruments the emulator was fitted as: the consensus over the six
|
|
9
10
|
* rolls with drawn nuance lines, and the setting that drew each of them,
|
|
@@ -13,8 +14,6 @@ import { defaultVelocityMap, velocityOf } from "../velocity.js";
|
|
|
13
14
|
export const instruments = { consensus: CONSENSUS, ...PRESETS };
|
|
14
15
|
/** The instruments by name, the consensus first. */
|
|
15
16
|
export const instrumentNames = ['consensus', ...Object.keys(PRESETS)];
|
|
16
|
-
const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
|
|
17
|
-
&& Object.entries(a).every(([name, value]) => b[name] === value);
|
|
18
17
|
/** The nuancing constants of an instrument, one set for each half of the keyboard. */
|
|
19
18
|
export const nuanceOf = (instrument) => ({ bass: instrument.bass, treble: instrument.treble });
|
|
20
19
|
/** The instrument a pair of nuancing constants belongs to, if it is one. */
|
|
@@ -61,101 +60,12 @@ const CODES = {
|
|
|
61
60
|
ElectricCutOff: ['electricCutoff', 'on']
|
|
62
61
|
};
|
|
63
62
|
const isWelteT100ExpressionType = (type) => Object.hasOwn(CODES, type);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const isNote = (event) => event.type === 'note';
|
|
68
|
-
const isExpression = (event) => event.type === 'expression';
|
|
69
|
-
const paperOf = (toOwnPaper) => ({
|
|
70
|
-
rowOf: place => place * toOwnPaper * ROWS_PER_MM,
|
|
71
|
-
placeOfRow: row => mm(row / (toOwnPaper * ROWS_PER_MM)),
|
|
72
|
-
paperOfRow: row => mm(row / ROWS_PER_MM)
|
|
73
|
-
});
|
|
74
|
-
/** When the spool brings a place on the roll to the tracker bar. */
|
|
75
|
-
export const secondsAt = (spool, place) => seconds(paperSeconds(spool, inCentimeters(place)));
|
|
76
|
-
const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
|
|
77
|
-
const readingOf = (paper) => (event) => {
|
|
78
|
-
const code = codeOf(event.expressionType);
|
|
79
|
-
if (!code)
|
|
63
|
+
/** The punch a T-100 expression is read as: its control, latched on or off. */
|
|
64
|
+
const punchOf = (event, rows) => {
|
|
65
|
+
if (!isWelteT100ExpressionType(event.expressionType))
|
|
80
66
|
return undefined;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
event,
|
|
85
|
-
punch: {
|
|
86
|
-
half,
|
|
87
|
-
control,
|
|
88
|
-
action,
|
|
89
|
-
rowOn: paper.rowOf(event.horizontal.from),
|
|
90
|
-
rowOff: paper.rowOf(event.horizontal.to)
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
|
|
95
|
-
/**
|
|
96
|
-
* One sample per row of the scan the constants were fitted on, from the
|
|
97
|
-
* beginning of the roll to a little past the last hole. The rows are
|
|
98
|
-
* equally spaced on the paper and not in time, which is what the
|
|
99
|
-
* emulator expects.
|
|
100
|
-
*/
|
|
101
|
-
const gridOver = (events, spool, paper) => {
|
|
102
|
-
const last = mm(events.reduce((furthest, event) => Math.max(furthest, event.horizontal.to), 0));
|
|
103
|
-
const length = Math.ceil(paper.rowOf(last) + RUN_OUT * ROWS_PER_MM) + 1;
|
|
104
|
-
const times = new Float64Array(length).map((_, row) => secondsAt(spool, paper.paperOfRow(row)));
|
|
105
|
-
return new Grid(0, times);
|
|
106
|
-
};
|
|
107
|
-
const nuanceCurves = (grid, ports, samples, options, instrument) => {
|
|
108
|
-
const curveOf = (half) => {
|
|
109
|
-
const params = options.nuance[half];
|
|
110
|
-
const output = pneumaticModel.run({ grid, half, ports }, params);
|
|
111
|
-
const travel = travelBetweenRails(output, params);
|
|
112
|
-
const hook = clamp(mezzoforteTravel(params), 0.01, 0.99);
|
|
113
|
-
return {
|
|
114
|
-
...samples,
|
|
115
|
-
name: half,
|
|
116
|
-
kind: 'dynamics',
|
|
117
|
-
instrument,
|
|
118
|
-
travel,
|
|
119
|
-
velocity: travel.map(value => velocityOf(value, hook, options.velocity))
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
return { bass: curveOf('bass'), treble: curveOf('treble') };
|
|
123
|
-
};
|
|
124
|
-
const pedalCurves = (grid, ports, samples, options) => {
|
|
125
|
-
const travel = runPedals({ grid, ports }, options.pedals);
|
|
126
|
-
return {
|
|
127
|
-
damper: { ...samples, name: 'damper', kind: 'pedal', travel: travel.damper },
|
|
128
|
-
hammerRail: { ...samples, name: 'hammerRail', kind: 'pedal', travel: travel.hammerRail }
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
const performNotes = (events, grid, nuance, options, paper) => events
|
|
132
|
-
.filter(isNote)
|
|
133
|
-
.flatMap((note) => {
|
|
134
|
-
const curve = nuance[halfOf(note, options.division)];
|
|
135
|
-
const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
|
|
136
|
-
return [
|
|
137
|
-
{ type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
|
|
138
|
-
{ type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
|
|
139
|
-
];
|
|
140
|
-
});
|
|
141
|
-
/**
|
|
142
|
-
* The travel of one pedal as controller steps. Each step is attributed to
|
|
143
|
-
* the last perforation of that pedal the tracker bar has reached, which is
|
|
144
|
-
* the one whose reading it follows from.
|
|
145
|
-
*/
|
|
146
|
-
const performPedal = (type, curve, grid, readings, mode) => {
|
|
147
|
-
if (readings.length === 0)
|
|
148
|
-
return [];
|
|
149
|
-
const ordered = readings.toSorted((a, b) => a.punch.rowOn - b.punch.rowOn);
|
|
150
|
-
const causeOf = (row) => ordered[Math.max(partitionPoint(ordered, reading => reading.punch.rowOn <= row) - 1, 0)].event;
|
|
151
|
-
return levelChanges(curve.travel, { mode })
|
|
152
|
-
.filter(change => change.index > 0)
|
|
153
|
-
.map(change => ({
|
|
154
|
-
type,
|
|
155
|
-
performs: causeOf(grid.rowAt(change.index)),
|
|
156
|
-
value: change.value,
|
|
157
|
-
at: seconds(curve.seconds[change.index])
|
|
158
|
-
}));
|
|
67
|
+
const [control, action] = CODES[event.expressionType];
|
|
68
|
+
return { half: event.scope, control, action, ...rows };
|
|
159
69
|
};
|
|
160
70
|
/**
|
|
161
71
|
* The mechanism, given a way of naming the instrument on the curves it
|
|
@@ -163,35 +73,16 @@ const performPedal = (type, curve, grid, readings, mode) => {
|
|
|
163
73
|
* valves, so it shares this; what it may not share is the name, since a
|
|
164
74
|
* Licensee playback runs on constants fitted to Freiburg instruments and the
|
|
165
75
|
* curve has to say so.
|
|
166
|
-
*
|
|
167
|
-
* The edition's tempo adjustment is left aside: it is stated as a paper
|
|
168
|
-
* speed, and what the spool holds constant is its rate of revolution, so
|
|
169
|
-
* the two are not the same quantity. The spool in the options sets the speed.
|
|
170
76
|
*/
|
|
171
77
|
export const performAs = (instrumentOf) => (events, options, roll) => {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const samples = {
|
|
181
|
-
place: grid.seconds.map((_, row) => paper.placeOfRow(row)),
|
|
182
|
-
seconds: grid.seconds
|
|
183
|
-
};
|
|
184
|
-
const nuance = nuanceCurves(grid, ports, samples, options, instrumentOf(options.nuance));
|
|
185
|
-
const pedals = pedalCurves(grid, ports, samples, options);
|
|
186
|
-
const readingsOf = (control) => readings.filter(reading => reading.punch.control === control);
|
|
187
|
-
return {
|
|
188
|
-
events: [
|
|
189
|
-
...performNotes(events, grid, nuance, options, paper),
|
|
190
|
-
...performPedal('damper', pedals.damper, grid, readingsOf('sustainPedal'), options.pedalMode),
|
|
191
|
-
...performPedal('hammerRail', pedals.hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
|
|
192
|
-
],
|
|
193
|
-
curves: [nuance.bass, nuance.treble, pedals.damper, pedals.hammerRail]
|
|
194
|
-
};
|
|
78
|
+
const { events: performed, curves } = performWelte(events, options, roll, {
|
|
79
|
+
punchOf,
|
|
80
|
+
portsOf: aperturePorts,
|
|
81
|
+
model: pneumaticModel,
|
|
82
|
+
runPedals,
|
|
83
|
+
instrument: instrumentOf(options.nuance)
|
|
84
|
+
});
|
|
85
|
+
return { events: performed, curves };
|
|
195
86
|
};
|
|
196
87
|
/**
|
|
197
88
|
* The red Welte, as welte-mignon-emulator models it: the take-up spool sets
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describeTrackerBar } from "
|
|
2
|
-
import { metersPerMinute, mm } from "../../Quantity.js";
|
|
1
|
+
import { describeTrackerBar } from "../TrackerBar.js";
|
|
2
|
+
import { metersPerMinute, mm } from "../../model/Quantity.js";
|
|
3
3
|
/**
|
|
4
4
|
* The commands of the Welte-Mignon T-98, as its tracker bar reads them.
|
|
5
5
|
*
|
|
@@ -17,6 +17,19 @@ export const welteT98ExpressionTypes = [
|
|
|
17
17
|
'SustainPedal',
|
|
18
18
|
'SoftPedal'
|
|
19
19
|
];
|
|
20
|
+
/**
|
|
21
|
+
* What each T-98 command operates. Every one of them holds its function
|
|
22
|
+
* for as long as its perforation lasts; see `welteT100Operations` for
|
|
23
|
+
* how the functions answer to the T-100's.
|
|
24
|
+
*/
|
|
25
|
+
const welteT98Operations = {
|
|
26
|
+
Mezzoforte: { operates: 'mezzoforte', spelling: 'held', sided: true },
|
|
27
|
+
Crescendo: { operates: 'crescendo', spelling: 'held', sided: true },
|
|
28
|
+
SforzandoForte: { operates: 'sforzando', spelling: 'held', sided: true },
|
|
29
|
+
SforzandoPiano: { operates: 'sforzando', spelling: 'held', sided: true },
|
|
30
|
+
SustainPedal: { operates: 'sustainPedal', spelling: 'held', sided: false },
|
|
31
|
+
SoftPedal: { operates: 'softPedal', spelling: 'held', sided: false }
|
|
32
|
+
};
|
|
20
33
|
/**
|
|
21
34
|
* Welte-Mignon T-98 ("green Welte"), cf. Hagmann, Anhang 11 (p. 179)
|
|
22
35
|
* and Phillips, p. 121.
|
|
@@ -56,6 +69,9 @@ export const welteT98 = describeTrackerBar({
|
|
|
56
69
|
notes: { from: 6, to: 93, lowestPitch: 21 },
|
|
57
70
|
paperSpeed: { value: metersPerMinute(2.2), unit: 'm/min' },
|
|
58
71
|
rewindTrack: 1,
|
|
72
|
+
operations: welteT98Operations,
|
|
73
|
+
/** A single added accent on the T-98 is one held perforation. */
|
|
74
|
+
accents: [['Crescendo'], ['SforzandoForte']],
|
|
59
75
|
/**
|
|
60
76
|
* The T-98 gives the rewind no line of its own — Welte's Forzando P line
|
|
61
77
|
* doubles as it — so a perforation there is the rewind only when it is far
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Half, Parameters
|
|
1
|
+
import { Half, Parameters } from "welte-mignon-emulator";
|
|
2
2
|
import { instrumentT98Of, labelOf, nuanceOf, WelteT98InstrumentName } from "welte-mignon-emulator/t98";
|
|
3
|
-
import { ReproducingSystem } from "
|
|
4
|
-
import { Millimeters
|
|
5
|
-
import { type
|
|
3
|
+
import { ReproducingSystem } from "../ReproducingSystem.js";
|
|
4
|
+
import { Millimeters } from "../../model/Quantity.js";
|
|
5
|
+
import { type WelteOptions } from "../welte.js";
|
|
6
6
|
export type { VelocityMap } from "../velocity.js";
|
|
7
|
+
export { secondsAt } from "../welte.js";
|
|
7
8
|
export type { WelteT98Instrument, WelteT98InstrumentName } from "welte-mignon-emulator/t98";
|
|
8
9
|
/**
|
|
9
10
|
* The instruments the emulator offers for the green Welte, in three groups that
|
|
@@ -54,27 +55,22 @@ export declare const pedalPresets: {
|
|
|
54
55
|
};
|
|
55
56
|
export type PedalPreset = keyof typeof pedalPresets;
|
|
56
57
|
export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The options every Welte system takes (see `WelteOptions`), with what the
|
|
60
|
+
* T-98 adds. No source states a T-98 spool geometry, so the default keeps the
|
|
61
|
+
* red circumference and layer and sets the revolution to make the initial
|
|
62
|
+
* paper speed 220 cm/min; it is better varied than trusted, since it scales
|
|
63
|
+
* every conductance by k and every time constant by 1/k and touches nothing
|
|
64
|
+
* dimensionless. No source gives the T-98's tracker bore either, and the red
|
|
65
|
+
* figure is carried over. Welte puts the division between f♯ and g
|
|
66
|
+
* (Betriebsanleitung p. 7), which on this bar is track 52; PlaySK's green
|
|
67
|
+
* configuration independently gives the last bass note as MIDI 66. A green
|
|
68
|
+
* roll re-cut from a Mignon master uses only the middle 80 of the 88 note
|
|
69
|
+
* positions, so the division falls inside the used compass either way.
|
|
70
|
+
*/
|
|
71
|
+
export type WelteT98Options = WelteOptions & {
|
|
72
|
+
/** Which instrument the nuancing constants are, so that a curve can say so. */
|
|
69
73
|
instrument: WelteT98InstrumentName;
|
|
70
|
-
/** Constants of the two pedal actions, one of `pedalPresets` or a set of one's own. */
|
|
71
|
-
pedals: Parameters;
|
|
72
|
-
velocity: VelocityMap;
|
|
73
|
-
pedalMode: PedalMode;
|
|
74
|
-
/** Diameter of the tracker-bar bore. No source gives the T-98's; the red figure is carried over. */
|
|
75
|
-
trackerBore: Millimeters;
|
|
76
|
-
/** Punch diameter for an edition whose copies record none. */
|
|
77
|
-
punchDiameter: Millimeters;
|
|
78
74
|
/**
|
|
79
75
|
* Chained punches whose gap is shorter than this are one perforation. A held
|
|
80
76
|
* T-98 command is punched as a chain of round holes on a 2.66 mm grid with
|
|
@@ -83,18 +79,6 @@ export type WelteT98Options = {
|
|
|
83
79
|
* same port only if the chain is merged.
|
|
84
80
|
*/
|
|
85
81
|
chainGap: Millimeters;
|
|
86
|
-
/**
|
|
87
|
-
* The track at which the keyboard is divided, so that notes from here
|
|
88
|
-
* upwards follow the treble expression and the ones below it the bass.
|
|
89
|
-
* Welte puts the division between f♯ and g (Betriebsanleitung p. 7), which
|
|
90
|
-
* on this bar is track 52; PlaySK's green configuration independently gives
|
|
91
|
-
* the last bass note as MIDI 66. Which side an expression perforation
|
|
92
|
-
* belongs to is not decided here but read off the tracker bar.
|
|
93
|
-
*
|
|
94
|
-
* A green roll re-cut from a Mignon master uses only the middle 80 of the 88
|
|
95
|
-
* note positions, so the division falls inside the used compass either way.
|
|
96
|
-
*/
|
|
97
|
-
division: Track;
|
|
98
82
|
/**
|
|
99
83
|
* What a long perforation on the bass sforzando-piano line does. It is the
|
|
100
84
|
* same valve and the same hole as the dynamic (Skala-Rolle §10), so it
|
|
@@ -108,8 +92,6 @@ export type WelteT98Options = {
|
|
|
108
92
|
rewind: 'stop' | 'ignore';
|
|
109
93
|
};
|
|
110
94
|
export declare const defaultWelteT98Options: WelteT98Options;
|
|
111
|
-
/** When the spool brings a place on the roll to the tracker bar. */
|
|
112
|
-
export declare const secondsAt: (spool: Spool, place: Millimeters) => Seconds;
|
|
113
95
|
/**
|
|
114
96
|
* The green Welte, as welte-mignon-emulator models it. Everything downstream of
|
|
115
97
|
* the relay is the T-100's, on Hagmann's authority that the nuancing unit is
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pedalBrushing, pedalDefaults, ROWS_PER_MM, WELTE_T98_SPOOL, } from "welte-mignon-emulator";
|
|
2
2
|
import { aperturePorts, CHAIN_GAP_MM, DERIVED, GENUINE, instrumentT98Of, labelOf, nuanceOf, PUNCH_T98_MM, pneumaticT98Model, rewindAt, runPedals, sforzandoPianoLift, STARTING_VALUES, TRACKER_BORE_T98_MM, } from "welte-mignon-emulator/t98";
|
|
3
3
|
import { welteT98 } from "./bar.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { mm, seconds, track } from "../../model/Quantity.js";
|
|
5
|
+
import { defaultVelocityMap } from "../velocity.js";
|
|
6
|
+
import { performWelte, sameParameters } from "../welte.js";
|
|
7
|
+
export { secondsAt } from "../welte.js";
|
|
7
8
|
/**
|
|
8
9
|
* The instruments the emulator offers for the green Welte, in three groups that
|
|
9
10
|
* answer three different questions and must not be listed as one.
|
|
@@ -37,8 +38,6 @@ export const instrumentNames = [
|
|
|
37
38
|
...Object.keys(DERIVED).map(derived => ({ derived })),
|
|
38
39
|
{ unfitted: 'starting-values' }
|
|
39
40
|
];
|
|
40
|
-
const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
|
|
41
|
-
&& Object.entries(a).every(([name, value]) => b[name] === value);
|
|
42
41
|
/** The instrument a pair of nuancing constants belongs to, if it is one. */
|
|
43
42
|
export const instrumentNameOf = (nuance) => instrumentNames.find(name => {
|
|
44
43
|
const instrument = instrumentT98Of(name);
|
|
@@ -86,98 +85,10 @@ const CODES = {
|
|
|
86
85
|
SoftPedal: 'hammerRail'
|
|
87
86
|
};
|
|
88
87
|
const isWelteT98ExpressionType = (type) => Object.hasOwn(CODES, type);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const isExpression = (event) => event.type === 'expression';
|
|
94
|
-
const paperOf = (toOwnPaper) => ({
|
|
95
|
-
rowOf: place => place * toOwnPaper * ROWS_PER_MM,
|
|
96
|
-
placeOfRow: row => mm(row / (toOwnPaper * ROWS_PER_MM)),
|
|
97
|
-
paperOfRow: row => mm(row / ROWS_PER_MM)
|
|
98
|
-
});
|
|
99
|
-
/** When the spool brings a place on the roll to the tracker bar. */
|
|
100
|
-
export const secondsAt = (spool, place) => seconds(paperSeconds(spool, inCentimeters(place)));
|
|
101
|
-
const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
|
|
102
|
-
const readingOf = (paper) => (event) => {
|
|
103
|
-
const control = codeOf(event.expressionType);
|
|
104
|
-
if (!control)
|
|
105
|
-
return undefined;
|
|
106
|
-
const half = event.scope;
|
|
107
|
-
return {
|
|
108
|
-
event,
|
|
109
|
-
punch: {
|
|
110
|
-
half,
|
|
111
|
-
control,
|
|
112
|
-
rowOn: paper.rowOf(event.horizontal.from),
|
|
113
|
-
rowOff: paper.rowOf(event.horizontal.to)
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
|
|
118
|
-
/**
|
|
119
|
-
* One sample per row of the scan the constants were fitted on, from the
|
|
120
|
-
* beginning of the roll to a little past the last hole.
|
|
121
|
-
*/
|
|
122
|
-
const gridOver = (events, spool, paper) => {
|
|
123
|
-
const last = mm(events.reduce((furthest, event) => Math.max(furthest, event.horizontal.to), 0));
|
|
124
|
-
const length = Math.ceil(paper.rowOf(last) + RUN_OUT * ROWS_PER_MM) + 1;
|
|
125
|
-
const times = new Float64Array(length).map((_, row) => secondsAt(spool, paper.paperOfRow(row)));
|
|
126
|
-
return new Grid(0, times);
|
|
127
|
-
};
|
|
128
|
-
const nuanceCurves = (grid, ports, samples, options) => {
|
|
129
|
-
const instrument = `Welte-Mignon T-98, ${labelOf(options.instrument)}`;
|
|
130
|
-
const curveOf = (half) => {
|
|
131
|
-
const params = options.nuance[half];
|
|
132
|
-
const output = pneumaticT98Model.run({ grid, half, ports }, params);
|
|
133
|
-
const travel = travelBetweenRails(output, params);
|
|
134
|
-
const hook = clamp(mezzoforteTravel(params), 0.01, 0.99);
|
|
135
|
-
return {
|
|
136
|
-
...samples,
|
|
137
|
-
name: half,
|
|
138
|
-
kind: 'dynamics',
|
|
139
|
-
instrument,
|
|
140
|
-
travel,
|
|
141
|
-
velocity: travel.map(value => velocityOf(value, hook, options.velocity))
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
return { bass: curveOf('bass'), treble: curveOf('treble') };
|
|
145
|
-
};
|
|
146
|
-
const pedalCurves = (grid, ports, samples, options) => {
|
|
147
|
-
const travel = runPedals({ grid, ports }, options.pedals);
|
|
148
|
-
return {
|
|
149
|
-
damper: { ...samples, name: 'damper', kind: 'pedal', travel: travel.damper },
|
|
150
|
-
hammerRail: { ...samples, name: 'hammerRail', kind: 'pedal', travel: travel.hammerRail }
|
|
151
|
-
};
|
|
152
|
-
};
|
|
153
|
-
const performNotes = (events, grid, nuance, options, paper) => events
|
|
154
|
-
.filter(isNote)
|
|
155
|
-
.flatMap((note) => {
|
|
156
|
-
const curve = nuance[halfOf(note, options.division)];
|
|
157
|
-
const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
|
|
158
|
-
return [
|
|
159
|
-
{ type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
|
|
160
|
-
{ type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
|
|
161
|
-
];
|
|
162
|
-
});
|
|
163
|
-
/**
|
|
164
|
-
* The travel of one pedal as controller steps, each attributed to the last
|
|
165
|
-
* perforation of that pedal the tracker bar has reached.
|
|
166
|
-
*/
|
|
167
|
-
const performPedal = (type, curve, grid, readings, mode) => {
|
|
168
|
-
if (readings.length === 0)
|
|
169
|
-
return [];
|
|
170
|
-
const ordered = readings.toSorted((a, b) => a.punch.rowOn - b.punch.rowOn);
|
|
171
|
-
const causeOf = (row) => ordered[Math.max(partitionPoint(ordered, reading => reading.punch.rowOn <= row) - 1, 0)].event;
|
|
172
|
-
return levelChanges(curve.travel, { mode })
|
|
173
|
-
.filter(change => change.index > 0)
|
|
174
|
-
.map(change => ({
|
|
175
|
-
type,
|
|
176
|
-
performs: causeOf(grid.rowAt(change.index)),
|
|
177
|
-
value: change.value,
|
|
178
|
-
at: seconds(curve.seconds[change.index])
|
|
179
|
-
}));
|
|
180
|
-
};
|
|
88
|
+
/** The punch a T-98 expression is read as: its control, held for as long as it runs. */
|
|
89
|
+
const punchOf = (event, rows) => isWelteT98ExpressionType(event.expressionType)
|
|
90
|
+
? { half: event.scope, control: CODES[event.expressionType], ...rows }
|
|
91
|
+
: undefined;
|
|
181
92
|
/**
|
|
182
93
|
* The row at which the Abstellbalg trips and the roll goes back, or the end of
|
|
183
94
|
* the grid where it never does. The shut-off hangs on the bass sforzando-piano
|
|
@@ -199,36 +110,24 @@ const cutCurve = (curve, rows) => ({
|
|
|
199
110
|
...(curve.kind === 'dynamics' ? { velocity: curve.velocity.slice(0, rows) } : {})
|
|
200
111
|
});
|
|
201
112
|
/**
|
|
202
|
-
* The
|
|
203
|
-
*
|
|
113
|
+
* The shared Welte performance, cut off where the rewind takes hold unless
|
|
114
|
+
* the options ignore it.
|
|
204
115
|
*/
|
|
205
116
|
const perform = (events, options, roll) => {
|
|
206
|
-
const paper = paperOf(roll.toOwnPaper ?? 1);
|
|
207
|
-
const readings = events
|
|
208
|
-
.filter(isExpression)
|
|
209
|
-
.map(readingOf(paper))
|
|
210
|
-
.filter((reading) => reading !== undefined);
|
|
211
|
-
const grid = gridOver(events, options.spool, paper);
|
|
212
|
-
const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
|
|
213
117
|
const gap = options.chainGap * ROWS_PER_MM;
|
|
214
|
-
const ports
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const readingsOf = (control) => readings.filter(reading => reading.punch.control === control);
|
|
118
|
+
const { grid, ports, events: performed, curves } = performWelte(events, options, roll, {
|
|
119
|
+
punchOf,
|
|
120
|
+
portsOf: (grid, punches, geometry) => aperturePorts(grid, punches, geometry, gap),
|
|
121
|
+
model: pneumaticT98Model,
|
|
122
|
+
runPedals,
|
|
123
|
+
instrument: `Welte-Mignon T-98, ${labelOf(options.instrument)}`
|
|
124
|
+
});
|
|
222
125
|
const stops = rewindRow(grid, ports, options);
|
|
223
126
|
const until = seconds(grid.seconds[Math.min(stops, grid.length - 1)]);
|
|
224
127
|
const rows = Math.min(stops + 1, grid.length);
|
|
225
128
|
return {
|
|
226
|
-
events: truncated(
|
|
227
|
-
|
|
228
|
-
...performPedal('damper', pedals.damper, grid, readingsOf('sustainPedal'), options.pedalMode),
|
|
229
|
-
...performPedal('hammerRail', pedals.hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
|
|
230
|
-
], until),
|
|
231
|
-
curves: [nuance.bass, nuance.treble, pedals.damper, pedals.hammerRail].map(curve => cutCurve(curve, rows))
|
|
129
|
+
events: truncated(performed, until),
|
|
130
|
+
curves: curves.map(curve => cutCurve(curve, rows))
|
|
232
131
|
};
|
|
233
132
|
};
|
|
234
133
|
/**
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ErrorObject } from "ajv";
|
|
2
|
-
import { Edition } from "./Edition.js";
|
|
2
|
+
import { Edition } from "./model/Edition.js";
|
|
3
3
|
/**
|
|
4
4
|
* Holds a document against the edition schema. `errors` says what the
|
|
5
5
|
* last call found wrong, and is empty where it found nothing.
|