diskstack 1.0.0__py3-none-any.whl

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 (119) hide show
  1. diskstack/__init__.py +5 -0
  2. diskstack/_vendor/__init__.py +0 -0
  3. diskstack/_vendor/greaseweazle/COPYING +24 -0
  4. diskstack/_vendor/greaseweazle/__init__.py +2 -0
  5. diskstack/_vendor/greaseweazle/codec/__init__.py +0 -0
  6. diskstack/_vendor/greaseweazle/codec/amiga/__init__.py +0 -0
  7. diskstack/_vendor/greaseweazle/codec/amiga/amigados.py +221 -0
  8. diskstack/_vendor/greaseweazle/codec/apple2/__init__.py +0 -0
  9. diskstack/_vendor/greaseweazle/codec/apple2/apple2_gcr.py +223 -0
  10. diskstack/_vendor/greaseweazle/codec/bitcell.py +117 -0
  11. diskstack/_vendor/greaseweazle/codec/codec.py +362 -0
  12. diskstack/_vendor/greaseweazle/codec/commodore/__init__.py +0 -0
  13. diskstack/_vendor/greaseweazle/codec/commodore/c64_gcr.py +229 -0
  14. diskstack/_vendor/greaseweazle/codec/datageneral/__init__.py +0 -0
  15. diskstack/_vendor/greaseweazle/codec/datageneral/datageneral.py +211 -0
  16. diskstack/_vendor/greaseweazle/codec/hp/__init__.py +0 -0
  17. diskstack/_vendor/greaseweazle/codec/hp/hp_mmfm.py +258 -0
  18. diskstack/_vendor/greaseweazle/codec/ibm/__init__.py +0 -0
  19. diskstack/_vendor/greaseweazle/codec/ibm/ibm.py +1005 -0
  20. diskstack/_vendor/greaseweazle/codec/macintosh/__init__.py +0 -0
  21. diskstack/_vendor/greaseweazle/codec/macintosh/mac_gcr.py +238 -0
  22. diskstack/_vendor/greaseweazle/codec/micropolis/__init__.py +0 -0
  23. diskstack/_vendor/greaseweazle/codec/micropolis/micropolis.py +233 -0
  24. diskstack/_vendor/greaseweazle/codec/northstar/__init__.py +0 -0
  25. diskstack/_vendor/greaseweazle/codec/northstar/northstar.py +210 -0
  26. diskstack/_vendor/greaseweazle/data/__init__.py +0 -0
  27. diskstack/_vendor/greaseweazle/data/diskdefs.cfg +81 -0
  28. diskstack/_vendor/greaseweazle/data/diskdefs_acorn.cfg +119 -0
  29. diskstack/_vendor/greaseweazle/data/diskdefs_akai.cfg +27 -0
  30. diskstack/_vendor/greaseweazle/data/diskdefs_amiga.cfg +17 -0
  31. diskstack/_vendor/greaseweazle/data/diskdefs_apple2.cfg +31 -0
  32. diskstack/_vendor/greaseweazle/data/diskdefs_apricot.cfg +25 -0
  33. diskstack/_vendor/greaseweazle/data/diskdefs_atari.cfg +33 -0
  34. diskstack/_vendor/greaseweazle/data/diskdefs_atarist.cfg +76 -0
  35. diskstack/_vendor/greaseweazle/data/diskdefs_coco.cfg +98 -0
  36. diskstack/_vendor/greaseweazle/data/diskdefs_commodore.cfg +127 -0
  37. diskstack/_vendor/greaseweazle/data/diskdefs_datageneral.cfg +8 -0
  38. diskstack/_vendor/greaseweazle/data/diskdefs_dec.cfg +37 -0
  39. diskstack/_vendor/greaseweazle/data/diskdefs_dragon.cfg +61 -0
  40. diskstack/_vendor/greaseweazle/data/diskdefs_eagle.cfg +25 -0
  41. diskstack/_vendor/greaseweazle/data/diskdefs_ensoniq.cfg +39 -0
  42. diskstack/_vendor/greaseweazle/data/diskdefs_epson.cfg +95 -0
  43. diskstack/_vendor/greaseweazle/data/diskdefs_gem.cfg +15 -0
  44. diskstack/_vendor/greaseweazle/data/diskdefs_hp.cfg +25 -0
  45. diskstack/_vendor/greaseweazle/data/diskdefs_ibm.cfg +140 -0
  46. diskstack/_vendor/greaseweazle/data/diskdefs_kaypro.cfg +58 -0
  47. diskstack/_vendor/greaseweazle/data/diskdefs_luxor.cfg +96 -0
  48. diskstack/_vendor/greaseweazle/data/diskdefs_mac.cfg +71 -0
  49. diskstack/_vendor/greaseweazle/data/diskdefs_micropolis.cfg +73 -0
  50. diskstack/_vendor/greaseweazle/data/diskdefs_mm1.cfg +69 -0
  51. diskstack/_vendor/greaseweazle/data/diskdefs_msx.cfg +45 -0
  52. diskstack/_vendor/greaseweazle/data/diskdefs_northstar.cfg +37 -0
  53. diskstack/_vendor/greaseweazle/data/diskdefs_occ1.cfg +23 -0
  54. diskstack/_vendor/greaseweazle/data/diskdefs_olivetti.cfg +18 -0
  55. diskstack/_vendor/greaseweazle/data/diskdefs_pc98.cfg +69 -0
  56. diskstack/_vendor/greaseweazle/data/diskdefs_raw.cfg +25 -0
  57. diskstack/_vendor/greaseweazle/data/diskdefs_rm.cfg +98 -0
  58. diskstack/_vendor/greaseweazle/data/diskdefs_sci.cfg +15 -0
  59. diskstack/_vendor/greaseweazle/data/diskdefs_sega.cfg +13 -0
  60. diskstack/_vendor/greaseweazle/data/diskdefs_sharp.cfg +13 -0
  61. diskstack/_vendor/greaseweazle/data/diskdefs_thomson.cfg +78 -0
  62. diskstack/_vendor/greaseweazle/data/diskdefs_tsc.cfg +81 -0
  63. diskstack/_vendor/greaseweazle/data/diskdefs_xerox.cfg +59 -0
  64. diskstack/_vendor/greaseweazle/data/diskdefs_zx.cfg +245 -0
  65. diskstack/_vendor/greaseweazle/error.py +19 -0
  66. diskstack/_vendor/greaseweazle/flux.py +315 -0
  67. diskstack/_vendor/greaseweazle/image/__init__.py +0 -0
  68. diskstack/_vendor/greaseweazle/image/a2r.py +133 -0
  69. diskstack/_vendor/greaseweazle/image/acorn.py +28 -0
  70. diskstack/_vendor/greaseweazle/image/adf.py +15 -0
  71. diskstack/_vendor/greaseweazle/image/apple2.py +18 -0
  72. diskstack/_vendor/greaseweazle/image/apridisk.py +107 -0
  73. diskstack/_vendor/greaseweazle/image/caps.py +464 -0
  74. diskstack/_vendor/greaseweazle/image/d64.py +51 -0
  75. diskstack/_vendor/greaseweazle/image/d81.py +28 -0
  76. diskstack/_vendor/greaseweazle/image/d88.py +211 -0
  77. diskstack/_vendor/greaseweazle/image/dcp.py +45 -0
  78. diskstack/_vendor/greaseweazle/image/dim.py +53 -0
  79. diskstack/_vendor/greaseweazle/image/dmk.py +166 -0
  80. diskstack/_vendor/greaseweazle/image/dsk.py +31 -0
  81. diskstack/_vendor/greaseweazle/image/edsk.py +591 -0
  82. diskstack/_vendor/greaseweazle/image/fd.py +23 -0
  83. diskstack/_vendor/greaseweazle/image/fdi.py +41 -0
  84. diskstack/_vendor/greaseweazle/image/hdm.py +15 -0
  85. diskstack/_vendor/greaseweazle/image/hfe.py +742 -0
  86. diskstack/_vendor/greaseweazle/image/image.py +130 -0
  87. diskstack/_vendor/greaseweazle/image/imd.py +201 -0
  88. diskstack/_vendor/greaseweazle/image/img.py +111 -0
  89. diskstack/_vendor/greaseweazle/image/kryoflux.py +304 -0
  90. diskstack/_vendor/greaseweazle/image/mgt.py +15 -0
  91. diskstack/_vendor/greaseweazle/image/msa.py +153 -0
  92. diskstack/_vendor/greaseweazle/image/nfd.py +113 -0
  93. diskstack/_vendor/greaseweazle/image/nsi.py +47 -0
  94. diskstack/_vendor/greaseweazle/image/scp.py +471 -0
  95. diskstack/_vendor/greaseweazle/image/sf7.py +15 -0
  96. diskstack/_vendor/greaseweazle/image/sharp2d.py +15 -0
  97. diskstack/_vendor/greaseweazle/image/td0.py +141 -0
  98. diskstack/_vendor/greaseweazle/image/xdf.py +15 -0
  99. diskstack/_vendor/greaseweazle/optimised/__init__.py +22 -0
  100. diskstack/_vendor/greaseweazle/tools/__init__.py +0 -0
  101. diskstack/_vendor/greaseweazle/tools/util.py +133 -0
  102. diskstack/_vendor/greaseweazle/track.py +494 -0
  103. diskstack/cache.py +210 -0
  104. diskstack/candidates.py +396 -0
  105. diskstack/cli.py +348 -0
  106. diskstack/errors.py +5 -0
  107. diskstack/filler.py +29 -0
  108. diskstack/formats.py +381 -0
  109. diskstack/parallel.py +73 -0
  110. diskstack/report.py +353 -0
  111. diskstack/stack.py +317 -0
  112. diskstack-1.0.0.dist-info/METADATA +329 -0
  113. diskstack-1.0.0.dist-info/RECORD +119 -0
  114. diskstack-1.0.0.dist-info/WHEEL +5 -0
  115. diskstack-1.0.0.dist-info/entry_points.txt +2 -0
  116. diskstack-1.0.0.dist-info/licenses/LICENSE +21 -0
  117. diskstack-1.0.0.dist-info/licenses/NOTICE +55 -0
  118. diskstack-1.0.0.dist-info/licenses/diskstack/_vendor/greaseweazle/COPYING +24 -0
  119. diskstack-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1005 @@
