scp.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # greaseweazle/image/scp.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
  8. from greaseweazle import error
  9. from greaseweazle.flux import Flux
  10. class SCP:
  11. # 40MHz
  12. sample_freq = 40000000
  13. def __init__(self, start_cyl, nr_sides):
  14. self.nr_sides = nr_sides
  15. self.nr_revs = None
  16. self.track_list = [(None,None)] * (start_cyl*2)
  17. @classmethod
  18. def to_file(cls, start_cyl, nr_sides):
  19. scp = cls(start_cyl, nr_sides)
  20. return scp
  21. @classmethod
  22. def from_file(cls, dat):
  23. header = struct.unpack("<3s9BI", dat[0:16])
  24. (sig, _, _, nr_revs, _, _, flags, _, _, _, _) = header
  25. error.check(sig == b"SCP", "SCP: Bad signature")
  26. trk_offs = struct.unpack("<168I", dat[16:0x2b0])
  27. scp = cls(0, 2)
  28. scp.nr_revs = nr_revs
  29. for trknr in range(len(trk_offs)):
  30. trk_off = trk_offs[trknr]
  31. if trk_off == 0:
  32. scp.track_list.append((None, None))
  33. continue
  34. # Parse the SCP track header and extract the flux data.
  35. thdr = dat[trk_off:trk_off+4+12*nr_revs]
  36. sig, tnr, _, _, s_off = struct.unpack("<3sB3I", thdr[:16])
  37. error.check(sig == b"TRK", "SCP: Missing track signature")
  38. error.check(tnr == trknr, "SCP: Wrong track number in header")
  39. _, e_nr, e_off = struct.unpack("<3I", thdr[-12:])
  40. tdat = dat[trk_off+s_off:trk_off+e_off+e_nr*2]
  41. scp.track_list.append((thdr, tdat))
  42. return scp
  43. def get_track(self, cyl, side, writeout=False):
  44. off = cyl*2 + side
  45. if off >= len(self.track_list):
  46. return None
  47. tdh, dat = self.track_list[off]
  48. if dat is None:
  49. return None
  50. tdh = tdh[4:]
  51. # Writeout requires only a single revolution
  52. if writeout:
  53. tdh = tdh[:12]
  54. _, nr, _ = struct.unpack("<3I", tdh)
  55. dat = dat[:nr*2]
  56. index_list = []
  57. while tdh:
  58. ticks, _, _ = struct.unpack("<3I", tdh[:12])
  59. index_list.append(ticks)
  60. tdh = tdh[12:]
  61. # Decode the SCP flux data into a simple list of flux times.
  62. flux_list = []
  63. val = 0
  64. for i in range(0, len(dat), 2):
  65. x = dat[i]*256 + dat[i+1]
  66. if x == 0:
  67. val += 65536
  68. continue
  69. flux_list.append(val + x)
  70. val = 0
  71. return Flux(index_list, flux_list, SCP.sample_freq)
  72. # append_track:
  73. # Converts a Flux object into a Supercard Pro Track and appends it to
  74. # the current image-in-progress.
  75. def append_track(self, flux):
  76. def _append(self, tdh, dat):
  77. self.track_list.append((tdh, dat))
  78. if self.nr_sides == 1:
  79. self.track_list.append((None, None))
  80. nr_revs = len(flux.index_list)
  81. if not self.nr_revs:
  82. self.nr_revs = nr_revs
  83. else:
  84. assert self.nr_revs == nr_revs
  85. factor = SCP.sample_freq / flux.sample_freq
  86. trknr = len(self.track_list)
  87. tdh = struct.pack("<3sB", b"TRK", trknr)
  88. dat = bytearray()
  89. len_at_index = rev = 0
  90. to_index = flux.index_list[0]
  91. rem = 0.0
  92. for x in flux.list:
  93. # Does the next flux interval cross the index mark?
  94. while to_index < x:
  95. # Append to the TDH for the previous full revolution
  96. tdh += struct.pack("<III",
  97. int(round(flux.index_list[rev]*factor)),
  98. (len(dat) - len_at_index) // 2,
  99. 4 + nr_revs*12 + len_at_index)
  100. # Set up for the next revolution
  101. len_at_index = len(dat)
  102. rev += 1
  103. if rev >= nr_revs:
  104. # We're done: We simply discard any surplus flux samples
  105. _append(self, tdh, dat)
  106. return
  107. to_index += flux.index_list[rev]
  108. # Process the current flux sample into SCP "bitcell" format
  109. to_index -= x
  110. y = x * factor + rem
  111. val = int(round(y))
  112. if (val & 65535) == 0:
  113. val += 1
  114. rem = y - val
  115. while val >= 65536:
  116. dat.append(0)
  117. dat.append(0)
  118. val -= 65536
  119. dat.append(val>>8)
  120. dat.append(val&255)
  121. # Header for last track(s) in case we ran out of flux timings.
  122. while rev < nr_revs:
  123. tdh += struct.pack("<III",
  124. int(round(flux.index_list[rev]*factor)),
  125. (len(dat) - len_at_index) // 2,
  126. 4 + nr_revs*12 + len_at_index)
  127. len_at_index = len(dat)
  128. rev += 1
  129. _append(self, tdh, dat)
  130. def get_image(self):
  131. # Track start/end seem to be cylinder numbers for single-sided images.
  132. single_sided = 1 if self.nr_sides == 1 else 0
  133. e_trk = len(self.track_list) // (2 if single_sided else 1) - 1
  134. # Generate the TLUT and concatenate all the tracks together.
  135. trk_offs = bytearray()
  136. trk_dat = bytearray()
  137. for tdh, dat in self.track_list:
  138. if dat is None:
  139. trk_offs += struct.pack("<I", 0)
  140. else:
  141. trk_offs += struct.pack("<I", 0x2b0 + len(trk_dat))
  142. trk_dat += tdh + dat
  143. trk_offs += bytes(0x2a0 - len(trk_offs))
  144. # Calculate checksum over all data (except 16-byte image header).
  145. csum = 0
  146. for x in trk_offs:
  147. csum += x
  148. for x in trk_dat:
  149. csum += x
  150. # Generate the image header.
  151. header = struct.pack("<3s9BI",
  152. b"SCP", # Signature
  153. 0, # Version
  154. 0x80, # DiskType = Other
  155. self.nr_revs, 0, e_trk,
  156. 0x01, # Flags = Index
  157. 0, # 16-bit cell width
  158. single_sided,
  159. 0, # 25ns capture
  160. csum & 0xffffffff)
  161. # Concatenate it all together and send it back.
  162. return header + trk_offs + trk_dat
  163. # Local variables:
  164. # python-indent: 4
  165. # End: