1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! Parsed data structures lifted out of a `.dw` payload.
//!
//! The shapes here mirror the runtime layout that the replayer
//! walks; field names follow the structural spec
//! `whittaker-dw-format.md` (repo root) and `FORMAT.md` (this
//! directory). None of the fields carry endianness: the importer
//! reads them big-endian and stores native integers.
use Vec;
/// Number of Paula channels — fixed at 4 on every Amiga model
/// Whittaker shipped his player on. Used as the position-list
/// fan-out per sub-song.
pub const DW_NUM_CHANNELS: usize = 4;
/// Sub-song descriptor lifted from the `sub_song_list_offset`
/// header (spec §4.1). One sub-song per module is the rule for
/// every Whittaker score we've inspected; the spec allows multiple
/// but the on-disk layout makes the count implicit, so we expose a
/// single entry and let the caller cope when (and if) a multi-song
/// `.dw` shows up.
/// One channel's position list (spec §4.2) — an ordered sequence
/// of track references. Each entry is the absolute file offset of
/// a track byte stream; the top bit (15 for new-player u16 entries,
/// 31 for qball-era u32 entries) marks a backward loop into the
/// list rather than a normal track reference.
///
/// Entries are stored as `u32` so the type covers both encodings
/// uniformly. The actual on-disk width is implicit in the layout
/// — the parser reads the matching size and zero-extends.
/// A single track byte stream — the bytes between a track's
/// position-list reference and its `EndOfTrack` command (`0x80`),
/// inclusive of the terminator. Notes (`0x00..=0x7F`) and
/// commands (`0x80..=0xFF`) are stored verbatim; the runtime tick
/// (spec §9) decodes them into Paula writes.
///
/// The same content is also available pre-decoded in
/// [`Self::events`] for clients that don't want to re-walk the
/// byte grammar.
/// A single sample: metadata + the raw 8-bit signed PCM payload.
/// A per-channel volume envelope — the byte stream consumed by
/// the `0xA0..` dispatcher bracket (which Whittaker's replayer
/// reads at `Play+0x412` to drive Paula's `AUD0VOL` register).
///
/// Each envelope is a sequence of 7-bit volume values in Paula's
/// 0..=127 scale (the replayer's `MULU.W global_vol ; LSR.W #6`
/// step rescales them to Paula's 0..=64). The last value in the
/// sequence is the *sustain* level — its on-disk byte had the
/// high bit set, signalling the replayer to stop advancing the
/// envelope.
///
/// `step_interval` is the per-channel `chan[+0x2A]` slot: the
/// number of replayer ticks the envelope holds each value before
/// advancing. `1` means a new step every tick (fastest decay);
/// higher values stretch the envelope out.
/// A per-channel **pitch arpeggio** — the byte stream consumed by
/// the `0x90..` dispatcher bracket (spec §4.4 / §10.12). Each
/// entry is a signed semitone offset added to the current note's
/// period-table index; the replayer cycles one entry per tick and
/// loops back to the start when it passes the terminator (the
/// on-disk byte with its `0x80` bit set, whose low 7 bits are the
/// last offset).