Переглянути джерело

gw: Faster reading and writing of AmigaDOS tracks.
No index-cueing required on read & verify.
For now, writes remain index cued.

Keir Fraser 4 роки тому
батько
коміт
ba1c9c1b9d

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

@@ -12,7 +12,7 @@ from bitarray import bitarray
 from greaseweazle.track import MasterTrack, RawTrack
 
 default_trackset = 'c=0-79:h=0-1'
-default_revs = 2
+default_revs = 1.1
 
 sync_bytes = b'\x44\x89\x44\x89'
 sync = bitarray(endian='big')
@@ -60,18 +60,17 @@ class AmigaDOS:
         for sec in self.map:
             self.sector[sec] = bytes(16), tdat[sec*512:(sec+1)*512]
 
-    def flux_for_writeout(self):
-        return self.raw_track().flux_for_writeout()
+    def flux_for_writeout(self, *args, **kwargs):
+        return self.raw_track().flux_for_writeout(args, kwargs)
 
-    def flux(self):
-        return self.raw_track().flux()
+    def flux(self, *args, **kwargs):
+        return self.raw_track().flux(args, kwargs)
 
 
     def decode_raw(self, track):
         raw = RawTrack(clock = 2e-6, data = track)
-        bits, times = raw.bitarray, raw.timearray
+        bits, _ = raw.get_all_data()
 
-        sectors = bits.search(sync)
         for offs in bits.itersearch(sync):
 
             if self.nr_missing() == 0:
@@ -132,6 +131,7 @@ class AmigaDOS:
             bits = mfm_encode(t),
             time_per_rev = 0.2)
         track.verify = self
+        track.verify_revs = default_revs
         return track
 
 

+ 7 - 0
scripts/greaseweazle/flux.py

@@ -133,6 +133,13 @@ class WriteoutFlux(Flux):
     def flux_for_writeout(self):
         return self
  
+
+    @property
+    def ticks_per_rev(self):
+        """Mean time between index pulses, in sample ticks"""
+        return sum(self.index_list) / len(self.index_list)
+
+
 # Local variables:
 # python-indent: 4
 # End:

+ 3 - 1
scripts/greaseweazle/image/hfe.py

@@ -69,7 +69,9 @@ class HFE(Image):
 
 
     def emit_track(self, cyl, side, track):
-        raw = RawTrack(clock = 5e-4 / self.bitrate, data = track)
+        flux = track.flux()
+        flux.cue_at_index()
+        raw = RawTrack(clock = 5e-4 / self.bitrate, data = flux)
         bits, _ = raw.get_revolution(0)
         bits.bytereverse()
         self.to_track[cyl,side] = (len(bits), bits.tobytes())

+ 7 - 1
scripts/greaseweazle/image/scp.py

@@ -39,6 +39,7 @@ class SCP(Image):
         self.opts = SCPOpts()
         self.nr_revs = None
         self.to_track = dict()
+        self.index_cued = True
 
     
     def side_count(self):