1
+ # greaseweazle/codec/ibm/ibm.py
2
+ #
3
+ # Written & released by Keir Fraser <keir.xen@gmail.com>
4
+ #
5
+ # This is free and unencumbered software released into the public domain.
6
+ # See the file COPYING for more details, or visit <http://unlicense.org>.
7
+
8
+ from __future__ import annotations
9
+ from typing import Any, List, Optional, Union, Tuple
10
+
11
+ import re
12
+ import copy, heapq, struct, functools
13
+ import itertools as it
14
+ from bitarray import bitarray
15
+ from enum import Enum
16
+ import crcmod.predefined
17
+
18
+ from diskstack._vendor.greaseweazle import error
19
+ from diskstack._vendor.greaseweazle.codec import codec
20
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
21
+ from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
22
+
23
+ default_revs = 2
24
+
25
+ def sync(dat, clk=0xc7):
26
+ x = 0
27
+ for i in range(8):
28
+ x <<= 1
29
+ x |= (clk >> (7-i)) & 1
30
+ x <<= 1
31
+ x |= (dat >> (7-i)) & 1
32
+ return bytes(struct.pack('>H', x))
33
+
34
+ fm_sync_prefix = bitarray(endian='big')
35
+ fm_sync_prefix.frombytes(b'\xaa\xaa' + sync(0xf8))
36
+ fm_sync_prefix = fm_sync_prefix[:16+10]
37
+
38
+ fm_iam_sync_bytes = sync(0xfc, 0xd7)
39
+ fm_iam_sync = bitarray(endian='big')
40
+ fm_iam_sync.frombytes(b'\xaa\xaa' + fm_iam_sync_bytes)
41
+
42
+ mfm_iam_sync_bytes = b'\x52\x24' * 3
43
+ mfm_iam_sync = bitarray(endian='big')
44
+ mfm_iam_sync.frombytes(mfm_iam_sync_bytes)
45
+
46
+ mfm_sync_bytes = b'\x44\x89' * 3
47
+ mfm_sync = bitarray(endian='big')
48
+ mfm_sync.frombytes(mfm_sync_bytes)
49
+
50
+ def fm_encode(dat):
51
+ out = bytearray()
52
+ for x in dat:
53
+ if (x & 0xaa) == 0:
54
+ x |= 0xaa
55
+ out.append(x)
56
+ return bytes(out)
57
+
58
+ def mfm_encode(dat):
59
+ y = 0
60
+ out = bytearray()
61
+ for x in dat:
62
+ y = (y<<8) | x
63
+ if (x & 0xaa) == 0:
64
+ y |= ~((y>>1)|(y<<1)) & 0xaaaa
65
+ y &= 255
66
+ out.append(y)
67
+ return bytes(out)
68
+
69
+ encode_list: List[int] = []
70
+ for x in range(256):
71
+ y = 0
72
+ for i in range(8):
73
+ y <<= 2
74
+ y |= (x >> (7-i)) & 1
75
+ encode_list.append(y)
76
+
77
+ def encode(dat):
78
+ out = bytearray()
79
+ for x in dat:
80
+ out += struct.pack('>H', encode_list[x])
81
+ return bytes(out)
82
+ doubler = encode
83
+
84
+ decode_list = bytearray(0x5556)
85
+ for x in range(0x5556):
86
+ index = x & 0x5555
87
+ y = (index + (index >> 1)) & 0x3333
88
+ y = (y + (y >> 2)) & 0x0F0F
89
+ y = (y + (y >> 4)) & 0x00FF
90
+ decode_list[index] = y
91
+
92
+ def decode(dat):
93
+ out = bytearray()
94
+ for x,y in zip(dat[::2], dat[1::2]):
95
+ out.append(decode_list[((x<<8)|y)&0x5555])
96
+ return bytes(out)
97
+
98
+ crc16 = crcmod.predefined.Crc('crc-ccitt-false')
99
+
100
+ # Create logical sector map in rotational order
101
+ def sec_map(nsec: int, interleave: int, cskew: int, hskew: int,
102
+ cyl: int, head: int) -> List[int]:
103
+ sec_map, pos = [-1] * nsec, 0
104
+ if nsec != 0:
105
+ pos = (cyl*cskew + head*hskew) % nsec
106
+ for i in range(nsec):
107
+ while sec_map[pos] != -1:
108
+ pos = (pos + 1) % nsec
109
+ sec_map[pos] = i
110
+ pos = (pos + interleave) % nsec
111
+ return sec_map
112
+
113
+ def sec_sz(n):
114
+ return 128 << n if n <= 7 else 128 << 8
115
+
116
+ class Gaps:
117
+ def __init__(self, gap1, gap2, gap3, gap4a):
118
+ self.gap1 = gap1 # Post IAM
119
+ self.gap2 = gap2 # Post IDAM
120
+ self.gap3 = gap3 # Post DAM
121
+ self.gap4a = gap4a # Post Index
122
+
123
+ FMGaps = Gaps(
124
+ gap1 = 26,
125
+ gap2 = 11,
126
+ gap3 = [ 27, 42, 58, 138, 255, 255, 255, 255 ],
127
+ gap4a = 40
128
+ )
129
+
130
+ MFMGaps = Gaps(
131
+ gap1 = 50,
132
+ gap2 = 22,
133
+ gap3 = [ 32, 54, 84, 116, 255, 255, 255, 255 ],
134
+ gap4a = 80
135
+ )
136
+
137
+
138
+ class Mark:
139
+ IAM = 0xfc
140
+ IDAM = 0xfe
141
+ DAM = 0xfb
142
+ DDAM = 0xf8
143
+ # DEC RX02
144
+ DAM_DEC_MMFM = 0xfd
145
+ DDAM_DEC_MMFM = 0xf9
146
+ # TRS-80
147
+ DAM_TRS80_DIR = 0xfa
148
+
149
+ class Mode(Enum):
150
+ FM, MFM, DEC_RX02 = range(3)
151
+ def __str__(self):
152
+ NAMES = [ 'IBM FM', 'IBM MFM', 'DEC RX02' ]
153
+ return f'{NAMES[self.value]}'
154
+
155
+ class TrackArea:
156
+ def __init__(self, start, end, crc=None):
157
+ self.start = start
158
+ self.end = end
159
+ self.crc = crc
160
+ def delta(self, delta):
161
+ self.start -= delta
162
+ self.end -= delta
163
+ def __eq__(self, x):
164
+ return (isinstance(x, type(self))
165
+ and abs(self.start - x.start) < 1000
166
+ and abs(self.end - x.end) < 1000
167
+ and self.crc == x.crc)
168
+
169
+ class IDAM(TrackArea):
170
+ def __init__(self, start, end, crc, c, h, r, n):
171
+ super().__init__(start, end, crc)
172
+ self.c = c
173
+ self.h = h
174
+ self.r = r
175
+ self.n = n
176
+ def __str__(self):
177
+ return ("IDAM:%6d-%6d c=%02x h=%02x r=%02x n=%02x CRC:%04x"
178
+ % (self.start, self.end, self.c, self.h, self.r, self.n,
179
+ self.crc))
180
+ def __eq__(self, x):
181
+ return (super().__eq__(x)
182
+ and self.c == x.c and self.h == x.h
183
+ and self.r == x.r and self.n == x.n)
184
+ def __copy__(self):
185
+ return IDAM(self.start, self.end, self.crc,
186
+ self.c, self.h, self.r, self.n)
187
+
188
+ class DAM(TrackArea):
189
+ def __init__(self, start, end, crc, mark, data=None):
190
+ super().__init__(start, end, crc)
191
+ self.mark = mark
192
+ self.data = data
193
+ def __str__(self):
194
+ return "DAM: %6d-%6d mark=%02x" % (self.start, self.end, self.mark)
195
+ def __eq__(self, x):
196
+ return (super().__eq__(x)
197
+ and self.mark == x.mark
198
+ and self.data == x.data)
199
+ def __copy__(self):
200
+ return DAM(self.start, self.end, self.crc, self.mark, self.data)
201
+
202
+ class Sector(TrackArea):
203
+ def __init__(self, idam, dam):
204
+ super().__init__(idam.start, dam.end, idam.crc | dam.crc)
205
+ self.idam = idam
206
+ self.dam = dam
207
+ def __str__(self):
208
+ s = "Sec: %6d-%6d CRC:%04x\n" % (self.start, self.end, self.crc)
209
+ s += " " + str(self.idam) + "\n"
210
+ s += " " + str(self.dam)
211
+ return s
212
+ def delta(self, delta):
213
+ super().delta(delta)
214
+ self.idam.delta(delta)
215
+ self.dam.delta(delta)
216
+ def __eq__(self, x):
217
+ return (super().__eq__(x)
218
+ and self.idam == x.idam
219
+ and self.dam == x.dam)
220
+
221
+ class IAM(TrackArea):
222
+ def __str__(self):
223
+ return "IAM: %6d-%6d" % (self.start, self.end)
224
+ def __copy__(self):
225
+ return IAM(self.start, self.end)
226
+
227
+
228
+ class DEC_MMFM:
229
+ def __init__(self):
230
+ # Encode: 011110 -> 01000100010
231
+ self.encode_search = bitarray('011110', endian='big')
232
+ self.encode_replace = bitarray('01000100010', endian='big')
233
+ # Decode: DCD=000 -> 11
234
+ self.decode_search = bitarray('000', endian='big')
235
+ self.decode_replace = bitarray('101', endian='big')
236
+ sync_prefix = bitarray(endian='big')
237
+ sync_prefix.frombytes(doubler(b'\xaa\xaa' + sync(0xf8)))
238
+ self.sync_prefix = sync_prefix[:len(fm_sync_prefix)*2]
239
+ def encode(self, pre: bytes) -> bytes:
240
+ pre_bits = bitarray(endian='big')
241
+ pre_bits.frombytes(pre)
242
+ post_bits = bitarray(endian='big')
243
+ post_bits.frombytes(mfm_encode(encode(pre)))
244
+ for x in pre_bits.search(self.encode_search):
245
+ post_bits[x*2+1:x*2+12] = self.encode_replace
246
+ return post_bits.tobytes()
247
+ def decode(self, bits: bitarray) -> bytes:
248
+ for x in bits.search(self.decode_search):
249
+ if x&1 != 0: # Only matches starting on a data bit
250
+ bits[x:x+3] = self.decode_replace
251
+ return decode(bits.tobytes())
252
+
253
+ dec_mmfm = DEC_MMFM()
254
+
255
+ class IBMTrack(codec.Codec):
256
+
257
+ # Subclasses must define these
258
+ time_per_rev: float
259
+ clock: float
260
+
261
+ verify_revs: float = 1
262
+
263
+ def __init__(self, cyl: int, head: int, mode: Mode):
264
+ self.cyl, self.head = cyl, head
265
+ self.sectors: List[Sector] = []
266
+ self.iams: List[IAM] = []
267
+ self.mode = mode
268
+ if mode is Mode.FM or mode is Mode.DEC_RX02:
269
+ self.gap_presync = 6
270
+ self.gapbyte = 0xff
271
+ elif mode is Mode.MFM:
272
+ self.gap_presync = 12
273
+ self.gapbyte = 0x4e
274
+ else:
275
+ raise error.Fatal('Unrecognised IBM mode')
276
+ self.img_bps: Optional[int] = None
277
+
278
+ @property
279
+ def nsec(self) -> int:
280
+ return len(self.sectors)
281
+
282
+ def summary_string(self) -> str:
283
+ nsec, nbad = len(self.sectors), self.nr_missing()
284
+ s = "%s (%d/%d sectors)" % (str(self.mode), nsec - nbad, nsec)
285
+ return s
286
+
287
+ def has_sec(self, sec_id: int):
288
+ return self.sectors[sec_id].crc == 0
289
+
290
+ def nr_missing(self) -> int:
291
+ return len(list(filter(lambda x: x.crc != 0, self.sectors)))
292
+
293
+ def set_img_track(self, tdat: bytes) -> int:
294
+ pos = 0
295
+ self.sectors.sort(key = lambda x: x.idam.r)
296
+ if self.img_bps is not None:
297
+ totsize = len(self.sectors) * self.img_bps
298
+ else:
299
+ totsize = functools.reduce(lambda x, y: x + len(y.dam.data),
300
+ self.sectors, 0)
301
+ if len(tdat) < totsize:
302
+ tdat += bytes(totsize - len(tdat))
303
+ for s in self.sectors:
304
+ s.crc = s.idam.crc = s.dam.crc = 0
305
+ size = len(s.dam.data)
306
+ s.dam.data = tdat[pos:pos+size]
307
+ if self.img_bps is not None:
308
+ pos += self.img_bps
309
+ else:
310
+ pos += size
311
+ self.sectors.sort(key = lambda x: x.start)
312
+ return totsize
313
+
314
+ def get_img_track(self) -> bytearray:
315
+ tdat = bytearray()
316
+ sectors = self.sectors.copy()
317
+ sectors.sort(key = lambda x: x.idam.r)
318
+ for s in sectors:
319
+ tdat += s.dam.data
320
+ if self.img_bps is not None:
321
+ tdat += bytes(self.img_bps - len(s.dam.data))
322
+ return tdat
323
+
324
+ def verify_track(self, flux) -> bool:
325
+ readback_track = self.__class__(self.cyl, self.head, self.mode)
326
+ readback_track.clock = self.clock
327
+ readback_track.time_per_rev = self.time_per_rev
328
+ for iam in self.iams:
329
+ readback_track.iams.append(copy.copy(iam))
330
+ for sec in self.sectors:
331
+ idam, dam = copy.copy(sec.idam), copy.copy(sec.dam)
332
+ idam.crc, dam.crc = 0xffff, 0xffff
333
+ readback_track.sectors.append(Sector(idam, dam))
334
+ readback_track.decode_flux(flux)
335
+ if readback_track.nr_missing() != 0:
336
+ return False
337
+ return self.sectors == readback_track.sectors
338
+
339
+ def mfm_master_track(self) -> bytes:
340
+
341
+ areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
342
+ t = bytes()
343
+
344
+ for a in areas:
345
+ start = a.start//16 - self.gap_presync
346
+ gap = max(start - len(t)//2, 0)
347
+ t += encode(bytes([self.gapbyte] * gap))
348
+ t += encode(bytes(self.gap_presync))
349
+ if isinstance(a, IAM):
350
+ t += mfm_iam_sync_bytes
351
+ t += encode(bytes([Mark.IAM]))
352
+ elif isinstance(a, Sector):
353
+ t += mfm_sync_bytes
354
+ idam = bytes([0xa1, 0xa1, 0xa1, Mark.IDAM,
355
+ a.idam.c, a.idam.h, a.idam.r, a.idam.n])
356
+ idam += struct.pack('>H', crc16.new(idam).crcValue)
357
+ t += encode(idam[3:])
358
+ start = a.dam.start//16 - self.gap_presync
359
+ gap = max(start - len(t)//2, 0)
360
+ t += encode(bytes([self.gapbyte] * gap))
361
+ t += encode(bytes(self.gap_presync))
362
+ t += mfm_sync_bytes
363
+ dam = bytes([0xa1, 0xa1, 0xa1, a.dam.mark]) + a.dam.data
364
+ dam += struct.pack('>H', crc16.new(dam).crcValue)
365
+ t += encode(dam[3:])
366
+
367
+ return t
368
+
369
+ def fm_master_track(self, mmfm_areas=None) -> bytes:
370
+
371
+ areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
372
+ t = bytes()
373
+
374
+ for a in areas:
375
+ start = a.start//16 - self.gap_presync
376
+ gap = max(start - len(t)//2, 0)
377
+ t += encode(bytes([self.gapbyte] * gap))
378
+ t += encode(bytes(self.gap_presync))
379
+ if isinstance(a, IAM):
380
+ t += fm_iam_sync_bytes
381
+ elif isinstance(a, Sector):
382
+ idam = bytes([Mark.IDAM,
383
+ a.idam.c, a.idam.h, a.idam.r, a.idam.n])
384
+ idam += struct.pack('>H', crc16.new(idam).crcValue)
385
+ t += sync(idam[0]) + encode(idam[1:])
386
+ start = a.dam.start//16 - self.gap_presync
387
+ gap = max(start - len(t)//2, 0)
388
+ t += encode(bytes([self.gapbyte] * gap))
389
+ t += encode(bytes(self.gap_presync))
390
+ dam = bytes([a.dam.mark]) + a.dam.data
391
+ dam += struct.pack('>H', crc16.new(dam).crcValue)
392
+ t += sync(dam[0])
393
+ if ((dam[0] & 0xfb) == Mark.DDAM_DEC_MMFM
394
+ and mmfm_areas is not None):
395
+ mmfm_areas.append((dec_mmfm.encode(dam[1:]), len(t)))
396
+ t += encode(bytes([self.gapbyte] * (128+2)))
397
+ else:
398
+ t += encode(dam[1:])
399
+
400
+ return t
401
+
402
+ def master_track(self) -> MasterTrack:
403
+
404
+ t: Union[bytes, bitarray]
405
+
406
+ if self.mode is Mode.FM:
407
+ t = self.fm_master_track()
408
+ elif self.mode is Mode.MFM:
409
+ t = self.mfm_master_track()
410
+ elif self.mode is Mode.DEC_RX02:
411
+ mmfm_areas: List[Tuple[bytes,int]] = list()
412
+ t = self.fm_master_track(mmfm_areas)
413
+
414
+ # Add the pre-index gap.
415
+ tlen = int((self.time_per_rev / self.clock) // 16)
416
+ gap = max(tlen - len(t)//2, 0)
417
+ t += encode(bytes([self.gapbyte] * gap))
418
+
419
+ if self.mode is Mode.FM:
420
+ t = fm_encode(t)
421
+ elif self.mode is Mode.MFM:
422
+ t = mfm_encode(t)
423
+ elif self.mode is Mode.DEC_RX02:
424
+ # Insert the MMFM areas into the doubled-rate FM track
425
+ b = bytearray(doubler(fm_encode(t)))
426
+ for a,o in mmfm_areas:
427
+ b[2*o:2*(o+256+2)] = a
428
+ # Insert the extra clock at the start of each MMFM area
429
+ bits = bitarray(endian='big')
430
+ bits.frombytes(b)
431
+ for _,o in reversed(mmfm_areas):
432
+ bits.insert(16*o, 0)
433
+ # We pass a bitarray to MasterTrack()
434
+ t = bits
435
+
436
+ track = MasterTrack(
437
+ bits = t,
438
+ time_per_rev = self.time_per_rev)
439
+ track.verify = self
440
+ return track
441
+
442
+ @staticmethod
443
+ def mfm_decode_raw(raw: PLLTrack) -> List[TrackArea]:
444
+
445
+ bits, _ = raw.get_all_data()
446
+ areas: List[TrackArea] = []
447
+ idam = None
448
+
449
+ ## 1. Calculate offsets within dump
450
+
451
+ for offs in bits.search(mfm_iam_sync):
452
+ if len(bits) < offs+4*16:
453
+ continue
454
+ mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
455
+ if mark == Mark.IAM:
456
+ areas.append(IAM(offs, offs+4*16))
457
+
458
+ for offs in bits.search(mfm_sync):
459
+
460
+ if len(bits) < offs+4*16:
461
+ continue
462
+ mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
463
+ if mark == Mark.IDAM:
464
+ s, e = offs, offs+10*16
465
+ if len(bits) < e:
466
+ continue
467
+ b = decode(bits[s:e].tobytes())
468
+ c,h,r,n = struct.unpack(">4x4B2x", b)
469
+ crc = crc16.new(b).crcValue
470
+ if idam is not None:
471
+ areas.append(idam)
472
+ idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
473
+ elif mark == Mark.DAM or mark == Mark.DDAM:
474
+ if idam is None or offs - idam.end > 1000:
475
+ areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
476
+ else:
477
+ sz = 128 << idam.n
478
+ s, e = offs, offs+(4+sz+2)*16
479
+ if len(bits) < e:
480
+ continue
481
+ b = decode(bits[s:e].tobytes())
482
+ crc = crc16.new(b).crcValue
483
+ dam = DAM(s, e, crc, mark=mark, data=b[4:-2])
484
+ areas.append(Sector(idam, dam))
485
+ idam = None
486
+ else:
487
+ pass # damaged tracks produce these constantly
488
+
489
+ if idam is not None:
490
+ areas.append(idam)
491
+
492
+ # Convert to offsets within track
493
+ areas.sort(key=lambda x:x.start)
494
+ index = iter([x.nr_bits for x in raw.revolutions])
495
+ p, n = 0, next(index)
496
+ for a in areas:
497
+ if a.start >= n:
498
+ p = n
499
+ try:
500
+ n += next(index)
501
+ except StopIteration:
502
+ n = float('inf')
503
+ a.delta(p)
504
+ areas.sort(key=lambda x:x.start)
505
+
506
+ return areas
507
+
508
+ @staticmethod
509
+ def fm_decode_raw(raw: PLLTrack,
510
+ mmfm_raw: Optional[PLLTrack] = None) -> List[TrackArea]:
511
+
512
+ bits, times = raw.get_all_data()
513
+ areas: List[TrackArea] = []
514
+ idam = None
515
+
516
+ if mmfm_raw is not None:
517
+ mmfm_bits, mmfm_times = mmfm_raw.get_all_data()
518
+ mmfm_iter = mmfm_bits.search(dec_mmfm.sync_prefix)
519
+ mmfm_offs = next(mmfm_iter, None)
520
+ fm_time, prev_fm_offs = 0.0, 0
521
+ mmfm_time, prev_mmfm_offs = 0.0, 0
522
+
523
+ ## 1. Calculate offsets within dump
524
+
525
+ for offs in bits.search(fm_iam_sync):
526
+ offs += 16
527
+ areas.append(IAM(offs, offs+1*16))
528
+
529
+ for offs in bits.search(fm_sync_prefix):
530
+
531
+ # DEC MMFM track: Ensure this looks like an FM mark even at
532
+ # double rate. This also finds the equivalent point in the
533
+ # double-rate bitstream.
534
+ if mmfm_raw is not None:
535
+ fm_time += sum(times[prev_fm_offs:offs])
536
+ prev_fm_offs = offs
537
+ while mmfm_offs is not None:
538
+ mmfm_time += sum(mmfm_times[prev_mmfm_offs:mmfm_offs])
539
+ prev_mmfm_offs = mmfm_offs
540
+ delta = fm_time - mmfm_time
541
+ if delta < 1e-5:
542
+ break
543
+ mmfm_offs = next(mmfm_iter, None)
544
+ # We require a match within 10us in the double-rate bitstream.
545
+ if mmfm_offs is None or abs(delta) > 1e-5:
546
+ continue
547
+
548
+ offs += 16
549
+ if len(bits) < offs+1*16:
550
+ continue
551
+ mark = decode(bits[offs:offs+1*16].tobytes())[0]
552
+ clock = decode(bits[offs-1:offs+1*16-1].tobytes())[0]
553
+ if clock != 0xc7:
554
+ continue
555
+ if mark == Mark.IDAM:
556
+ s, e = offs, offs+7*16
557
+ if len(bits) < e:
558
+ continue
559
+ b = decode(bits[s:e].tobytes())
560
+ c,h,r,n = struct.unpack(">x4B2x", b)
561
+ crc = crc16.new(b).crcValue
562
+ if idam is not None:
563
+ areas.append(idam)
564
+ idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
565
+ elif (mark == Mark.DAM or mark == Mark.DDAM
566
+ or mark == Mark.DAM_TRS80_DIR
567
+ or ((mark & 0xfb) == Mark.DDAM_DEC_MMFM
568
+ and mmfm_raw is not None)):
569
+ if idam is None or offs - idam.end > 1000:
570
+ areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
571
+ continue
572
+ sz = 128 << idam.n
573
+ s, e = offs, offs+(1+sz+2)*16
574
+ if (mark & 0xfb) != Mark.DDAM_DEC_MMFM:
575
+ if len(bits) < e:
576
+ continue
577
+ b = decode(bits[s:e].tobytes())
578
+ else:
579
+ assert mmfm_offs is not None
580
+ ds, de = mmfm_offs+64+1, mmfm_offs+64+1+(sz*2+2)*16
581
+ if len(mmfm_bits) < de:
582
+ continue
583
+ b = bytes([mark]) + dec_mmfm.decode(mmfm_bits[ds:de])
584
+ crc = crc16.new(b).crcValue
585
+ dam = DAM(s, e, crc, mark=mark, data=b[1:-2])
586
+ areas.append(Sector(idam, dam))
587
+ idam = None
588
+ else:
589
+ pass # damaged tracks produce these constantly
590
+
591
+ if idam is not None:
592
+ areas.append(idam)
593
+
594
+ # Convert to offsets within track
595
+ areas.sort(key=lambda x:x.start)
596
+ index = iter([x.nr_bits for x in raw.revolutions])
597
+ p, n = 0, next(index)
598
+ for a in areas:
599
+ if a.start >= n:
600
+ p = n
601
+ try:
602
+ n += next(index)
603
+ except StopIteration:
604
+ n = float('inf')
605
+ a.delta(p)
606
+ areas.sort(key=lambda x:x.start)
607
+
608
+ return areas
609
+
610
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
611
+ flux = track.flux()
612
+ flux.cue_at_index()
613
+ raw = PLLTrack(time_per_rev = self.time_per_rev,
614
+ clock = self.clock, data = flux, pll = pll)
615
+ self.decode_raw(raw, pll, flux)
616
+
617
+ def decode_raw(self, raw: PLLTrack, pll: Optional[PLL],
618
+ flux: Flux) -> None:
619
+
620
+ if self.mode is Mode.FM:
621
+ areas = self.fm_decode_raw(raw)
622
+ elif self.mode is Mode.MFM:
623
+ areas = self.mfm_decode_raw(raw)
624
+ elif self.mode is Mode.DEC_RX02:
625
+ mmfm_raw = PLLTrack(time_per_rev = self.time_per_rev,
626
+ clock = self.clock/2, data = flux, pll = pll)
627
+ areas = self.fm_decode_raw(raw, mmfm_raw)
628
+
629
+ # Add to the deduped lists
630
+ for a in areas:
631
+ dupe = False
632
+ if isinstance(a, IAM):
633
+ for iam in self.iams:
634
+ if dupe := abs(iam.start - a.start) < 1000:
635
+ break
636
+ if not dupe:
637
+ self.iams.append(a)
638
+ elif isinstance(a, Sector):
639
+ for i, sec in enumerate(self.sectors):
640
+ if dupe := abs(sec.start - a.start) < 1000:
641
+ if sec.crc != 0 and a.crc == 0:
642
+ self.sectors[i] = a
643
+ break
644
+ if not dupe:
645
+ self.sectors.append(a)
646
+ self.iams.sort(key=lambda x:x.start)
647
+ self.sectors.sort(key=lambda x:x.start)
648
+
649
+
650
+ class IBMTrack_Fixed(IBMTrack):
651
+
652
+ def __init__(self, cyl: int, head: int, mode: Mode):
653
+ super().__init__(cyl, head, mode)
654
+ self.raw = IBMTrack(cyl, head, mode)
655
+ self.oversized = False
656
+
657
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
658
+ self.raw.clock = self.clock
659
+ self.raw.time_per_rev = self.time_per_rev
660
+ self.raw.decode_flux(track, pll)
661
+ mismatches = set()
662
+ for r in self.raw.sectors:
663
+ if r.idam.crc != 0:
664
+ continue
665
+ matched = False
666
+ for s in self.sectors:
667
+ if (s.idam.c == r.idam.c and
668
+ s.idam.h == r.idam.h and
669
+ s.idam.r == r.idam.r and
670
+ s.idam.n == r.idam.n):
671
+ s.idam.crc = 0
672
+ matched = True
673
+ if r.dam.crc == 0 and s.dam.crc != 0:
674
+ s.dam.crc = s.crc = 0
675
+ s.dam.data = r.dam.data
676
+ s.dam.mark = r.dam.mark
677
+ if not matched:
678
+ mismatches.add((r.idam.c, r.idam.h, r.idam.r, r.idam.n))
679
+ for m in mismatches:
680
+ print('T%d.%d: Ignoring unexpected sector C:%d H:%d R:%d N:%d'
681
+ % (self.cyl, self.head, *m))
682
+
683
+ @classmethod
684
+ def from_config(cls, config: IBMTrack_FixedDef, cyl: int, head: int,
685
+ warn_on_oversize = True):
686
+
687
+ def sec_n(i):
688
+ return config.sz[i] if i < len(config.sz) else config.sz[-1]
689
+
690
+ if config.format_name == 'dec.rx02':
691
+ mode, gaps, mark_dam = Mode.DEC_RX02, FMGaps, Mark.DAM_DEC_MMFM
692
+ synclen = 1 # Mark
693
+ elif config.format_name == 'ibm.mfm':
694
+ mode, gaps, mark_dam = Mode.MFM, MFMGaps, Mark.DAM
695
+ synclen = 4 # A1 A1 A1 Mark
696
+ elif config.format_name == 'ibm.fm':
697
+ mode, gaps, mark_dam = Mode.FM, FMGaps, Mark.DAM
698
+ synclen = 1 # Mark
699
+
700
+ t = cls(cyl, head, mode)
701
+ nsec = config.secs
702
+ t.img_bps = config.img_bps
703
+
704
+ if config.gapbyte is not None:
705
+ t.gapbyte = config.gapbyte
706
+
707
+ if config.iam:
708
+ gap1 = gaps.gap1 if config.gap1 is None else config.gap1
709
+ else:
710
+ gap1 = None
711
+ gap2 = gaps.gap2 if config.gap2 is None else config.gap2
712
+ gap3 = 0 if config.gap3 is None else config.gap3
713
+ gap4a = gaps.gap4a if config.gap4a is None else config.gap4a
714
+
715
+ idx_sz = gap4a
716
+ if gap1 is not None:
717
+ idx_sz += t.gap_presync + synclen + gap1
718
+ idam_sz = t.gap_presync + synclen + 4 + 2 + gap2
719
+ dam_sz_pre = t.gap_presync + synclen
720
+ dam_sz_post = 2 + gap3
721
+
722
+ tracklen = idx_sz + (idam_sz + dam_sz_pre + dam_sz_post) * nsec
723
+ for i in range(nsec):
724
+ tracklen += 128 << sec_n(i)
725
+ tracklen *= 16
726
+
727
+ rate, rpm = config.rate, config.rpm
728
+ if rate == 0:
729
+ # FM: 0 = Micro-diskette (125kbps), 1 = 8-inch disk (250kbps)
730
+ # MFM: 1 = DD (250kbps), 2 = HD (500kbps), 3 = ED (1000kbps)
731
+ for i in (range(2),range(1,4))[mode is Mode.MFM]:
732
+ maxlen = (50000*300//rpm) << i
733
+ maxlen += maxlen * 3 // 100 # Allow a few percent overage
734
+ if tracklen < maxlen:
735
+ break
736
+ rate = 125 << i
737
+
738
+ if mode is Mode.MFM and config.gap2 is None and rate >= 1000:
739
+ # At ED rate the default GAP2 is 41 bytes.
740
+ gap2 = 41
741
+ idam_sz += gap2 - gaps.gap2
742
+ tracklen += 16 * nsec * (gap2 - gaps.gap2)
743
+
744
+ tracklen_bc = rate * 400 * 300 // rpm
745
+
746
+ # Calculate a sensible gap3 value if none is manually specified
747
+ if nsec != 0 and config.gap3 is None:
748
+ space = max(0, tracklen_bc - tracklen)
749
+ no = sec_n(0)
750
+ gap3 = min(space // (16*nsec), gaps.gap3[no])
751
+ dam_sz_post += gap3
752
+ tracklen += 16 * nsec * gap3
753
+
754
+ # Allow for at least 1% pre-index gap, including final gap3
755
+ pre_index_sz = tracklen_bc // 100
756
+ if nsec != 0:
757
+ pre_index_sz = max(0, pre_index_sz - gap3 * 16)
758
+ tracklen += pre_index_sz
759
+
760
+ # Steal some post-index gap if there is insufficient pre-index gap
761
+ if tracklen > tracklen_bc and config.gap4a is None:
762
+ new_gap4a = gap4a // 2
763
+ idx_sz -= gap4a - new_gap4a
764
+ tracklen -= gap4a - new_gap4a
765
+ gap4a = new_gap4a
766
+
767
+ if tracklen > tracklen_bc * 105//100:
768
+ t.oversized = True
769
+ if warn_on_oversize:
770
+ print('T%d.%d: IBM: WARNING: Track is %.2f%% too long'
771
+ % (cyl, head, 100.0*tracklen/tracklen_bc))
772
+ tracklen_bc = max(tracklen_bc, tracklen)
773
+
774
+ t.time_per_rev = 60 / rpm
775
+ t.clock = t.time_per_rev / tracklen_bc
776
+
777
+ pos = gap4a
778
+ if gap1 is not None:
779
+ pos += t.gap_presync
780
+ t.iams = [IAM(pos*16,(pos+synclen)*16)]
781
+ pos += synclen + gap1
782
+
783
+ id0 = config.id
784
+ h = head if config.h is None else config.h
785
+ for sec in sec_map(nsec, config.interleave,
786
+ config.cskew, config.hskew, cyl, head):
787
+ pos += t.gap_presync
788
+ idam = IDAM(pos*16, (pos+synclen+4+2)*16, 0xffff,
789
+ c = cyl, h = h, r = id0+sec, n = sec_n(sec))
790
+ pos += synclen + 4 + 2 + gap2 + t.gap_presync
791
+ size = 128 << idam.n
792
+ datsz = size*2 if mark_dam == Mark.DAM_DEC_MMFM else size
793
+ dam = DAM(pos*16, (pos+synclen+size+2)*16, 0xffff,
794
+ mark=mark_dam, data=b'-=[BAD SECTOR]=-'*(datsz//16))
795
+ t.sectors.append(Sector(idam, dam))
796
+ pos += synclen + size + 2 + gap3
797
+
798
+ return t
799
+
800
+
801
+ class IBMTrack_FixedDef(codec.TrackDef):
802
+
803
+ default_revs = default_revs
804
+
805
+ def __init__(self, format_name: str):
806
+ self.secs = 0
807
+ self.sz: List[int] = []
808
+ self.id = 1
809
+ self.h: Optional[int] = None
810
+ self.format_name = format_name
811
+ self.interleave = 1
812
+ self.cskew, self.hskew = 0, 0
813
+ self.rpm = 300
814
+ self.gap1: Optional[int] = None
815
+ self.gap2: Optional[int] = None
816
+ self.gap3: Optional[int] = None
817
+ self.gap4a: Optional[int] = None
818
+ self.gapbyte: Optional[int] = None
819
+ self.iam = True
820
+ self.rate = 0
821
+ self.img_bps: Optional[int] = None
822
+ self.finalised = False
823
+
824
+ def add_param(self, key: str, val: str) -> None:
825
+ if key == 'secs':
826
+ n = int(val)
827
+ error.check(0 <= n <= 256, '%s out of range' % key)
828
+ self.secs = n
829
+ elif key == 'bps':
830
+ self.sz = []
831
+ for x in val.split(','):
832
+ y = re.match(r'(\d+)\*(\d+)', x)
833
+ if y is not None:
834
+ n, l = int(y.group(1)), int(y.group(2))
835
+ else:
836
+ n, l = int(x), 1
837
+ s = 0
838
+ while n != 128<<s:
839
+ s += 1
840
+ error.check(s <= 6, 'bps value out of range')
841
+ for _ in range(l):
842
+ self.sz.append(s)
843
+ elif key == 'interleave':
844
+ n = int(val)
845
+ error.check(1 <= n <= 255, '%s out of range' % key)
846
+ self.interleave = n
847
+ elif key in ['id', 'cskew', 'hskew']:
848
+ n = int(val, base=0)
849
+ error.check(0 <= n <= 255, '%s out of range' % key)
850
+ setattr(self, key, n)
851
+ elif key in ['gap1', 'gap2', 'gap3', 'gap4a', 'gapbyte', 'h']:
852
+ if val == 'auto':
853
+ n = None
854
+ else:
855
+ n = int(val, base=0)
856
+ error.check(0 <= n <= 255, '%s out of range' % key)
857
+ setattr(self, key, n)
858
+ elif key == 'iam':
859
+ error.check(val in ['yes', 'no'], 'bad iam value')
860
+ self.iam = val == 'yes'
861
+ elif key in ['rate', 'rpm']:
862
+ n = int(val)
863
+ error.check(1 <= n <= 2000, '%s out of range' % key)
864
+ setattr(self, key, n)
865
+ elif key == 'img_bps':
866
+ n = int(val)
867
+ error.check(128 <= n <= 8192, '%s out of range' % key)
868
+ self.img_bps = n
869
+ else:
870
+ raise error.Fatal('unrecognised track option %s' % key)
871
+
872
+ def finalise(self) -> None:
873
+ if self.finalised:
874
+ return
875
+ error.check(self.iam or self.gap1 is None,
876
+ 'gap1 specified but no iam')
877
+ error.check(self.secs == 0 or len(self.sz) != 0,
878
+ 'sector size not specified')
879
+ error.check((self.img_bps is None
880
+ or self.img_bps >= max(self.sz, default=0)),
881
+ 'img_bps cannot be smaller than sector data size')
882
+ self.finalised = True
883
+
884
+ def mk_track(self, cyl: int, head: int) -> IBMTrack_Fixed:
885
+ return IBMTrack_Fixed.from_config(self, cyl, head)
886
+
887
+
888
+ class IBMTrack_Empty(IBMTrack):
889
+
890
+ def __init__(self, cyl, head):
891
+ # Fake some parameters for master_track()
892
+ super().__init__(cyl, head, Mode.MFM)
893
+ self.time_per_rev = 0.2
894
+ self.clock = 2e-6
895
+
896
+ def summary_string(self) -> str:
897
+ return "IBM Empty"
898
+
899
+ def set_img_track(self, tdat: bytes) -> int:
900
+ raise error.Fatal('ibm.scan: Cannot handle IMG input data')
901
+
902
+
903
+ class IBMTrack_Scan(codec.Codec):
904
+
905
+ RATES = [ 125, 250, 500 ]
906
+ RPMS = [ 300, 360 ]
907
+ BEST_GUESS = None
908
+
909
+ def __init__(self, cyl: int, head: int, config: IBMTrack_ScanDef):
910
+ self.cyl, self.head = cyl, head
911
+ self.rate, self.rpm = config.rate, config.rpm
912
+ self.track: IBMTrack = IBMTrack_Empty(cyl, head)
913
+
914
+ @property
915
+ def nsec(self) -> int:
916
+ return self.track.nsec
917
+
918
+ def summary_string(self) -> str:
919
+ return self.track.summary_string()
920
+
921
+ def has_sec(self, sec_id: int):
922
+ return self.track.has_sec(sec_id)
923
+
924
+ def nr_missing(self) -> int:
925
+ return self.track.nr_missing()
926
+
927
+ def master_track(self) -> MasterTrack:
928
+ return self.track.master_track()
929
+
930
+ def set_img_track(self, tdat: bytes) -> int:
931
+ return self.track.set_img_track(tdat)
932
+
933
+ def get_img_track(self) -> bytearray:
934
+ return self.track.get_img_track()
935
+
936
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
937
+
938
+ # Add more data to an existing track instance?
939
+ if not isinstance(self.track, IBMTrack_Empty):
940
+ self.track.decode_flux(track, pll)
941
+ return
942
+
943
+ # Try our best guess first.
944
+ if IBMTrack_Scan.BEST_GUESS is not None:
945
+ time_per_rev, clock, mode = IBMTrack_Scan.BEST_GUESS
946
+ t = IBMTrack(self.cyl, self.head, mode)
947
+ t.clock, t.time_per_rev = clock, time_per_rev
948
+ t.decode_flux(track, pll)
949
+ # Perfect match, no missing sectors?
950
+ if t.nsec != 0 and t.nr_missing() == 0:
951
+ self.track = t
952
+ return
953
+
954
+ rates = self.RATES if self.rate is None else [self.rate]
955
+ rpms = self.RPMS if self.rpm is None else [self.rpm]
956
+
957
+ flux = track.flux()
958
+ flux.cue_at_index()
959
+
960
+ # Scan at various rates & modes to find at least one sector
961
+ for rpm in rpms:
962
+ time_per_rev = 60 / rpm
963
+ for rate in rates:
964
+ clock = 5e-4 / rate
965
+ raw = PLLTrack(time_per_rev = time_per_rev,
966
+ clock = clock, data = flux, pll = pll)
967
+ for mode in [Mode.MFM, Mode.FM]:
968
+ t = IBMTrack(self.cyl, self.head, mode)
969
+ t.clock, t.time_per_rev = clock, time_per_rev
970
+ t.decode_raw(raw, pll, flux)
971
+ if ((t.nsec - t.nr_missing())
972
+ > (self.track.nsec - self.track.nr_missing())):
973
+ self.track = t
974
+
975
+ # If we found a match, remember it as a best guess for the next track.
976
+ if not isinstance(self.track, IBMTrack_Empty):
977
+ t = self.track
978
+ IBMTrack_Scan.BEST_GUESS = (t.time_per_rev, t.clock, t.mode)
979
+
980
+
981
+ class IBMTrack_ScanDef(codec.TrackDef):
982
+
983
+ default_revs = default_revs
984
+
985
+ def __init__(self, format_name: str):
986
+ self.rpm: Optional[int] = None
987
+ self.rate: Optional[int] = None
988
+
989
+ def add_param(self, key: str, val: str) -> None:
990
+ if key in ['rate', 'rpm']:
991
+ n = int(val)
992
+ error.check(1 <= n <= 2000, '%s out of range' % key)
993
+ setattr(self, key, n)
994
+ else:
995
+ raise error.Fatal('unrecognised track option %s' % key)
996
+
997
+ def finalise(self) -> None:
998
+ pass
999
+
1000
+ def mk_track(self, cyl: int, head: int) -> IBMTrack_Scan:
1001
+ return IBMTrack_Scan(cyl, head, self)
1002
+
1003
+ # Local variables:
1004
+ # python-indent: 4
1005
+ # End: