linked-rolls 0.56.0 → 0.58.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 (131) hide show
  1. package/README.md +54 -361
  2. package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
  3. package/lib/{constraints.js → analysis/constraints.js} +9 -8
  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} +4 -4
  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} +6 -6
  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} +22 -51
  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.d.ts → io/migrate.d.ts} +9 -0
  35. package/lib/{migrate.js → io/migrate.js} +97 -54
  36. package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
  37. package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
  38. package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +1 -2
  39. package/lib/{Edit.d.ts → model/Edit.d.ts} +1 -1
  40. package/lib/{Edition.d.ts → model/Edition.d.ts} +2 -2
  41. package/lib/{Feature.d.ts → model/Feature.d.ts} +3 -1
  42. package/lib/{Feature.js → model/Feature.js} +2 -0
  43. package/lib/{FeatureSource.d.ts → model/FeatureSource.d.ts} +1 -1
  44. package/lib/{Perforator.d.ts → model/Perforator.d.ts} +1 -1
  45. package/lib/{RollCopy.d.ts → model/RollCopy.d.ts} +5 -12
  46. package/lib/{RollCopy.js → model/RollCopy.js} +3 -5
  47. package/lib/{Symbol.d.ts → model/Symbol.d.ts} +1 -1
  48. package/lib/{Version.d.ts → model/Version.d.ts} +11 -3
  49. package/lib/{Version.js → model/Version.js} +24 -1
  50. package/lib/{vocabulary.js → model/vocabulary.js} +2 -2
  51. package/lib/ops/beliefs.d.ts +9 -0
  52. package/lib/ops/beliefs.js +31 -0
  53. package/lib/ops/collation.d.ts +62 -0
  54. package/lib/ops/collation.js +228 -0
  55. package/lib/ops/commands.d.ts +9 -0
  56. package/lib/ops/commands.js +29 -0
  57. package/lib/ops/copies.d.ts +74 -0
  58. package/lib/ops/copies.js +135 -0
  59. package/lib/ops/draft.d.ts +59 -0
  60. package/lib/ops/draft.js +81 -0
  61. package/lib/ops/edits.d.ts +10 -0
  62. package/lib/ops/edits.js +82 -0
  63. package/lib/ops/features.d.ts +84 -0
  64. package/lib/ops/features.js +296 -0
  65. package/lib/ops/forget.d.ts +31 -0
  66. package/lib/ops/forget.js +110 -0
  67. package/lib/ops/immutable.d.ts +16 -0
  68. package/lib/ops/immutable.js +46 -0
  69. package/lib/ops/index.d.ts +13 -0
  70. package/lib/ops/index.js +7 -0
  71. package/lib/ops/versions.d.ts +33 -0
  72. package/lib/ops/versions.js +95 -0
  73. package/lib/readers/AtonParser.js +5 -7
  74. package/lib/readers/phillipsEroll.d.ts +3 -3
  75. package/lib/readers/phillipsEroll.js +4 -5
  76. package/lib/readers/spencerBar.d.ts +2 -2
  77. package/lib/readers/spencerBar.js +2 -3
  78. package/lib/readers/stanfordAton.d.ts +3 -3
  79. package/lib/readers/stanfordAton.js +2 -4
  80. package/lib/schema.json +11 -151
  81. package/lib/spec/context.json +2 -12
  82. package/lib/{ReproducingSystem.d.ts → systems/ReproducingSystem.d.ts} +3 -3
  83. package/lib/{TrackerBar.d.ts → systems/TrackerBar.d.ts} +48 -8
  84. package/lib/{TrackerBar.js → systems/TrackerBar.js} +11 -2
  85. package/lib/systems/index.d.ts +8 -2
  86. package/lib/systems/index.js +7 -1
  87. package/lib/systems/welte.d.ts +100 -0
  88. package/lib/systems/welte.js +119 -0
  89. package/lib/systems/welteLicensee/bar.d.ts +1 -1
  90. package/lib/systems/welteLicensee/bar.js +5 -2
  91. package/lib/systems/welteLicensee/system.d.ts +1 -1
  92. package/lib/systems/welteLicensee/system.js +1 -1
  93. package/lib/systems/welteT100/bar.d.ts +27 -1
  94. package/lib/systems/welteT100/bar.js +44 -2
  95. package/lib/systems/welteT100/system.d.ts +6 -39
  96. package/lib/systems/welteT100/system.js +19 -128
  97. package/lib/systems/welteT98/bar.d.ts +1 -1
  98. package/lib/systems/welteT98/bar.js +18 -2
  99. package/lib/systems/welteT98/system.d.ts +20 -38
  100. package/lib/systems/welteT98/system.js +20 -121
  101. package/lib/validate.d.ts +1 -1
  102. package/lib/{EditionView.d.ts → view/EditionView.d.ts} +38 -48
  103. package/lib/{EditionView.js → view/EditionView.js} +90 -96
  104. package/package.json +7 -2
  105. package/lib/Emulation.d.ts +0 -36
  106. package/lib/Emulation.js +0 -250
  107. package/lib/editionOps.d.ts +0 -260
  108. package/lib/editionOps.js +0 -1081
  109. package/lib/importJsonLd.js +0 -62
  110. /package/lib/{Agent.js → model/Agent.js} +0 -0
  111. /package/lib/{Assumption.js → model/Assumption.js} +0 -0
  112. /package/lib/{ConditionState.js → model/ConditionState.js} +0 -0
  113. /package/lib/{Edit.js → model/Edit.js} +0 -0
  114. /package/lib/{Edition.js → model/Edition.js} +0 -0
  115. /package/lib/{FeatureSource.js → model/FeatureSource.js} +0 -0
  116. /package/lib/{Perforator.js → model/Perforator.js} +0 -0
  117. /package/lib/{Quantity.d.ts → model/Quantity.d.ts} +0 -0
  118. /package/lib/{Quantity.js → model/Quantity.js} +0 -0
  119. /package/lib/{Symbol.js → model/Symbol.js} +0 -0
  120. /package/lib/{TrackCalibration.d.ts → model/TrackCalibration.d.ts} +0 -0
  121. /package/lib/{TrackCalibration.js → model/TrackCalibration.js} +0 -0
  122. /package/lib/{notes.d.ts → model/notes.d.ts} +0 -0
  123. /package/lib/{notes.js → model/notes.js} +0 -0
  124. /package/lib/{procedures.d.ts → model/procedures.d.ts} +0 -0
  125. /package/lib/{procedures.js → model/procedures.js} +0 -0
  126. /package/lib/{vocabulary.d.ts → model/vocabulary.d.ts} +0 -0
  127. /package/lib/{sorted.d.ts → shared/sorted.d.ts} +0 -0
  128. /package/lib/{sorted.js → shared/sorted.js} +0 -0
  129. /package/lib/{utils.d.ts → shared/utils.d.ts} +0 -0
  130. /package/lib/{utils.js → shared/utils.js} +0 -0
  131. /package/lib/{ReproducingSystem.js → systems/ReproducingSystem.js} +0 -0
package/README.md CHANGED
@@ -1,396 +1,89 @@
1
1
  # linked-rolls
2
2
 
3
- linked-rolls is a lightweight Typescript library for creating, manipulating
4
- and exporting digital editions of piano roll. In particular it allows to:
3
+ A TypeScript library for digital editions of piano rolls. It reads roll
4
+ copies from scans and analysis files, collates them into versions,
5
+ records editorial assumptions together with the beliefs behind them,
6
+ exports the edition as JSON-LD, and plays a version through an emulated
7
+ reproducing piano.
5
8
 
6
- - import piano rolls from different formats (such
7
- as SUPRA's roll analysis files, DSP, etc.)
8
- - collate differing piano roll copies
9
- - create an manipulate editorial assumptions
10
- - export the edition as JSON-LD (based on the
11
- [Roll Edition Format](https://pfefferniels.github.io/linked-rolls/))
9
+ It is the foundation of the [Roll Desk](https://github.com/pfefferniels/roll-desk).
12
10
 
13
- This library is the foundation of the
14
- [Roll Desk](https://github.com/pfefferniels/roll-desk).
11
+ ## Install
15
12
 
16
- ## Vocabulary
17
-
18
- The JSON-LD export uses the Roll Edition Ontology (REO), namespace
19
- `https://w3id.org/reo/`, prefix `reo`, alongside CIDOC CRM, LRMoo and
20
- CRMinf. The ontology lives in `ontology/`, and the context every
21
- export references is `https://w3id.org/reo/context.jsonld`. The
22
- ontology, its type vocabulary and the context are published at
23
- https://pfefferniels.github.io/linked-rolls/reo/; the w3id.org
24
- identifiers are not registered yet. `ontology/README.md` records the
25
- naming decisions.
26
-
27
- Beliefs travel as JSON-LD-star annotations (`@annotation`), which state
28
- the triple they annotate. A reference the edition holds possible,
29
- unlikely or false is therefore written as an embedded node beside the
30
- document: RDF names the statement without stating it, and
31
- `importJsonLd` puts it back where it stood. A doubted value, such as a
32
- date or an attribution, stays annotated in place for now. Reading the
33
- beliefs needs a processor that implements JSON-LD-star, such as the
34
- Ruby `json-ld` gem; jsonld.js ignores `@annotation`, and every belief
35
- with it.
36
-
37
- ## Format revisions
38
-
39
- Files written by linked-rolls 0.1 load unchanged: `importJsonLd`
40
- recognises their shapes and brings them to the current format, in
41
- which versions and conditions carry a typology key beside their type,
42
- the keeper and the production metadata are nodes with a name and
43
- authority links, and the roll names its reproducing system. A date
44
- written as a value of its own is read as the day the event falls
45
- within. A copy that held its features in one list, naming by id what a
46
- modification had added, has each feature put into the act that brought
47
- it about, and a feature a patch bears that was left unnamed is named
48
- after the patch. Exports are always in the current format.
49
-
50
- ## How a date is stated
51
-
52
- A date is the time-span of the event rather than a value on it.
53
- `within` gives the day it falls within, and `after` and `before` give
54
- the bounds where nobody can give the day. A bound the edition does not
55
- know is left out, so `after` alone says "not before". In RDF the three
56
- become `P82 at some time within`, `P82a begin of the begin` and `P82b
57
- end of the end` on the `E52 Time-Span` the event has. `assignDate`,
58
- `notBefore` and `notAfter` build the three shapes, and `dateOf`,
59
- `earliestOf` and `latestOf` read them.
60
-
61
- Note that `before` and `after` mean something else on a command,
62
- where they order two onsets. The context defines them for a date
63
- within the date itself, so the two never meet.
64
-
65
- ## What a version derives from
66
-
67
- A version names the versions it is held to derive from in `basedOn`,
68
- each under the belief it rests on. Its text is read against the
69
- principal derivation, the first of those held most certain, and one
70
- held unlikely or false never gives the text. The others stand as
71
- hypotheses, such as a contamination: `stateDerivation` adds one and
72
- `clearDerivation` takes it back. A version may leave out its `edits`
73
- where nobody can state them, as for a text that only a recording hints
74
- at. It then reads as the version it derives from, and
75
- `reservationsAboutVersion` says so. A file that names a single
76
- derivation loads as a list of one.
77
-
78
- What a version is called is not stored with it. `siglaOf` reads the
79
- sigla off the stemma as it stands, so a label never outlives the
80
- arrangement it describes, and nothing should cite one without saying
81
- which state of the edition it belongs to.
82
-
83
- Handed an `EditionView`, which knows the copies as well, `siglaOf`
84
- lowercases the versions no copy's features carry at first hand, as
85
- editions mark a state nothing surviving shows: r3 stands to R3 as a
86
- reconstructed state stands to a witnessed one. A version reached only
87
- through the versions derived from it is lowercased, and so is one a
88
- copy does no more than state it carries, since a statement gives no
89
- readings. `attestedVersions` reports the same fact on its own. Handed
90
- the versions alone, `siglaOf` cannot tell and leaves every siglum in
91
- capitals.
92
-
93
- ## The tolerance a derivation was collated at
94
-
95
- `collationTolerance` on a derivation is the window two readings of one
96
- symbol must fall in to be collated: how far apart they may lie at
97
- either end, and, since two copies differ by a systematic offset as well
98
- as by scatter, where that window is centred. A window naming no offset
99
- is centred on nothing, which is what every window written before the
100
- offset was held here means.
101
-
102
- The number decides what counts as a reading at all, so it is worth
103
- measuring rather than picking. `scatterOfCopy` measures how far one
104
- copy puts each symbol from where the copies collated with it put it,
105
- and `scatterOf` describes that sample: its median, its scatter as a
106
- median absolute deviation scaled to a standard deviation, a histogram
107
- with the normal curve to lay over it, and the readings the curve does
108
- not account for. The scatter is taken from the median because the
109
- displacements an editor is looking for are in the sample, and a
110
- standard deviation would grow towards them until they no longer stood
111
- out.
112
-
113
- The copies named are one **side** of the comparison and the rest are
114
- the other, so which copies to name is the first question and the
115
- edition no longer answers it: a collation hands a child's carriers up
116
- to the parent's symbols, so a descendant's copies come to bear an
117
- ancestor's readings and look like its own. `sidesOf` reads the sides
118
- off the edits instead, the copies bearing what a version inserts
119
- standing for the reading it moves to and those bearing what it strikes
120
- for the reading it moves from. Naming a set that is not a side measures
121
- one copy's noise against a mixture of both sides, which is wider than
122
- the truth.
123
-
124
- The tolerance that follows is the scatter times a threshold fixed by
125
- the size of the sample, the point beyond which fewer than one reading
126
- is expected to fall by chance (`departureThreshold`). For the few
127
- hundred readings an edge of a stemma yields this lands a little above
128
- three. `toleranceAcross` covers several samples with one window, since
129
- a derivation states one tolerance while notes and expressions scatter
130
- differently. It asks which side was measured, because a window is
131
- signed: the offset runs from the parent to the child, and one stored
132
- the other way round has the right width and the wrong centre, so it
133
- separates readings that belong together and merges readings that do
134
- not while nothing in the number looks wrong. `inferredTolerance` builds
135
- the belief the number rests on, to annotate `collationTolerance` with. It is held likely and not
136
- true: the tolerance follows from the sample only as far as the sample
137
- is normal, which is what the reported excess kurtosis and tail counts
138
- are there to show.
139
-
140
- Two limits are worth naming. Notes and expressions are estimated apart
141
- by default, which is a stopgap standing in for a skew across the width
142
- of the paper rather than a distinction in the model, so the grouping is
143
- a parameter of the estimator and appears nowhere in the format. And the
144
- method screens: it says which readings the curve does not account for,
145
- and an editor says which of those are editorial acts.
146
-
147
- A separation reports which end made it (`separatedBy`, and
148
- `admittedAtEnds` for a collation). The two ends answer different
149
- questions: the onset, with the kind, decides whether two copies read one
150
- command, and the end decides whether that command was lengthened or
151
- shortened. On welte225.org the end alone decides 24 collations of 2839
152
- and the onset alone 39, so neither test is idle, but a difference at the
153
- end is as often a punch measured badly as a punch genuinely prolonged,
154
- and the two are not worth confusing.
155
-
156
- ## What a reader adds to a chain of holes
157
-
158
- A copy read pneumatically rather than scanned reports how long a valve
159
- stayed open, and a valve is held on past the perforation that opened it,
160
- so the chains it reports run longer than the punched ones while their
161
- onsets agree. Left in, that is not a difference between copies: a
162
- collation comparing such a copy's chain ends against a scanned copy's
163
- compares an on-time with a slot, and reads the one as a lengthening of
164
- the other.
165
-
166
- `shortenChains` takes the extension off and records it on the copy,
167
- `revertShortening` puts it back, and `readFromPhillipsEroll` will take
168
- it off at import. Only chains of holes are touched, what a writing spans
169
- being no valve. The figure is measured against the other copies rather than
170
- derived from the bar, since Phillips gives only the difference between
171
- two bars and not the absolute.
172
-
173
- On welte225.org that figure is about 1.6 mm, and its shape is simple. It
174
- does not grow with the length of the perforation: the ratio of the read
175
- length to the true one falls from 1.34 on the shortest punches to 1.01
176
- on the longest, which is what a constant added to everything does. There
177
- is no shortest on-time the valve cannot fall below. It shows no run
178
- along the roll, though one roll's span of paper speed is too narrow to
179
- tell a constant in millimetres from a constant in time. What it does
180
- vary with is the port that read it, by about half a millimetre either
181
- way and with no gradient across the bar, which is measured but too
182
- coarse to model from a single roll.
183
-
184
- ## Collating a derivation a second time
185
-
186
- A collated symbol is one symbol carrying every copy that reads it, so a
187
- collation cannot simply be run again: there is no second symbol left to
188
- match. `separateReadings` is the inverse. It takes the carriers of one
189
- side back off the symbols it shares, gives them to new symbols of the
190
- version's own, and states the exchange, leaving the edition in the state
191
- a collation would have produced had nothing joined. `connectVersions` at
192
- a new tolerance then re-collates, joining again whatever the new window
193
- admits.
194
-
195
- It takes a side and not a copy, the same side `sidesOf` names and a
196
- window is measured over. Naming one copy of a side that has several does
197
- not separate that side: the rest of it stays behind on the other side's
198
- symbol, and a collation then compares one copy against that mixture
199
- rather than the two texts against each other.
200
-
201
- Nothing an editor established is lost by that. `connectVersions`
202
- rewrites only what a collation wrote (`isCollationsOwn`): a bare
203
- insertion or deletion, or an equivalence between two systems' spellings.
204
- An edit naming what the change is, why it was made, or what it rests on
205
- stays, and the symbols it speaks for are left out of the collation. An
206
- equivalence counts as the collation's own, since it is derived from the
207
- two systems' vocabularies rather than read off the paper and freezing it
208
- would leave the transfers uncollatable, but one drawn a second time over
209
- the very same symbols is kept as it stands, identifier and motivation
210
- and all. Readings already separated by hand are carried by one copy
211
- alone, so `separateReadings` passes over them and they keep their
212
- identifiers.
213
-
214
- ## What brought a feature about
215
-
216
- A copy states no list of features. Each feature stands in the act that
217
- brought it about: the copy's own production for what the punching made,
218
- an `Alteration` for what a later hand made, an `Attachment` for a patch
219
- glued on. A `Removal` names by id what was taken off, which stood there
220
- before the act.
13
+ ```
14
+ npm i linked-rolls
15
+ ```
221
16
 
222
- Which act a feature stands in says when it came about and by whose
223
- hand, and says nothing about what kind of feature it is. A hole may be
224
- punched by hand long after the roll was cut, and a mark may come from
225
- the factory, rarely though that happens. A feature no later act is
226
- known to have made belongs to the punching, which is where a reading of
227
- a scan puts every chain of holes it finds.
17
+ ## Usage
228
18
 
229
- Three kinds of act rather than two, because the CRM tells them apart.
230
- E79 Part Addition asks that what is added be "a separate identifiable
231
- whole prior to" the act, which a glued-on label is and a pencil line is
232
- not: drawing, writing and punching bring a feature into being and are
233
- productions (E12), which state what they made with P108 has produced.
19
+ An edition is plain data. Operations are functions applied to an
20
+ [immer](https://immerjs.github.io/immer/) draft, so each one is a
21
+ single undo step.
234
22
 
235
- The export states what that shape only implies. A copy bears every
236
- feature its acts brought about (P56 bears feature) and is composed of
237
- the patches glued onto it (P46 is composed of, a patch being an object
238
- rather than a feature); an attachment augments the copy and a removal
239
- diminishes it. The four keys are derived on the way out and read off
240
- again on the way in, so they are no part of the edition, and they are
241
- the one place where the JSON tree and the graph differ in shape.
23
+ ```ts
24
+ import { produce } from 'immer'
25
+ import { asJsonLd, connectVersions, createVersion, EditionView, importJsonLd, readFromStanfordAton } from 'linked-rolls'
242
26
 
243
- ## Where a copy's features come from
27
+ let edition = importJsonLd(json)
244
28
 
245
- `readFrom` states what a copy's features were read from: the roll
246
- itself, a `scan` of it, an `analysis` somebody else measured on a
247
- scan, a `reading` of the timed switches of a roll reader, an
248
- `emulation` in which the roll has already been read into notes and
249
- commands, or a `recording` of the copy being played on an instrument.
250
- Beside the kind it holds who carried the capture out, on what device,
251
- with what software, on which instrument a recorded copy was played,
252
- when, and a note. What is not known is left out.
29
+ // Add a copy read from a SUPRA analysis file, with a version of its own
30
+ edition = produce(edition, createVersion(readFromStanfordAton(aton)))
253
31
 
254
- This says where the numbers of the edition come from and nothing
255
- about the state of the paper, which is a condition of the copy. In
256
- RDF it is `reo:capture`, an activity typed by what it read from; the
257
- file it produced is `crmdig:L11 had output`, the machine it ran on
258
- `crmdig:L12 happened on device`, the software `crmdig:L23 used
259
- software or firmware`, the instrument `crm:P16 used specific object`.
32
+ // Collate that version against another one
33
+ const view = new EditionView(edition)
34
+ edition = produce(edition, connectVersions(view, childId, parentId))
260
35
 
261
- Nothing records that a copy is doubtful. `reservationsAbout` works
262
- out from what a copy states what the edition cannot vouch for in it:
263
- that it names no source, that the making of its source is
264
- undocumented, that no software or instrument is named for an
265
- emulation or a recording, that its features are somebody else's
266
- reading, that its source bears no physical evidence, that no measuring
267
- software is recorded, that it is not calibrated, that nobody is known
268
- to hold it. A reservation goes away when the gap it names is filled.
36
+ const document = asJsonLd(edition)
37
+ ```
269
38
 
270
- A copy nobody can reach, such as one known only from a recording, has
271
- no features to carry the symbols of a version. It states instead
272
- which versions it is held to carry, in `carries`, each under a belief
273
- that says how certainly and why. `witnessesOf` gathers the copies of a
274
- version, whether their features carry its symbols or they state that
275
- they carry it, a statement with the belief it rests on;
276
- `versionsWitnessedBy` gathers the same from the side of a copy.
277
- `carriageProblems` reports a statement made beside features that carry
278
- symbols already, or one naming a version the edition lacks. A copy may
279
- carry a `siglum`, which is given by hand and not read off anything;
280
- one without is named by its keeper.
39
+ Documents written by earlier releases are brought up to date on import.
40
+ To check a document against the schema:
281
41
 
282
42
  ```ts
283
- import { reservationsAbout, stateSource } from 'linked-rolls'
284
-
285
- const next = produce(edition, stateSource(copyId, {
286
- kind: 'emulation',
287
- output: 'https://example.org/wm225.mid',
288
- note: 'MIDI from a third party; the emulator is not named.'
289
- }))
290
-
291
- reservationsAbout(next.copies[0]).map(reservation => reservation.note)
43
+ import { validate } from 'linked-rolls/validate'
292
44
  ```
293
45
 
294
46
  ## Emulation
295
47
 
296
- `Emulation` turns a version of the edition into MIDI. The core of the
297
- library does the part that belongs to the edition: it negotiates the
298
- symbols of a version into placed events, hands them to a
299
- `ReproducingSystem`, and writes the performance out with every note and
300
- pedal step labelled by the symbol it performs. A reproducing system is a
301
- tracker bar and a `perform` function; the core does not depend on any one
302
- instrument's model.
303
-
304
- The first system is the red Welte, `linked-rolls/welte-t100`, built on
305
- [welte-mignon-emulator](https://github.com/pfefferniels/welte-t100): the
306
- take-up spool sets the time axis, the Nuancierbälge fill through their
307
- conduits and are arrested by the Mezzoforte pin, and the two pedals travel
308
- rather than switch. The nuancing constants come as `instruments`: the
309
- consensus fitted across the hand-drawn nuance lines of six rolls, which
310
- is the default, and the setting that drew each of those rolls, named by
311
- its Welte number, all with the terms that describe the drawing apparatus
312
- switched off. The pedals leave no drawn line, so their constants
313
- come as two readings, `pedalPresets.damping`, under which every lift the
314
- rolls punch damps, and `pedalPresets.brushing`, under which the quick runs
315
- of latch changes brush the strings without damping. What the emulator does
316
- not determine is how bellows travel maps onto MIDI velocity;
317
- `WelteT100Options.velocity` anchors that map at the open rail, the
318
- Mezzoforte pin and the closed rail, and its defaults are midi2exp's.
48
+ A version can be played through a reproducing system and written out
49
+ as MIDI. The Welte-Mignon systems use
50
+ [welte-mignon-emulator](https://github.com/pfefferniels/welte-t100), an
51
+ optional peer dependency that you install only if you need playback.
319
52
 
320
53
  ```ts
321
- import { Emulation } from 'linked-rolls'
54
+ import { emulate, midiOf } from 'linked-rolls'
322
55
  import { welteT100System } from 'linked-rolls/welte-t100'
323
56
 
324
- const emulation = new Emulation(welteT100System)
325
- emulation.emulateVersion(version, view)
326
- const midi = emulation.asMIDI()
57
+ const { events, source } = emulate(welteT100System, version, view)
58
+ const midi = midiOf(events, welteT100System.name, welteT100System.defaultOptions, source)
327
59
  ```
328
60
 
329
- The second is the green Welte, `linked-rolls/welte-t98`. Everything
330
- downstream of the relay is the same mechanism — Hagmann has the nuancing
331
- unit built the same for both tracker scales (p. 96) — and what differs is
332
- in front of it: each function lasts exactly as long as its own perforation
333
- runs over the glide block rather than latching until a cancel line is read,
334
- four conduits stand on one bellows and their drives add as flows, the
335
- crescendo's ceiling is the balance of its throttle against a permanently
336
- open bleed rather than a cap, the two pedals sit on the opposite edges of
337
- the paper, and a long perforation on the bass sforzando-piano line sends
338
- the roll back. Its constants are **not fitted**: `instruments.genuine` and
339
- `instruments.derived` are empty until their fits are run, and until then a
340
- playback runs on unfitted starting values, which every curve says in its
341
- own `instrument` field.
342
-
343
- Both systems share one velocity map, and `DynamicsCurve.travel` means the
344
- same thing on both: the position on the printed ordinate of Welte's own
345
- ruled band, 0 at that half's P.P. gridline and 1 at the shared F.F. line.
346
- That is what lets a red issue and a green issue of one recording be
347
- compared at all, since the band is ruled the same way on both.
61
+ The systems are `linked-rolls/welte-t100` (red Welte),
62
+ `linked-rolls/welte-t98` (green Welte) and `linked-rolls/welte-licensee`.
348
63
 
349
- ```ts
350
- import { welteT98System } from 'linked-rolls/welte-t98'
351
- ```
64
+ ## Format
352
65
 
353
- The emulator is an optional peer dependency: an application that uses one
354
- of the Welte systems installs `welte-mignon-emulator` itself, and one that
355
- only reads editions does not need it. For development on both at once,
356
- `npm link` a checkout of
357
- [welte-t100](https://github.com/pfefferniels/welte-t100) into this
358
- repository.
66
+ The export uses the Roll Edition Ontology (REO), namespace
67
+ `https://w3id.org/reo/`, together with CIDOC CRM, LRMoo and CRMinf. The
68
+ ontology is in `ontology/`. It is published with its context and the
69
+ documentation of the format at
70
+ https://pfefferniels.github.io/linked-rolls/ (the w3id.org identifiers are
71
+ not registered yet).
359
72
 
360
- ### Alignment, order and pairing
73
+ Beliefs are written as JSON-LD-star annotations (`@annotation`), so you
74
+ need a processor that supports JSON-LD-star to read them.
361
75
 
362
- A command takes its place from the chains of holes that carry it. Four
363
- fields on a command let an editor state where the measurement should
364
- give way. `alignedWith` names another command whose onset this one takes
365
- in the performance, as a "crescendo off" is meant to fall on the note
366
- it belongs to. `before` and `after` name a command whose onset this
367
- one precedes or follows, without saying by how much: where the copies
368
- disagree on which side of a note an expression falls, the statement
369
- settles the order. A command the measurement already has on the
370
- stated side keeps its place. One it does not is put on that side, as
371
- far from the reference as the copies that agree with the statement put
372
- it, and a punch diameter away where none does. A command makes one
373
- of these three statements at most. `pairedWith` names a partner whose
374
- distance to this one is fixed, as a "forzando on" belongs with its
375
- "forzando off": whatever displaces the one displaces the other. Any two
376
- commands may be paired, the relation is symmetric, and it is stated
377
- on one side only. All are applied when a version is emulated.
378
- `constraintProblems` lists, version by version, the cases in which the
379
- statements cannot hold: a reference or partner that is absent, a
380
- command placed relative to itself or in several ways at once, one
381
- claimed by several pairs, or a pair whose members are both placed.
76
+ ## Development
382
77
 
383
- ## Building
384
78
  ```
385
- npm i
386
- npm run build
79
+ npm ci
80
+ npm test
81
+ npm run lint
82
+ npm run build
387
83
  ```
388
84
 
389
85
  ## Releasing
390
86
 
391
- Releases go out through `.github/workflows/publish.yml`, never from a
392
- laptop: raise the version in `package.json`, commit, and push a tag
393
- `v<version>`, or run the workflow by hand from the Actions tab. The
394
- workflow builds, runs the tests and publishes with provenance through
395
- npm's trusted publishing, so no token is stored. It checks out the
396
- emulator beside this repository to build the T-100 entry point.
87
+ Raise the version in `package.json`, commit, and push a tag
88
+ `v<version>`. The `publish.yml` workflow builds, tests and publishes to
89
+ npm.
@@ -1,4 +1,4 @@
1
- import { EditionView } from "./EditionView.js";
1
+ import { EditionView } from "../view/EditionView.js";
2
2
  export type ConstraintProblem = {
3
3
  version: string;
4
4
  symbol: string;
@@ -1,9 +1,10 @@
1
- import { idOf } from "./Assumption.js";
2
- import { isCommand, pairsAmong, placementsOf } from "./Symbol.js";
3
- import { keyOf } from "./TrackerBar.js";
4
- import { trackerBarOf } from "./systems/index.js";
5
- import { barOf } from "./RollCopy.js";
6
- import { deletedBy, insertedBy } from "./Version.js";
1
+ import { speedScalesIn } from "./ownPaper.js";
2
+ import { idOf } from "../model/Assumption.js";
3
+ import { isCommand, pairsAmong, placementsOf } from "../model/Symbol.js";
4
+ import { keyOf } from "../systems/TrackerBar.js";
5
+ import { trackerBarOf } from "../systems/index.js";
6
+ import { barOf } from "../model/RollCopy.js";
7
+ import { deletedBy, insertedBy } from "../model/Version.js";
7
8
  const missingReference = {
8
9
  alignedWith: 'alignment-reference-missing',
9
10
  before: 'before-reference-missing',
@@ -86,7 +87,7 @@ const carriersOffTheirMeaning = (view, version, commands) => {
86
87
  .some(meaning => keyOf(meaning) === keyOf(symbol));
87
88
  };
88
89
  return commands
89
- .filter(symbol => view.carriersOf(symbol).some(carrier => misread(carrier, symbol)))
90
+ .filter(symbol => view.placedCarriersOf(symbol).some(carrier => misread(carrier, symbol)))
90
91
  .map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
91
92
  };
92
93
  /**
@@ -98,7 +99,7 @@ const carriersOffTheirMeaning = (view, version, commands) => {
98
99
  * is evidence about the copies, and averaging it away would hide both
99
100
  * the disagreement and the fact that the playback rests on a guess.
100
101
  */
101
- const paperDisagreed = (view, version) => view.speedScalesIn(version).length > 1
102
+ const paperDisagreed = (view, version) => speedScalesIn(view, version).length > 1
102
103
  ? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
103
104
  : [];
104
105
  /**
@@ -0,0 +1,26 @@
1
+ import type { EditionView } from "../view/EditionView.js";
2
+ import type { Version } from "../model/Version.js";
3
+ import { RollCopy } from "../model/RollCopy.js";
4
+ /** The copies of the version's own system that carry any of its symbols. */
5
+ export declare const copiesOwning: (view: EditionView, version: Readonly<Version>) => Readonly<RollCopy>[];
6
+ /** The scales of the version's own copies that are not their own paper stretch. */
7
+ export declare const speedScalesIn: (view: EditionView, version: Readonly<Version>) => number[];
8
+ /**
9
+ * How a place on the edition's shared axis relates to the paper of
10
+ * this version: place × factor = millimetres of its own paper.
11
+ *
12
+ * Copies cut for different systems are scaled onto one axis so that
13
+ * they can be collated at all, which leaves a version of another
14
+ * system carrying places in the axis copy's millimetres. A
15
+ * performance needs the paper the roll actually ran on, and the
16
+ * factor is the inverse of the scale `alignCopy` recorded.
17
+ *
18
+ * It is read only from the copies of the version's own system, since
19
+ * under the shared axis a green version's notes are carried by red
20
+ * copies too and those say nothing about green paper. A copy whose
21
+ * scale is put down to its own paper having stretched is left out as
22
+ * well: that is a fact about the one exemplar, not about the speed
23
+ * the system's rolls were cut at. Where what remains disagrees,
24
+ * `constraintProblems` reports it rather than averaging it away.
25
+ */
26
+ export declare const toOwnPaperOf: (view: EditionView, version: Readonly<Version>) => number | undefined;
@@ -0,0 +1,41 @@
1
+ import { idsOf } from "../model/Assumption.js";
2
+ import { isPaperStretch } from "../model/RollCopy.js";
3
+ import { systemIdOf } from "../systems/TrackerBar.js";
4
+ /** The copies of the version's own system that carry any of its symbols. */
5
+ export const copiesOwning = (view, version) => {
6
+ const system = systemIdOf(version.system);
7
+ const carrying = new Set(view.snapshot(version.id)
8
+ .flatMap(symbol => idsOf(symbol.carriers))
9
+ .flatMap(id => {
10
+ const copy = view.copyOf(id);
11
+ return copy ? [copy.id] : [];
12
+ }));
13
+ return view.edition.copies.filter(copy => carrying.has(copy.id) && systemIdOf(copy.production?.system) === system);
14
+ };
15
+ /** The scales of the version's own copies that are not their own paper stretch. */
16
+ export const speedScalesIn = (view, version) => [...new Set(copiesOwning(view, version)
17
+ .filter(copy => !copy.conditions.some(isPaperStretch))
18
+ .map(copy => copy.measurements.scale)
19
+ .filter((scale) => scale !== undefined && scale > 0))];
20
+ /**
21
+ * How a place on the edition's shared axis relates to the paper of
22
+ * this version: place × factor = millimetres of its own paper.
23
+ *
24
+ * Copies cut for different systems are scaled onto one axis so that
25
+ * they can be collated at all, which leaves a version of another
26
+ * system carrying places in the axis copy's millimetres. A
27
+ * performance needs the paper the roll actually ran on, and the
28
+ * factor is the inverse of the scale `alignCopy` recorded.
29
+ *
30
+ * It is read only from the copies of the version's own system, since
31
+ * under the shared axis a green version's notes are carried by red
32
+ * copies too and those say nothing about green paper. A copy whose
33
+ * scale is put down to its own paper having stretched is left out as
34
+ * well: that is a fact about the one exemplar, not about the speed
35
+ * the system's rolls were cut at. Where what remains disagrees,
36
+ * `constraintProblems` reports it rather than averaging it away.
37
+ */
38
+ export const toOwnPaperOf = (view, version) => {
39
+ const scales = speedScalesIn(view, version);
40
+ return scales.length === 1 ? 1 / scales[0] : undefined;
41
+ };
@@ -1,6 +1,6 @@
1
- import { RollCopy } from "./RollCopy.js";
2
- import { Version } from "./Version.js";
3
- import { EditionView } from "./EditionView.js";
1
+ import { RollCopy } from "../model/RollCopy.js";
2
+ import { Version } from "../model/Version.js";
3
+ import { EditionView } from "../view/EditionView.js";
4
4
  export declare const reservationTypes: readonly ['source-not-stated', 'source-undocumented', 'software-not-named', 'instrument-not-named', 'features-interpreted', 'no-physical-evidence', 'measurement-undocumented', 'not-calibrated', 'system-unknown', 'keeper-unknown'];
5
5
  export type ReservationType = typeof reservationTypes[number];
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { bearsPhysicalEvidence, isMeasured, sourceLabels } from "./FeatureSource.js";
2
- import { calibrationOf, featuresOf } from "./RollCopy.js";
3
- import { trackerBarOf } from "./systems/index.js";
1
+ import { bearsPhysicalEvidence, isMeasured, sourceLabels } from "../model/FeatureSource.js";
2
+ import { calibrationOf, featuresOf } from "../model/RollCopy.js";
3
+ import { trackerBarOf } from "../systems/index.js";
4
4
  import { witnessesOf } from "./witnesses.js";
5
5
  export const reservationTypes = [
6
6
  'source-not-stated',
@@ -1,5 +1,5 @@
1
- import { Edition } from './Edition.js';
2
- import { EditionView } from './EditionView.js';
1
+ import { Edition } from '../model/Edition.js';
2
+ import { EditionView } from '../view/EditionView.js';
3
3
  /**
4
4
  * What the sigla are read off: the versions alone, or a view, which knows
5
5
  * the copies as well and so can tell which versions a witness shows.
@@ -1,7 +1,7 @@
1
- import { idOf } from './Assumption.js';
2
- import { systemIdOf } from './TrackerBar.js';
3
- import { trackerBarOf } from './systems/index.js';
4
- import { principalDerivationOf } from './Version.js';
1
+ import { idOf } from '../model/Assumption.js';
2
+ import { systemIdOf } from '../systems/TrackerBar.js';
3
+ import { trackerBarOf } from '../systems/index.js';
4
+ import { principalDerivationOf } from '../model/Version.js';
5
5
  import { attestedVersions } from './witnesses.js';
6
6
  /** The letter the versions of a system are labelled with. */
7
7
  const letters = new Map([
@@ -1,5 +1,5 @@
1
- import { Belief, Certainty } from "./Assumption.js";
2
- import { EditionView } from "./EditionView.js";
1
+ import { Belief, Certainty } from "../model/Assumption.js";
2
+ import { EditionView } from "../view/EditionView.js";
3
3
  export type WitnessBy = 'carriers' | 'statement';
4
4
  /** A copy that bears witness to a version, and how. */
5
5
  export interface Witness {
@@ -1,5 +1,5 @@
1
- import { certaintyOf, idOf, idsOf } from "./Assumption.js";
2
- import { insertedBy } from "./Version.js";
1
+ import { certaintyOf, idOf, idsOf } from "../model/Assumption.js";
2
+ import { insertedBy } from "../model/Version.js";
3
3
  /** The copies whose features carry any of the symbols. */
4
4
  const copiesCarrying = (view, symbols) => new Set(symbols
5
5
  .flatMap(symbol => idsOf(symbol.carriers))
@@ -70,7 +70,7 @@ const witnessesIn = (carriers, view, versionId) => {
70
70
  * reported as it stands. A version whose edits are not stated inserts
71
71
  * nothing and is witnessed by statement alone.
72
72
  */
73
- export const witnessesOf = (view, versionId) => view.get(versionId) ? witnessesIn(carriersIn(view), view, versionId) : [];
73
+ export const witnessesOf = (view, versionId) => view.version(versionId) ? witnessesIn(carriersIn(view), view, versionId) : [];
74
74
  /**
75
75
  * The versions the copy bears witness to, each with how, in the order the
76
76
  * edition lists its versions.
@@ -92,7 +92,7 @@ export const carriageProblems = (view) => {
92
92
  const version = idOf(statement);
93
93
  return [
94
94
  ...(carrying.has(copy.id) ? [{ copy: copy.id, version, problem: 'stated-beside-carriers' }] : []),
95
- ...(view.get(version)?.type === 'Version' ? [] : [{ copy: copy.id, version, problem: 'version-missing' }])
95
+ ...(view.version(version) ? [] : [{ copy: copy.id, version, problem: 'version-missing' }])
96
96
  ];
97
97
  }));
98
98
  };