/ a2woz / a2rimage.py
a2rimage.py
 1  # SPDX-FileCopyrightText: 2019 4am
 2  #
 3  # SPDX-License-Identifier: MIT
 4  
 5  from .wozardry import Track, raise_if
 6  from . import a2rchery
 7  import bitarray
 8  import collections
 9  
10  class A2RSeekError(a2rchery.A2RError): pass
11  
12  class A2RImage:
13      def __init__(self, iostream):
14          self.tracks = collections.OrderedDict()
15          self.a2r_image = a2rchery.A2RReader(stream=iostream)
16          self._speed = 32
17  
18      @property
19      def speed(self):
20          if self._speed is None:
21              fluxxen = flux_record["data"][1:]
22              speeds = [(len([1 for i in fluxxen[:8192] if i%t==0]), t) for t in range(0x1e,0x23)]
23              self._speed = speeds[-1][1]
24          return self._speed
25  
26      def to_json(self):
27          return self.a2r_image.to_json()
28  
29      def to_bits(self, flux_record):
30          """|flux_record| is a dictionary of 'capture_type', 'data_length', 'tick_count', and 'data'"""
31          bits = bitarray.bitarray()
32          if not flux_record or flux_record["capture_type"] != a2rchery.kCaptureTiming:
33              return bits
34          tick_count = flux_record['tick_count']
35          fluxxen = flux_record["data"][1:]
36          speed = self.speed
37          flux_total = flux_start = -speed//2
38          rev_total = 0
39          for flux_value in fluxxen:
40              rev_total += flux_value
41              flux_total += flux_value
42              if flux_value == 0xFF:
43                  continue
44              if flux_total >= speed:
45                  bits.extend("0" * (flux_total // speed))
46              bits.extend("1")
47              flux_total = flux_start
48  #            if rev_total > tick_count:
49  #                print(f"bailing out at {rev_total}")
50  #                break
51          return bits
52  
53      def seek(self, track_num):
54          if type(track_num) != float:
55              track_num = float(track_num)
56          if track_num < 0.0 or \
57             track_num > 35.0 or \
58             track_num.as_integer_ratio()[1] not in (1,2,4):
59              raise A2RSeekError("Invalid track %s" % track_num)
60          location = int(track_num * 4)
61          if not self.tracks.get(location):
62              # just return the bits from the first flux read
63              # (if the caller determines that they're not good, it will call reseek()
64              # which is smarter but takes longer)
65              bits = bitarray.bitarray()
66              if location in self.a2r_image.flux:
67                  global flux_
68                  flux_record = self.a2r_image.flux[location][0]
69                  bits = self.to_bits(flux_record)
70                  est_bit_len = round(flux_record['tick_count'] / self.speed)
71              else:
72                  est_bit_len = None
73              self.tracks[location] = Track(bits, len(bits), est_bit_len)
74          return self.tracks[location]
75  
76      def reseek(self, track_num):
77          location = int(track_num * 4)
78          # reset cached speed so we'll recalculate it
79          self.speed = 0
80          # read the rest of the flux records and concatenate them on the end
81          # of the existing bitstream (this assumes you've called seek() before
82          # on this track)
83          all_bits = self.tracks[location].bits
84          for flux_record in self.a2r_image.flux[location][1:]:
85              all_bits.extend(self.to_bits(flux_record))
86          self.tracks[location] = Track(all_bits, len(all_bits))
87          return self.tracks[location]