welte-mignon-emulator
v1.1.0
Published
The expression mechanism of a Welte-Mignon: the take-up spool, the nuancing bellows and the pedals, with the latching relay of the T-100 and the duration-coded relay of the T-98 in front of them.
Readme
Welte-Mignon Emulator
A few red Welte rolls of the late production period carry drawn expression lines (Nuancierlinien). According to Hans-W. Schmitz they were drawn onto the finished rolls by two pens coupled to the two Nuancierbälge while the roll was played back („Welte-Mignon und Hupfeld DEA", Das Mechanische Musikinstrument 19, 1981, pp. 4–5). Hermann Gottschewski's proposal at the 3rd Global Piano Roll Meeting 2024 was to read the rules of the mechanism off such lines and then emulate the lines by software. He observed several differences from common emulators such as SUPRA's midi2exp, for example that the Welte crescendos and diminuendos are far from linear. This emulator follows Gottschewski's proposal.
It derives the travel of the two Nuancierbälge from the punched expression code by modelling the pneumatics that move them: the valves the code operates, the conduits of different bore through which the bellows fills and empties, the wind chamber they draw on, and the Mezzoforte stop that arrests the travel. Their constants were fitted by differential evolution with a Nelder–Mead polish against the expression lines of six rolls, and scored on the blocks of each roll left out of the fit. The library ships the six different instrument settings as presets, named by Welte number, and beside them a consensus: one instrument fitted to all six lines at once. The consensus is the default.
This emulator also implements Gottschewski's model of roll acceleration (Die Interpretation als Kunstwerk, Laaber 1996, pp. 135–137) and makes an assumption about the pedals' travel based on the pneumatic description by Peter Hagmann (Das Welte-Mignon-Klavier, die Welte-Philharmonie-Orgel und die Anfänge der Reproduktion von Musik, Diss. Basel, Bern 1984, pp. 106–107).
Using it as a library
Node 24 or later, no runtime dependencies. The package is welte-mignon-emulator on npm, with
three entry points: . for the nuancing block both scales share, ./t100 for the red Welte's
latching relay and the instruments it was fitted as, and ./t98 for the green Welte's
duration-coded relay. Hagmann has the nuancing unit built the same on both scales (p. 96), so it
is written once and the two relays stand in front of it.
npm install # typescript and @types/node, for checking only
npm run check
npm test
npm run build # writes dist/, which is what the package exportsdist/ is committed, so a dependency on a checkout of this repository needs no build of its
own, which is how linked-rolls and the empirics
are developed against it:
"welte-mignon-emulator": "file:../welte-t100"Rebuild dist/ whenever src/ changes, and commit it. The empirics import the built
package, so an experiment on the model is an edit here, npm run build, and a run there.
src/t100/instruments.data.ts is generated by the empirics' src/cli/instruments.ts from
its fits and written into this tree; the same build and commit follow.
Releases go out through .github/workflows/publish.yml: raise the version in package.json,
commit, and push a tag v<version>, or run the workflow from the Actions tab.
Example
A SUPRA raw MIDI carries one tick per scan row and the roll's metadata. The emulator reads its bytes, sets the time axis from the spool law, turns the expression perforations into the open area of each tracker port row by row, and runs the two mechanisms on that grid. Nothing in the library reads a file itself.
import { readFileSync } from "node:fs";
import {
aperturePorts, Grid, instrumentParameters, perforations, pneumaticModel, readRoll,
runPedals, travelBetweenRails, type Half,
} from "welte-mignon-emulator/t100";
// The punched code as the open area of each tracker port, row by row, on the spool's time axis.
const roll = readRoll("jq774vx6544", readFileSync("jq774vx6544_raw.mid"));
const punches = perforations(roll);
const lastRow = Math.max(...punches.map((punch) => punch.rowOff));
const grid = Grid.overRows(roll.timing, roll.timing.rowAtTick(0), lastRow);
const ports = aperturePorts(grid, punches);
// Each half has its own bellows and its own constants: the consensus instrument by default,
// or { preset: "3309" } for the setting that drew one particular roll.
const travelOf = (half: Half) => {
const params = instrumentParameters(half);
return travelBetweenRails(pneumaticModel.run({ grid, half, ports }, params), params);
};
// Bellows travel per row: 0 fully open (P.P.), 1 fully closed (F.F.).
const nuance = {
bass: travelOf("bass"),
treble: travelOf("treble"),
};
// Damper and hammer rail per row, 0 at rest to 1 fully moved. The default has the dampers
// arrive in 180 ms; `pedalBrushing` slows the fall until the quick runs of the SUPRA corpus
// dip without damping, the other reading of those runs (see `src/core/pedal.ts`).
const pedals = runPedals({ grid, ports });A roll that is not a SUPRA scan needs only its punches in scan rows and a Grid of seconds,
which is how linked-rolls drives the emulator from an edition. geometryInMm puts a roll of
another punch diameter on the same grid, and levelChanges turns the pedal travel into
controller messages.
Layout
| | |
| --- | --- |
| src/index.ts | the scale-neutral surface, . |
| src/core/ | what both scales share: the take-up spool, the tracker-bar aperture, the relay valves, the flow law, the Mezzoforte stop, the nuancing bellows, the pedal mechanism, a MIDI reader and writer |
| src/t100/ | the red Welte, ./t100: the expression code, the latching relay, the SUPRA reader, and the instruments playback runs as; instruments.data.ts is generated from the fits |
| src/t98/ | the green Welte, ./t98: the expression code, the duration-coded relay, the rewind, and the instruments |
| docs/sources.md | what the sources say, by topic: Hagmann, Schmitz, Gottschewski, the patents, midi2exp and pianolatron |
The model
The state is the closure of the Nuancierbalg: 0 fully open, which sets the cone valve for the least vacuum and so the softest attack, 1 fully closed and loudest. Valves admit air to it or draw air out of it through conduits of different bore, and the speed of a bellows filling through a conduit depends on how far it still has to go:
dx/dt = Σ g · a · sign(T − x) · |T − x|^αone term per open path, with g the conductance of its conduit, a how far the tracker port
is open, T the position that path pulls towards, and α the exponent of the flow law.
α = 1 is a laminar throttle and gives an exponential approach, α = ½ an orifice, α = 0
a constant rate (which is what midi2exp and pianolatron assume). α is fitted rather than
chosen, so the family contains the prior art as a special case. The fitted values are in
src/t100/instruments.data.ts.
The two relays
The equation and everything after it are the same on both tracker scales, on Hagmann's authority that the nuancing unit is "von geringfügigen Unterschieden abgesehen, für beide Blockskalen gleich konstruiert" (p. 96) and because his Anhang 13 and 14 are the same drawing of parts 85–101 under two different relays. What differs is which paths are open, and when.
The T-100 puts a hold chamber above each "on" valve, so a short punch sets a function and it stands until its cancel line is read. That is claim 3 of DRP 162 708, whose purpose is stated as "daß man im Notenblatt nur kurze Öffnungen vorzusehen braucht, um lang anhaltende Wirkungen hervorzubringen". Three paths hang on it: conduit 39 switched between vacuum and atmosphere, conduit 23 under the sforzando, throttle 96 under the cancel.
The T-98 has no hold chamber on any of its four units, so "die Funktion bleibt genau so
lange ausgeführt, als die entsprechende Perforation im Notenband über die Gleitblock-Oeffnung
läuft" (pp. 100 f.). Four paths, and the fourth is the difference that matters: bore 100 is a
permanently open bleed to atmosphere, which Welte added because the crescendo's own throttle
cannot readmit air fast enough (Betriebsanleitung p. 13). It weakens the crescendo, so the
ceiling a slow crescendo reaches is the balance of the two screws rather than a fitted
asymptote, and the symmetry Welte requires of the crescendo and its decay falls out of the
structure instead of having to be imposed. Four conduits stand on one bellows and nothing
arbitrates between them, so opposed drives add as flows and the bellows goes where they
balance — neither midi2exp's arithmetic cancellation of constant velocity steps nor PlaySK's
forte-wins. A long perforation on the bass sforzando-piano line is the rewind, read a second
time by a sluggish dead-banded integrator on the same valve (Skala-Rolle 98 §10).
Nothing on the T-98 side is fitted. src/t98/instruments.ts carries a discriminated union of
genuine instruments, to be fitted to drawn green nuance lines, derived ones, to be fitted
so that a green code reproduces what the T-100 emulator makes of the red copy of the same
recording, and the unfitted starting values that ship in the meantime. The first two are
empty. The difference between a genuine and a derived instrument, in the printed ordinate both
scales share, is how far the transfer of a red reading onto the green mechanism succeeded, and
it is what the two exist for. Welte's own regulation controls run as tests in
src/t98/controls.test.ts and are a gate on any fitted result rather than a term in its
objective; several of them record what the starting values do against what Welte requires.
Observations
- Differently regulated instruments. Each of the six rolls fits about as well as 3309, with held-out errors between 0.017 and 0.053 of the scale, and a foreign roll's constants score two to three times worse than the roll's own. Re-registering the rails and the lead removes about a tenth of the excess.
- Bass sits below treble on all six rolls. Both the crescendo and the sforzando asymptotes are lower in the bass than in the treble on every roll. Under a sign test that is p = 0.03 each, uncorrected, with nine parameters examined, so it is suggestive at most. It points the same way as Hagemann's measurement that the discant dome travels further than the bass dome for the same change of vacuum (Das Mechanische Musikinstrument 80, 2001, p. 26).
- Fall is four times the rise. The sforzando release runs at about four times the
rate of the sforzando closing, on 51 and 81 clean episodes each way. Welte's own control 6c
requires the two increments equal, so the instruments that drew the lines did not satisfy it.
Against the line,
midi2exp's 300 ms fast crescendo is of the right order and its 400 ms fast decrescendo is six to seven times too slow. - Crescendo latch speeds the sforzando up. With the crescendo set, a sforzando closes faster by about 1.1 units/s. Conduit 39 admitting air whenever the crescendo is cancelled predicts about two thirds of that gap, and the rest grows with position. The model carries the remainder as a load the nuancing system puts on its own blower, which is a proposal rather than a reading, and the crescendo-off episodes it rests on are few.
- Mezzoforte stop rebounds. Arrivals at the hook faster than 5 units/s overshoot and swing back once, a damped bounce of period about 32 ms that has settled by 50 ms. The rails show nothing of the kind. Rests arriving from below lie above the rests arriving from above, the opposite of the rigid hook of finite thickness Schmitz draws, and what a stop that yields in the direction it is pushed would give. The rising rests are 13 and 7.
- Lead of line drifts along the roll. Gottschewski observed that the lines run ahead of the punches. Along the roll the lead drifts by up to a few rows per 100 000 rows of paper, in either direction, as often one way as the other, and often by different amounts in the two halves.