@@ -175,6 +176,8 @@ class SCP(Image):
         """
 
         flux = track.flux()
+        if not flux.index_cued:
+            self.index_cued = False
 
         nr_revs = len(flux.index_list)
         if not self.nr_revs:
@@ -275,12 +278,15 @@ class SCP(Image):
             csum += x
 
         # Generate the image header.
+        flags = 2 # 96TPI
+        if self.index_cued:
+            flags |= 1 # Index-Cued
         header = struct.pack("<3s9BI",
                              b"SCP",    # Signature
                              0,         # Version
                              0x80,      # DiskType = Other
                              self.nr_revs, 0, ntracks-1,
-                             0x03,      # Flags = Index, 96TPI
+                             flags,
                              0,         # 16-bit cell width
                              single_sided,
                              0,         # 25ns capture

+ 12 - 33
scripts/greaseweazle/tools/read.py

@@ -29,43 +29,15 @@ def open_image(args, image_class):
     return image
 
 
-def normalise_rpm(flux, rpm):
-    """Adjust all revolutions in Flux object to have specified rotation speed.
-    """
-
-    index_list, freq = flux.index_list, flux.sample_freq
-    
-    norm_to_index = 60/rpm * flux.sample_freq
-    norm_flux = []
-
-    to_index, index_list = index_list[0], index_list[1:]
-    factor = norm_to_index / to_index
-    
-    for x in flux.list:
-        to_index -= x
-        if to_index >= 0:
-            norm_flux.append(x*factor)
-            continue
-        if not index_list:
-            break
-        n_to_index, index_list = index_list[0], index_list[1:]
-        n_factor = norm_to_index / n_to_index
-        norm_flux.append((x+to_index)*factor - to_index*n_factor)
-        to_index, factor = n_to_index, n_factor
-
-    return Flux([norm_to_index]*len(flux.index_list), norm_flux, freq)
-
-
-def read_and_normalise(usb, args, revs):
-    flux = usb.read_track(revs)
-    flux.cue_at_index()
+def read_and_normalise(usb, args, revs, ticks=0):
+    flux = usb.read_track(revs=revs, ticks=ticks)
     if args.rpm is not None:
-        flux = normalise_rpm(flux, args.rpm)
+        flux.scale((60/args.rpm) / flux.time_per_rev)
     return flux
 
 
 def read_with_retry(usb, args, cyl, head, decoder):
-    flux = read_and_normalise(usb, args, args.revs)
+    flux = read_and_normalise(usb, args, args.revs, args.ticks)
     if decoder is None:
         return flux
     dat = decoder(cyl, head, flux)
@@ -115,6 +87,13 @@ def read_to_image(usb, args, image, decoder=None):
     """Reads a floppy disk and dumps it into a new image file.
     """
 
+    args.ticks = 0
+    if isinstance(args.revs, float):
+        # Measure drive RPM.
+        # We will adjust the flux intervals per track to allow for this.
+        args.ticks = int(usb.read_track(2).ticks_per_rev * args.revs)
+        args.revs = 2
+
     summary = dict()
 
     for t in args.tracks:
@@ -171,7 +150,7 @@ def main(argv):
             def_tracks.update_from_trackspec(args.tracks.trackspec)
         args.tracks = def_tracks
         
-        print("Reading %s revs=%d" % (args.tracks, args.revs))
+        print(("Reading %s revs=" % args.tracks) + str(args.revs))
         with open_image(args, image_class) as image:
             util.with_drive_selected(read_to_image, usb, args, image,
                                      decoder=decoder)

+ 13 - 10
scripts/greaseweazle/tools/write.py

@@ -36,9 +36,10 @@ class Formatter:
 # Writes the specified image file to floppy disk.
 def write_from_image(usb, args, image):
 
-    # @drive_ticks is the time in Greaseweazle ticks between index pulses.
+    # Measure drive RPM.
     # We will adjust the flux intervals per track to allow for this.
-    drive_ticks = usb.read_track(2).ticks_per_rev
+    drive = usb.read_track(2)
+    del drive.list
 
     verified_count, not_verified_count = 0, 0
 
@@ -54,16 +55,16 @@ def write_from_image(usb, args, image):
               ("Writ" if track is not None else "Eras", cyl, head),
               end="", flush=True)
         usb.seek(t.physical_cyl, head)
-            
+
         if track is None:
-            usb.erase_track(drive_ticks * 1.1)
+            usb.erase_track(drive.ticks_per_rev * 1.1)
             continue
 
         flux = track.flux_for_writeout()
-            
+
         # @factor adjusts flux times for speed variations between the
         # read-in and write-out drives.
-        factor = drive_ticks / flux.index_list[0]
+        factor = drive.ticks_per_rev / flux.index_list[0]
 
         # Convert the flux samples to Greaseweazle sample frequency.
         rem = 0.0
@@ -89,10 +90,12 @@ def write_from_image(usb, args, image):
                 not_verified_count += 1
                 verified = True
                 break
-            v_revs = 1 if track.splice == 0 else 2
-            v_flux = usb.read_track(v_revs)
-            v_flux.cue_at_index()
-            v_flux.scale(flux.time_per_rev / v_flux.time_per_rev)
+            v_revs, v_ticks = track.verify_revs, 0
+            if isinstance(v_revs, float):
+                v_ticks = int(drive.ticks_per_rev * v_revs)
+                v_revs = 2
+            v_flux = usb.read_track(revs = v_revs, ticks = v_ticks)
+            v_flux.scale(flux.time_per_rev / drive.time_per_rev)
             verified = track.verify.verify_track(v_flux)
             if verified:
                 verified_count += 1

+ 43 - 25
scripts/greaseweazle/track.py

@@ -48,10 +48,10 @@ class MasterTrack:
         #s += str(binascii.hexlify(self.bits.tobytes()))
         return s
 
-    def flux_for_writeout(self):
-        return self.flux(for_writeout=True)
+    def flux_for_writeout(self, cue_at_index=True):
+        return self.flux(for_writeout=True, cue_at_index=cue_at_index)
 
-    def flux(self, for_writeout=False):
+    def flux(self, for_writeout=False, cue_at_index=True):
 
         # We're going to mess with the track data, so take a copy.
         bits = self.bits.copy()
@@ -91,19 +91,32 @@ class MasterTrack:
             # Similarly modify the last bit of the weak region.
             bits[e-1] = not(bits[e-2] or bits[e])
 
-        # Rotate data to start at the index (writes are always aligned there).
-        index = -self.splice % bitlen
-        if index != 0:
-            bits = bits[index:] + bits[:index]
-            bit_ticks = bit_ticks[index:] + bit_ticks[:index]
-        splice_at_index = index < 4 or bitlen - index < 4
+        if cue_at_index:
+            # Rotate data to start at the index.
+            index = -self.splice % bitlen
+            if index != 0:
+                bits = bits[index:] + bits[:index]
+                bit_ticks = bit_ticks[index:] + bit_ticks[:index]
+            splice_at_index = index < 4 or bitlen - index < 4
+        else:
+            splice_at_index = False
 
         if not for_writeout:
             # Do not extend the track for reliable writeout to disk.
             pass
+        elif not cue_at_index:
+            # We write the track wherever it may fall (uncued).
+            # We stretch the track with extra header gap bytes, in case the
+            # drive spins slow and we need more length to create an overlap.
+            # Thus if the drive spins slow, the track gets a longer header.
+            pos = 4
+            # We stretch by 10 percent, which is way more than enough.
+            rep = bitlen // (10 * 32)
+            bit_ticks = bit_ticks[pos:pos+32] * rep + bit_ticks[pos:]
+            bits = bits[pos:pos+32] * rep + bits[pos:]
         elif splice_at_index:
             # Splice is at the index (or within a few bitcells of it).
-            # We stretch the track with extra bytes of filler, in case the
+            # We stretch the track with extra footer gap bytes, in case the
             # drive motor spins slower than expected and we need more filler
             # to get us to the index pulse (where the write will terminate).
             # Thus if the drive spins slow, the track gets a longer footer.
@@ -143,7 +156,7 @@ class MasterTrack:
         # Package up the flux for return.
         flux = WriteoutFlux(ticks_to_index, flux_list,
                             ticks_to_index / self.time_per_rev,
-                            index_cued = True,
+                            index_cued = cue_at_index,
                             terminate_at_index = splice_at_index)
         return flux
 
@@ -151,7 +164,7 @@ class MasterTrack:
 # Track data generated from flux.
 class RawTrack:
 
-    def __init__(self, clock = 2e-6, data = None):
+    def __init__(self, clock, data):
         self.clock = clock
         self.clock_max_adj = 0.10
         self.pll_period_adj = 0.05
@@ -159,16 +172,18 @@ class RawTrack:
         self.bitarray = bitarray(endian='big')
         self.timearray = []
         self.revolutions = []
-        if data is not None:
-            self.append_revolutions(data)
+        self.import_flux_data(data)
 
 
     def __str__(self):
         s = "\nRaw Track: %d revolutions\n" % len(self.revolutions)
         for rev in range(len(self.revolutions)):
             b, _ = self.get_revolution(rev)
-            s += "Revolution %u: " % rev
+            s += "Revolution %u (%u bits): " % (rev, len(b))
             s += str(binascii.hexlify(b.tobytes())) + "\n"
+        b = self.bitarray[sum(self.revolutions):]
+        s += "Tail (%u bits): " % (len(b))
+        s += str(binascii.hexlify(b.tobytes())) + "\n"
         return s[:-1]
 
 
@@ -178,7 +193,11 @@ class RawTrack:
         return self.bitarray[start:end], self.timearray[start:end]
 
 
-    def append_revolutions(self, data):
+    def get_all_data(self):
+        return self.bitarray, self.timearray
+
+
+    def import_flux_data(self, data):
 
         flux = data.flux()
         freq = flux.sample_freq
@@ -188,14 +207,16 @@ class RawTrack:
         clock_max = self.clock * (1 + self.clock_max_adj)
         ticks = 0.0
 
-        index_iter = iter(map(lambda x: x/freq, flux.index_list))
+        index_iter = it.chain(iter(map(lambda x: x/freq, flux.index_list)),
+                              [float('inf')])
 
         bits, times = bitarray(endian='big'), []
         to_index = next(index_iter)
 
         # Make sure there's enough time in the flux list to cover all
         # revolutions by appending a "large enough" final flux value.
-        for x in it.chain(flux.list, [sum(flux.index_list)]):
+        tail = max(0, sum(flux.index_list) - sum(flux.list) + clock*freq*2)
+        for x in it.chain(flux.list, [tail]):
 
             # Gather enough ticks to generate at least one bitcell.
             ticks += x / freq
@@ -213,10 +234,7 @@ class RawTrack:
                     self.timearray += times
                     self.revolutions.append(len(times))
                     assert len(times) == len(bits)
-                    try:
-                        to_index += next(index_iter)
-                    except StopIteration:
-                        return
+                    to_index += next(index_iter)
                     bits, times = bitarray(endian='big'), []
 
                 ticks -= clock
@@ -242,9 +260,9 @@ class RawTrack:
             times[-1] += ticks - new_ticks
             ticks = new_ticks
 
-        # We can't get here: We should run out of indexes before we run
-        # out of flux.
-        assert False
+        # Append trailing bits.
+        self.bitarray += bits
+        self.timearray += times
 
 
 # Local variables: