scp.py 6.1 KB

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