kryoflux.py 7.0 KB

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