kryoflux.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # greaseweazle/image/kryoflux.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. import struct, re, math, os
  8. import itertools as it
  9. from greaseweazle import error
  10. from greaseweazle.flux import Flux
  11. from .image import Image
  12. mck = 18432000 * 73 / 14 / 2
  13. sck = mck / 2
  14. class Op:
  15. Nop1 = 8
  16. Nop2 = 9
  17. Nop3 = 10
  18. Ovl16 = 11
  19. Flux3 = 12
  20. OOB = 13
  21. class OOB:
  22. StreamInfo = 1
  23. Index = 2
  24. StreamEnd = 3
  25. KFInfo = 4
  26. EOF = 13
  27. class KryoFlux(Image):
  28. def __init__(self, name):
  29. if os.path.isdir(name):
  30. self.basename = os.path.join(name, '')
  31. else:
  32. m = re.search("(\d{2}.[01])?.raw$", name)
  33. self.basename = name[:m.start()]
  34. @classmethod
  35. def to_file(cls, name):
  36. return cls(name)
  37. @classmethod
  38. def from_file(cls, name):
  39. return cls(name)
  40. def get_track(self, cyl, side):
  41. name = self.basename + '%02d.%d.raw' % (cyl, side)
  42. try:
  43. with open(name, 'rb') as f:
  44. dat = f.read()
  45. except FileNotFoundError:
  46. return None
  47. # Parse the index-pulse stream positions.
  48. index = []
  49. idx = 0
  50. while idx < len(dat):
  51. op = dat[idx]
  52. if op == Op.OOB:
  53. oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
  54. idx += 4
  55. if oob_op == OOB.Index:
  56. pos, = struct.unpack('<I', dat[idx:idx+4])
  57. index.append(pos)
  58. elif oob_op == OOB.EOF:
  59. break
  60. idx += oob_sz
  61. elif op == Op.Nop3 or op == Op.Flux3:
  62. idx += 3
  63. elif op <= 7 or op == Op.Nop2:
  64. idx += 2
  65. else:
  66. idx += 1
  67. # Build the flux and index lists for the Flux object.
  68. flux, flux_list, index_list = [], [], []
  69. val, index_idx, stream_idx, idx = 0, 0, 0, 0
  70. while idx < len(dat):
  71. if index_idx < len(index) and stream_idx >= index[index_idx]:
  72. # We've passed an index marker.
  73. index_list.append(sum(flux))
  74. flux_list += flux
  75. flux = []
  76. index_idx += 1
  77. op = dat[idx]
  78. if op <= 7:
  79. # Flux2
  80. val += (op << 8) + dat[idx+1]
  81. flux.append(val)
  82. val = 0
  83. stream_idx += 2
  84. idx += 2
  85. elif op <= 10:
  86. # Nop1, Nop2, Nop3
  87. nr = op-7
  88. stream_idx += nr
  89. idx += nr
  90. elif op == Op.Ovl16:
  91. # Ovl16
  92. val += 0x10000
  93. stream_idx += 1
  94. idx += 1
  95. elif op == Op.Flux3:
  96. # Flux3
  97. val += (dat[idx+1] << 8) + dat[idx+2]
  98. flux.append(val)
  99. val = 0
  100. stream_idx += 3
  101. idx += 3
  102. elif op == Op.OOB:
  103. # OOB
  104. oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
  105. idx += 4
  106. if oob_op == OOB.StreamInfo or oob_op == OOB.StreamEnd:
  107. pos, = struct.unpack('<I', dat[idx:idx+4])
  108. error.check(pos == stream_idx,
  109. "Out-of-sync during KryoFlux stream read")
  110. elif oob_op == OOB.EOF:
  111. break
  112. idx += oob_sz
  113. else:
  114. # Flux1
  115. val += op
  116. flux.append(val)
  117. val = 0
  118. stream_idx += 1
  119. idx += 1
  120. flux_list += flux
  121. # Crop partial first revolution.
  122. if len(index_list) > 1:
  123. short_index, index_list = index_list[0], index_list[1:]
  124. flux = 0
  125. for i in range(len(flux_list)):
  126. if flux >= short_index:
  127. break
  128. flux += flux_list[i]
  129. flux_list = flux_list[i:]
  130. return Flux(index_list, flux_list, sck)
  131. def emit_track(self, cyl, side, track):
  132. """Converts @track into a KryoFlux stream file."""
  133. # Check if we should insert an OOB record for the next index mark.
  134. def check_index(prev_flux):
  135. nonlocal index_idx, dat
  136. if index_idx < len(index) and total >= index[index_idx]:
  137. dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12,
  138. stream_idx,
  139. round(index[index_idx] - total + prev_flux),
  140. round(index[index_idx]/8))
  141. index_idx += 1
  142. # Emit a resampled flux value to the KryoFlux data stream.
  143. def emit(f):
  144. nonlocal stream_idx, dat, total
  145. while f >= 0x10000:
  146. stream_idx += 1
  147. dat.append(Op.Ovl16)
  148. f -= 0x10000
  149. total += 0x10000
  150. check_index(0x10000)
  151. if f >= 0x800:
  152. stream_idx += 3
  153. dat += struct.pack('>BH', Op.Flux3, f)
  154. elif Op.OOB < f < 0x100:
  155. stream_idx += 1
  156. dat.append(f)
  157. else:
  158. stream_idx += 2
  159. dat += struct.pack('>H', f)
  160. total += f
  161. check_index(f)
  162. flux = track.flux()
  163. factor = sck / flux.sample_freq
  164. # Start the data stream with a dummy index because our Flux objects
  165. # are cued to index.
  166. dat = bytearray()
  167. dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12, 0, 0, 0)
  168. # Prefix-sum list of resampled index timings.
  169. index = list(it.accumulate(map(lambda x: x*factor, flux.index_list)))
  170. index_idx = 0
  171. stream_idx, total, rem = 0, 0, 0.0
  172. for x in flux.list:
  173. y = x * factor + rem
  174. f = round(y)
  175. rem = y - f
  176. emit(f)
  177. # We may not have enough flux to get to the final index value.
  178. # Generate a dummy flux just enough to get us there.
  179. if index_idx < len(index):
  180. emit(math.ceil(index[index_idx] - total) + 1)
  181. # A dummy cell so that we definitely have *something* after the
  182. # final OOB.Index, so that all parsers should register the Index.
  183. emit(round(sck*12)) # 12us
  184. # Emit StreamEnd and EOF blocks to terminate the stream.
  185. dat += struct.pack('<2BH2I', Op.OOB, OOB.StreamEnd, 8, stream_idx, 0)
  186. dat += struct.pack('<2BH', Op.OOB, OOB.EOF, 0x0d0d)
  187. name = self.basename + '%02d.%d.raw' % (cyl, side)
  188. with open(name, 'wb') as f:
  189. f.write(dat)
  190. def __enter__(self):
  191. return self
  192. def __exit__(self, type, value, tb):
  193. pass
  194. # Local variables:
  195. # python-indent: 4
  196. # End: