Bladeren bron

gw: KryoFlux stream support

Keir Fraser 4 jaren geleden
bovenliggende
commit
5c49b76c8f

+ 234 - 0
scripts/greaseweazle/image/kryoflux.py

@@ -0,0 +1,234 @@
+# greaseweazle/image/kryoflux.py
+#
+# Written & released by Keir Fraser <keir.xen@gmail.com>
+#
+# This is free and unencumbered software released into the public domain.
+# See the file COPYING for more details, or visit <http://unlicense.org>.
+
+import struct, re, math, os
+import itertools as it
+
+from greaseweazle import error
+from greaseweazle.flux import Flux
+from .image import Image
+
+mck = 18432000 * 73 / 14 / 2
+sck = mck / 2
+
+class Op:
+    Nop1  =  8
+    Nop2  =  9
+    Nop3  = 10
+    Ovl16 = 11
+    Flux3 = 12
+    OOB   = 13
+
+class OOB:
+    StreamInfo =  1
+    Index      =  2
+    StreamEnd  =  3
+    KFInfo     =  4
+    EOF        = 13
+
+class KryoFlux(Image):
+
+    def __init__(self, name):
+        if os.path.isdir(name):
+            self.basename = os.path.join(name, '')
+        else:
+            m = re.search("(\d{2}.[01])?.raw$", name)
+            self.basename = name[:m.start()]
+
+
+    @classmethod
+    def to_file(cls, name, start_cyl, nr_sides):
+        cls = cls(name)
+        cls.cyl, cls.side = start_cyl, 0
+        cls.nr_sides = nr_sides
+        return cls
+
+    @classmethod
+    def from_file(cls, name):
+        return cls(name)
+
+
+    def get_track(self, cyl, side):
+
+        name = self.basename + '%02d.%d.raw' % (cyl, side)
+        try:
+            with open(name, 'rb') as f:
+                dat = f.read()
+        except FileNotFoundError:
+            return None
+
+        # Parse the index-pulse stream positions.
+        index = []
+        idx = 0
+        while idx < len(dat):
+            op = dat[idx]
+            if op == Op.OOB:
+                oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
+                idx += 4
+                if oob_op == OOB.Index:
+                    pos, = struct.unpack('<I', dat[idx:idx+4])
+                    index.append(pos)
+                elif oob_op == OOB.EOF:
+                    break
+                idx += oob_sz
+            elif op == Op.Nop3 or op == Op.Flux3:
+                idx += 3
+            elif op <= 7 or op == Op.Nop2:
+                idx += 2
+            else:
+                idx += 1
+
+        # Build the flux and index lists for the Flux object.
+        flux, flux_list, index_list = [], [], []
+        val, index_idx, stream_idx, idx = 0, 0, 0, 0
+        while idx < len(dat):
+            if index_idx < len(index) and stream_idx >= index[index_idx]:
+                # We've passed an index marker.
+                index_list.append(sum(flux))
+                flux_list += flux
+                flux = []
+                index_idx += 1
+            op = dat[idx]
+            if op <= 7:
+                # Flux2
+                val += (op << 8) + dat[idx+1]
+                flux.append(val)
+                val = 0
+                stream_idx += 2
+                idx += 2
+            elif op <= 10:
+                # Nop1, Nop2, Nop3
+                nr = op-7
+                stream_idx += nr
+                idx += nr
+            elif op == Op.Ovl16:
+                # Ovl16
+                val += 0x10000
+                stream_idx += 1
+                idx += 1
+            elif op == Op.Flux3:
+                # Flux3
+                val += (dat[idx+1] << 8) + dat[idx+2]
+                flux.append(val)
+                val = 0
+                stream_idx += 3
+                idx += 3
+            elif op == Op.OOB:
+                # OOB
+                oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
+                idx += 4
+                if oob_op == OOB.StreamInfo or oob_op == OOB.StreamEnd:
+                    pos, = struct.unpack('<I', dat[idx:idx+4])
+                    error.check(pos == stream_idx,
+                                "Out-of-sync during KryoFlux stream read")
+                elif oob_op == OOB.EOF:
+                    break
+                idx += oob_sz
+            else:
+                # Flux1
+                val += op
+                flux.append(val)
+                val = 0
+                stream_idx += 1
+                idx += 1
+
+        flux_list += flux
+
+        # Crop partial first revolution.
+        if len(index_list) > 1:
+            short_index, index_list = index_list[0], index_list[1:]
+            flux = 0
+            for i in range(len(flux_list)):
+                if flux >= short_index:
+                    break
+                flux += flux_list[i]
+            flux_list = flux_list[i:]
+
+        return Flux(index_list, flux_list, sck)
+
+
+    def append_track(self, track):
+        """Converts @track into a KryoFlux stream file."""
+
+        # Check if we should insert an OOB record for the next index mark.
+        def check_index(prev_flux):
+            nonlocal index_idx, dat
+            if index_idx < len(index) and total >= index[index_idx]:
+                dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12,
+                                   stream_idx,
+                                   round(index[index_idx] - total + prev_flux),
+                                   round(index[index_idx]/8))
+                index_idx += 1
+
+        # Emit a resampled flux value to the KryoFlux data stream.
+        def emit(f):
+            nonlocal stream_idx, dat, total
+            while f >= 0x10000:
+                stream_idx += 1
+                dat.append(Op.Ovl16)
+                f -= 0x10000
+                total += 0x10000
+                check_index(0x10000)
+            if f >= 0x800:
+                stream_idx += 3
+                dat += struct.pack('>BH', Op.Flux3, f)
+            elif Op.OOB < f < 0x100:
+                stream_idx += 1
+                dat.append(f)
+            else:
+                stream_idx += 2
+                dat += struct.pack('>H', f)
+            total += f
+            check_index(f)
+
+        flux = track.flux()
+        factor = sck / flux.sample_freq
+
+        # Start the data stream with a dummy index because our Flux objects
+        # are cued to index.
+        dat = bytearray()
+        dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12, 0, 0, 0)
+
+        # Prefix-sum list of resampled index timings.
+        index = list(it.accumulate(map(lambda x: x*factor, flux.index_list)))
+        index_idx = 0
+        
+        stream_idx, total, rem = 0, 0, 0.0
+        for x in flux.list:
+            y = x * factor + rem
+            f = round(y)
+            rem = y - f
+            emit(f)
+
+        # We may not have enough flux to get to the final index value.
+        # Generate a dummy flux just enough to get us there.
+        if index_idx < len(index):
+            emit(math.ceil(index[index_idx] - total) + 1)
+
+        # Emit StreamEnd and EOF blocks to terminate the stream.
+        dat += struct.pack('<2BH2I', Op.OOB, OOB.StreamEnd, 8, stream_idx, 0)
+        dat += struct.pack('<2BH', Op.OOB, OOB.EOF, 0x0d0d)
+
+        name = self.basename + '%02d.%d.raw' % (self.cyl, self.side)
+        with open(name, 'wb') as f:
+                f.write(dat)
+
+        self.side += 1
+        if self.side >= self.nr_sides:
+            self.side = 0
+            self.cyl += 1
+        
+
+    def __enter__(self):
+        return self
+    def __exit__(self, type, value, tb):
+        pass
+
+
+# Local variables:
+# python-indent: 4
+# End:

+ 3 - 3
scripts/greaseweazle/image/scp.py

@@ -168,7 +168,7 @@ class SCP(Image):
             while to_index < x:
                 # Append to the TDH for the previous full revolution
                 tdh += struct.pack("<III",
-                                   int(round(flux.index_list[rev]*factor)),
+                                   round(flux.index_list[rev]*factor),
                                    (len(dat) - len_at_index) // 2,
                                    4 + nr_revs*12 + len_at_index)
                 # Set up for the next revolution
@@ -183,7 +183,7 @@ class SCP(Image):
             # Process the current flux sample into SCP "bitcell" format
             to_index -= x
             y = x * factor + rem
-            val = int(round(y))
+            val = round(y)
             if (val & 65535) == 0:
                 val += 1
             rem = y - val
@@ -197,7 +197,7 @@ class SCP(Image):
         # Header for last track(s) in case we ran out of flux timings.
         while rev < nr_revs:
             tdh += struct.pack("<III",
-                               int(round(flux.index_list[rev]*factor)),
+                               round(flux.index_list[rev]*factor),
                                (len(dat) - len_at_index) // 2,
                                4 + nr_revs*12 + len_at_index)
             len_at_index = len(dat)

+ 9 - 5
scripts/greaseweazle/tools/util.py

@@ -66,11 +66,15 @@ def get_image_class(name):
     image_types = { '.adf': 'ADF',
                     '.scp': 'SCP',
                     '.hfe': 'HFE',
-                    '.ipf': 'IPF' }
-    _, ext = os.path.splitext(name)
-    error.check(ext.lower() in image_types,
-                "%s: Unrecognised file suffix '%s'" % (name, ext))
-    typename = image_types[ext.lower()]
+                    '.ipf': 'IPF',
+                    '.raw': 'KryoFlux' }
+    if os.path.isdir(name):
+        typename = 'KryoFlux'
+    else:
+        _, ext = os.path.splitext(name)
+        error.check(ext.lower() in image_types,
+                    "%s: Unrecognised file suffix '%s'" % (name, ext))
+        typename = image_types[ext.lower()]
     mod = importlib.import_module('greaseweazle.image.' + typename.lower())
     return mod.__dict__[typename]
 

+ 1 - 1
scripts/greaseweazle/tools/write.py

@@ -71,7 +71,7 @@ def write_from_image(usb, args, image):
             flux_list = []
             for x in flux.list:
                 y = x * factor + rem
-                val = int(round(y))
+                val = round(y)
                 rem = y - val
                 flux_list.append(val)