Browse Source

Modify the trackset/trackspec syntax

Keir Fraser 4 years ago
parent
commit
5c7cc23819

+ 1 - 1
scripts/greaseweazle/codec/amiga/amigados.py

@@ -11,7 +11,7 @@ from bitarray import bitarray
 
 from greaseweazle.track import MasterTrack, RawTrack
 
-default_cyls = (0,79)
+default_trackset = 'c=0-79:h=0-1'
 default_revs = 2
 
 sync_bytes = b'\x44\x89\x44\x89'

+ 9 - 6
scripts/greaseweazle/tools/erase.py

@@ -23,9 +23,9 @@ def erase(usb, args):
     del flux
 
     for t in args.tracks:
-        cyl, side = t.cyl, t.side
-        print("\rErasing Track %u.%u..." % (cyl, side), end="")
-        usb.seek(t.physical_cyl, side)
+        cyl, head = t.cyl, t.head
+        print("\rErasing Track %u.%u..." % (cyl, head), end="")
+        usb.seek(t.physical_cyl, head)
         usb.erase_track(drive_ticks * 1.1)
 
     print()
@@ -37,15 +37,18 @@ def main(argv):
     parser.add_argument("--device", help="greaseweazle device name")
     parser.add_argument("--drive", type=util.drive_letter, default='A',
                         help="drive to write (A,B,0,1,2)")
-    parser.add_argument("--tracks", type=util.trackset,
-                        default='c=0-81:s=0-1',
-                        help="which tracks to read")
+    parser.add_argument("--tracks", type=util.TrackSet,
+                        help="which tracks to erase")
     parser.description = description
     parser.prog += ' ' + argv[1]
     args = parser.parse_args(argv[2:])
 
     try:
         usb = util.usb_open(args.device)
+        tracks = util.TrackSet('c=0-81:h=0-1')
+        if args.tracks is not None:
+            tracks.update_from_trackspec(args.tracks.trackspec)
+        args.tracks = tracks
         print("Erasing %s" % (args.tracks))
         util.with_drive_selected(erase, usb, args)
     except USB.CmdError as error:

+ 37 - 25
scripts/greaseweazle/tools/read.py

@@ -63,15 +63,15 @@ def read_and_normalise(usb, args, revs):
     return flux
 
 
-def read_with_retry(usb, args, cyl, side, decoder):
+def read_with_retry(usb, args, cyl, head, decoder):
     flux = read_and_normalise(usb, args, args.revs)
     if decoder is None:
         return flux
-    dat = decoder(cyl, side, flux)
+    dat = decoder(cyl, head, flux)
     if dat.nr_missing() != 0:
         for retry in range(3):
             print("T%u.%u: %s - %d sectors missing - Retrying (%d)"
-                  % (cyl, side, dat.summary_string(),
+                  % (cyl, head, dat.summary_string(),
                      dat.nr_missing(), retry+1))
             flux = read_and_normalise(usb, args, max(args.revs, 3))
             dat.decode_raw(flux)
@@ -81,14 +81,23 @@ def read_with_retry(usb, args, cyl, side, decoder):
 
 
 def print_summary(args, summary):
-    print("H. S: Cyls %d-%d -->" % args.tracks.cyl)
+    s = 'Cyl-> '
+    p = -1
+    for c in args.tracks.cyls:
+        s += ' ' if c//10==p else str(c//10)
+        p = c//10
+    print(s)
+    s = 'H. S: '
+    for c in args.tracks.cyls:
+        s += str(c%10)
+    print(s)
     tot_sec = good_sec = 0
-    for side in range(args.tracks.side[0], args.tracks.side[1]+1):
-        nsec = max(x.nsec for x in summary[side])
+    for head in args.tracks.heads:
+        nsec = max(summary[x].nsec for x in summary if x[1] == head)
         for sec in range(nsec):
-            print("%d.%2d: " % (side, sec), end="")
-            for cyl in range(args.tracks.cyl[0], args.tracks.cyl[1]+1):
-                s = summary[side][cyl-args.tracks.cyl[0]]
+            print("%d.%2d: " % (head, sec), end="")
+            for cyl in args.tracks.cyls:
+                s = summary[cyl,head]
                 if sec > s.nsec:
                     print(" ", end="")
                 else:
@@ -96,23 +105,24 @@ def print_summary(args, summary):
                     if s.has_sec(sec): good_sec += 1
                     print("." if s.has_sec(sec) else "X", end="")
             print()
-    print("Found %d sectors of %d (%d%%)" %
-          (good_sec, tot_sec, good_sec*100/tot_sec))
+    if tot_sec != 0:
+        print("Found %d sectors of %d (%d%%)" %
+              (good_sec, tot_sec, good_sec*100/tot_sec))
 
 
 def read_to_image(usb, args, image, decoder=None):
     """Reads a floppy disk and dumps it into a new image file.
     """
 
-    summary = [[],[]]
+    summary = dict()
 
     for t in args.tracks:
-        cyl, side = t.cyl, t.side
-        usb.seek(t.physical_cyl, side)
-        dat = read_with_retry(usb, args, cyl, side, decoder)
-        print("T%u.%u: %s" % (cyl, side, dat.summary_string()))
-        summary[side].append(dat)
-        image.emit_track(cyl, side, dat)
+        cyl, head = t.cyl, t.head
+        usb.seek(t.physical_cyl, head)
+        dat = read_with_retry(usb, args, cyl, head, decoder)
+        print("T%u.%u: %s" % (cyl, head, dat.summary_string()))
+        summary[cyl,head] = dat
+        image.emit_track(cyl, head, dat)
 
     if decoder is not None:
         print_summary(args, summary)
@@ -127,7 +137,7 @@ def main(argv):
     parser.add_argument("--format", help="disk format")
     parser.add_argument("--revs", type=int,
                         help="number of revolutions to read per track")
-    parser.add_argument("--tracks", type=util.trackset,
+    parser.add_argument("--tracks", type=util.TrackSet,
                         help="which tracks to read")
     parser.add_argument("--rate", type=int, help="data rate (kbit/s)")
     parser.add_argument("--rpm", type=int, help="convert drive speed to RPM")
@@ -143,7 +153,7 @@ def main(argv):
         image_class = util.get_image_class(args.file)
         if not args.format and hasattr(image_class, 'default_format'):
             args.format = image_class.default_format
-        decoder = None
+        decoder, def_tracks = None, None
         if args.format:
             try:
                 mod = importlib.import_module('greaseweazle.codec.'
@@ -151,13 +161,15 @@ def main(argv):
                 decoder = mod.decode_track
             except (ModuleNotFoundError, AttributeError) as ex:
                 raise error.Fatal("Unknown format '%s'" % args.format) from ex
-            if args.tracks is None:
-                args.tracks = util.trackset('c=0-81:s=0-1')
-                args.tracks.cyl = mod.default_cyls
+            def_tracks = util.TrackSet(mod.default_trackset)
             if args.revs is None: args.revs = mod.default_revs
-        if args.tracks is None:
-            args.tracks = util.trackset('c=0-81:s=0-1')
+        if def_tracks is None:
+            def_tracks = util.TrackSet('c=0-81:h=0-1')
         if args.revs is None: args.revs = 3
+        if args.tracks is not None:
+            def_tracks.update_from_trackspec(args.tracks.trackspec)
+        args.tracks = def_tracks
+        
         print("Reading %s revs=%d" % (args.tracks, args.revs))
         with open_image(args, image_class) as image:
             util.with_drive_selected(read_to_image, usb, args, image,

+ 77 - 51
scripts/greaseweazle/tools/util.py

@@ -46,75 +46,101 @@ def drive_letter(letter):
         raise argparse.ArgumentTypeError("invalid drive letter: '%s'" % letter)
     return types[letter.upper()]
 
-def range_str(s, e):
-    str = "%d" % s
-    if s != e: str += "-%d" % e
+def range_str(l):
+    if len(l) == 0:
+        return '<none>'
+    p, str = None, ''
+    for i in l:
+        if p is not None and i == p[1]+1:
+            p = p[0], i
+            continue
+        if p is not None:
+            str += ('%d,' % p[0]) if p[0] == p[1] else ('%d-%d,' % p)
+        p = (i,i)
+    if p is not None:
+        str += ('%d' % p[0]) if p[0] == p[1] else ('%d-%d' % p)
     return str
 
 class TrackSet:
 
     class TrackIter:
+        """Iterate over a TrackSet in physical <cyl,head> order."""
         def __init__(self, ts):
             l = []
-            for c in range(ts.cyl[0], ts.cyl[1]+1):
-                for s in range(ts.side[0], ts.side[1]+1):
-                    pc = (c, c*2)[ts.double_step]
-                    pc += ts.side_off[0] if s == ts.side[0] else ts.side_off[1]
-                    l.append((pc, s, c))
+            for c in ts.cyls:
+                for h in ts.heads:
+                    pc = c*ts.step + ts.h_off[h]
+                    l.append((pc, h, c))
             l.sort()
             self.l = iter(l)
         def __next__(self):
-            self.physical_cyl, self.side, self.cyl = next(self.l)
+            self.physical_cyl, self.head, self.cyl = next(self.l)
             return self
     
-    def __init__(self):
-        self.cyl = (0,79)
-        self.side = (0,1)
-        self.double_step = False
-
+    def __init__(self, trackspec):
+        self.cyls = list()
+        self.heads = list()
+        self.h_off = [0]*2
+        self.step = 1
+        self.trackspec = ''
+        self.update_from_trackspec(trackspec)
+
+    def update_from_trackspec(self, trackspec):
+        """Update a TrackSet based on a trackspec."""
+        self.trackspec += trackspec
+        for x in trackspec.split(':'):
+            k,v = x.split('=')
+            if k == 'c':
+                cyls = [False]*100
+                for crange in v.split(','):
+                    m = re.match('(\d\d?)(-(\d\d?))?$', crange)
+                    if m is None: raise ValueError()
+                    if m.group(3) is None:
+                        s,e = int(m.group(1)), int(m.group(1))
+                    else:
+                        s,e = int(m.group(1)), int(m.group(3))
+                    for c in range(s, e+1):
+                        cyls[c] = True
+                self.cyls = []
+                for c in range(len(cyls)):
+                    if cyls[c]: self.cyls.append(c)
+            elif k == 'h':
+                heads = [False]*2
+                for hrange in v.split(','):
+                    m = re.match('([01])(-([01]))?$', hrange)
+                    if m is None: raise ValueError()
+                    if m.group(3) is None:
+                        s,e = int(m.group(1)), int(m.group(1))
+                    else:
+                        s,e = int(m.group(1)), int(m.group(3))
+                    for h in range(s, e+1):
+                        heads[h] = True
+                self.heads = []
+                for h in range(len(heads)):
+                    if heads[h]: self.heads.append(h)
+            elif k == 'h_off':
+                m = re.match('([01])([+-][\d])$', v)
+                if m is None: raise ValueError()
+                self.h_off[int(m.group(1))] = int(m.group(2))
+            elif k == 'step':
+                self.step = int(v)
+                if self.step <= 0: raise ValueError()
+            else:
+                raise ValueError()
+        
     def __str__(self):
-        a, b = self.cyl
-        s = 'c=%d' % a
-        if a != b: s += '-%d' % b
-        if self.double_step: s += 'x2'
-        a, c = self.side
-        b, d = self.side_off
-        s += ':s=%d' % a
-        if b != 0: s += '[%s%d]' % ('+' if b >= 0 else '', b)
-        if a != c:
-            s += '-%d' % c
-            if d != 0: s += '[%s%d]' % ('+' if d >= 0 else '', d)
+        s = 'c=%s' % range_str(self.cyls)
+        s += ':h=%s' % range_str(self.heads)
+        for i in range(len(self.h_off)):
+            x = self.h_off[i]
+            if x != 0:
+                s += ':h_off=%d%s%d' % (i, '+' if x >= 0 else '', x)
+        if self.step != 1: s += ':step=%d' % self.step
         return s
 
     def __iter__(self):
         return self.TrackIter(self)
 
-def trackset(tracks):
-    ts = TrackSet()
-    for x in tracks.split(':'):
-        k,v = x.split('=')
-        if k == 'c':
-            m = re.match('(\d+)(-(\d+))?(x2)?$', v)
-            if m is None: raise ValueError()
-            if m.group(3) is None:
-                ts.cyl = int(m.group(1)), int(m.group(1))
-            else:
-                ts.cyl = int(m.group(1)), int(m.group(3))
-            if m.group(4) is not None:
-                ts.double_step = True
-        elif k == 's':
-            m = re.match('(?P<s>\d+)(\[(?P<s_off>[-+]\d)\])?'
-                         '(-(?P<e>\d+)(\[(?P<e_off>[-+]\d)\])?)?$', v)
-            if m is None: raise ValueError()
-            s, e = m.group('s'), m.group('e')
-            ts.side = int(s), int(e if e is not None else s)
-            s, e = m.group('s_off'), m.group('e_off')
-            s = int(s) if s is not None else 0
-            e = int(e) if e is not None else 0
-            ts.side_off = s,e
-    return ts
-
-
 def split_opts(seq):
     """Splits a name from its list of options."""
     parts = seq.split('::')

+ 11 - 8
scripts/greaseweazle/tools/write.py

@@ -46,16 +46,16 @@ def write_from_image(usb, args, image):
 
     for t in args.tracks:
 
-        cyl, side = t.cyl, t.side
+        cyl, head = t.cyl, t.head
 
-        track = image.get_track(cyl, side)
+        track = image.get_track(cyl, head)
         if track is None and not args.erase_empty:
             continue
 
         print("\r%sing Track %u.%u..." %
-              ("Writ" if track is not None else "Eras", cyl, side),
+              ("Writ" if track is not None else "Eras", cyl, head),
               end="", flush=True)
-        usb.seek(t.physical_cyl, side)
+        usb.seek(t.physical_cyl, head)
             
         if track is None:
             usb.erase_track(drive_ticks * 1.1)
@@ -98,7 +98,7 @@ def write_from_image(usb, args, image):
                 break
             formatter.print(" Retry %d" % (retry + 1))
         formatter.erase()
-        error.check(verified, "Failed to write Track %u.%u" % (cyl, side))
+        error.check(verified, "Failed to write Track %u.%u" % (cyl, head))
 
     print()
     if not_verified_count == 0:
@@ -120,9 +120,8 @@ def main(argv):
     parser.add_argument("--device", help="greaseweazle device name")
     parser.add_argument("--drive", type=util.drive_letter, default='A',
                         help="drive to write (A,B,0,1,2)")
-    parser.add_argument("--tracks", type=util.trackset,
-                        default='c=0-81:s=0-1',
-                        help="which tracks to read")
+    parser.add_argument("--tracks", type=util.TrackSet,
+                        help="which tracks to write")
     parser.add_argument("--erase-empty", action="store_true",
                         help="erase empty tracks (default: skip)")
     parser.add_argument("--no-verify", action="store_true",
@@ -135,6 +134,10 @@ def main(argv):
     try:
         usb = util.usb_open(args.device)
         image = open_image(args)
+        tracks = util.TrackSet('c=0-81:h=0-1')
+        if args.tracks is not None:
+            tracks.update_from_trackspec(args.tracks.trackspec)
+        args.tracks = tracks
         print("Writing %s" % (args.tracks))
         util.with_drive_selected(write_from_image, usb, args, image)
     except USB.CmdError as error: