|
@@ -0,0 +1,275 @@
|
|
|
+# greaseweazle/image/ipf.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 os
|
|
|
+import platform
|
|
|
+import ctypes as ct
|
|
|
+from bitarray import bitarray
|
|
|
+from greaseweazle.flux import Flux
|
|
|
+
|
|
|
+class CapsDateTimeExt(ct.Structure):
|
|
|
+ _pack_ = 1
|
|
|
+ _fields_ = [
|
|
|
+ ('year', ct.c_uint),
|
|
|
+ ('month', ct.c_uint),
|
|
|
+ ('day', ct.c_uint),
|
|
|
+ ('hour', ct.c_uint),
|
|
|
+ ('min', ct.c_uint),
|
|
|
+ ('sec', ct.c_uint),
|
|
|
+ ('tick', ct.c_uint)]
|
|
|
+
|
|
|
+class CapsImageInfo(ct.Structure):
|
|
|
+ platform_name = [
|
|
|
+ "N/A", "Amiga", "Atari ST", "IBM PC", "Amstrad CPC",
|
|
|
+ "Spectrum", "Sam Coupe", "Archimedes", "C64", "Atari (8-bit)" ]
|
|
|
+ _pack_ = 1
|
|
|
+ _fields_ = [
|
|
|
+ ('type', ct.c_uint), # image type
|
|
|
+ ('release', ct.c_uint), # release ID
|
|
|
+ ('revision', ct.c_uint), # release revision ID
|
|
|
+ ('mincylinder', ct.c_uint), # lowest cylinder number
|
|
|
+ ('maxcylinder', ct.c_uint), # highest cylinder number
|
|
|
+ ('minhead', ct.c_uint), # lowest head number
|
|
|
+ ('maxhead', ct.c_uint), # highest head number
|
|
|
+ ('crdt', CapsDateTimeExt), # image creation date.time
|
|
|
+ ('platform', ct.c_uint * 4)] # intended platform(s)
|
|
|
+
|
|
|
+class CapsTrackInfoT2(ct.Structure):
|
|
|
+ _pack_ = 1
|
|
|
+ _fields_ = [
|
|
|
+ ('type', ct.c_uint), # track type
|
|
|
+ ('cylinder', ct.c_uint), # cylinder#
|
|
|
+ ('head', ct.c_uint), # head#
|
|
|
+ ('sectorcnt', ct.c_uint), # available sectors
|
|
|
+ ('sectorsize', ct.c_uint), # sector size, unused
|
|
|
+ ('trackbuf', ct.POINTER(ct.c_ubyte)), # track buffer memory
|
|
|
+ ('tracklen', ct.c_uint), # track buffer memory length
|
|
|
+ ('timelen', ct.c_uint), # timing buffer length
|
|
|
+ ('timebuf', ct.POINTER(ct.c_uint)), # timing buffer
|
|
|
+ ('overlap', ct.c_int), # overlap position
|
|
|
+ ('startbit', ct.c_uint), # start position of the decoding
|
|
|
+ ('wseed', ct.c_uint), # weak bit generator data
|
|
|
+ ('weakcnt', ct.c_uint)] # number of weak data areas
|
|
|
+
|
|
|
+class CapsSectorInfo(ct.Structure):
|
|
|
+ _pack_ = 1
|
|
|
+ _fields_ = [
|
|
|
+ ('descdatasize', ct.c_uint), # data size in bits from IPF descriptor
|
|
|
+ ('descgapsize', ct.c_uint), # gap size in bits from IPF descriptor
|
|
|
+ ('datasize', ct.c_uint), # data size in bits from decoder
|
|
|
+ ('gapsize', ct.c_uint), # gap size in bits from decoder
|
|
|
+ ('datastart', ct.c_uint), # data start pos in bits from decoder
|
|
|
+ ('gapstart', ct.c_uint), # gap start pos in bits from decoder
|
|
|
+ ('gapsizews0', ct.c_uint), # gap size before write splice
|
|
|
+ ('gapsizews1', ct.c_uint), # gap size after write splice
|
|
|
+ ('gapws0mode', ct.c_uint), # gap size mode before write splice
|
|
|
+ ('gapws1mode', ct.c_uint), # gap size mode after write splice
|
|
|
+ ('celltype', ct.c_uint), # bitcell type
|
|
|
+ ('enctype', ct.c_uint)] # encoder type
|
|
|
+
|
|
|
+class CapsDataInfo(ct.Structure):
|
|
|
+ _pack_ = 1
|
|
|
+ _fields_ = [
|
|
|
+ ('type', ct.c_uint), # data type
|
|
|
+ ('start', ct.c_uint), # start position
|
|
|
+ ('size', ct.c_uint)] # size in bits
|
|
|
+
|
|
|
+class DI_LOCK:
|
|
|
+ INDEX = 1<<0
|
|
|
+ ALIGN = 1<<1
|
|
|
+ DENVAR = 1<<2
|
|
|
+ DENAUTO = 1<<3
|
|
|
+ DENNOISE = 1<<4
|
|
|
+ NOISE = 1<<5
|
|
|
+ NOISEREV = 1<<6
|
|
|
+ MEMREF = 1<<7
|
|
|
+ UPDATEFD = 1<<8
|
|
|
+ TYPE = 1<<9
|
|
|
+ DENALT = 1<<10
|
|
|
+ OVLBIT = 1<<11
|
|
|
+ TRKBIT = 1<<12
|
|
|
+ NOUPDATE = 1<<13
|
|
|
+ SETWSEED = 1<<14
|
|
|
+ def_flags = (DENVAR | UPDATEFD | TYPE | OVLBIT | TRKBIT)
|
|
|
+
|
|
|
+
|
|
|
+class IPF:
|
|
|
+
|
|
|
+ def __init__(self, start_cyl, nr_sides):
|
|
|
+ self.lib = get_libcaps()
|
|
|
+ self.start_cyl = start_cyl
|
|
|
+ self.nr_sides = nr_sides
|
|
|
+
|
|
|
+ def __del__(self):
|
|
|
+ try:
|
|
|
+ self.lib.CAPSUnlockAllTracks(self.iid)
|
|
|
+ self.lib.CAPSUnlockImage(self.iid)
|
|
|
+ self.lib.CAPSRemImage(self.iid)
|
|
|
+ del(self.iid)
|
|
|
+ except AttributeError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ pi = self.pi
|
|
|
+ s = "IPF Image File:"
|
|
|
+ s += "\n SPS ID: %04d (rev %d)" % (pi.release, pi.revision)
|
|
|
+ s += "\n Platform: "
|
|
|
+ nr_platforms = 0
|
|
|
+ for p in pi.platform:
|
|
|
+ if p == 0 and nr_platforms != 0:
|
|
|
+ break
|
|
|
+ if nr_platforms > 0:
|
|
|
+ s += ", "
|
|
|
+ s += pi.platform_name[p]
|
|
|
+ nr_platforms += 1
|
|
|
+ s += ("\n Created: %d/%d/%d %02d:%02d:%02d"
|
|
|
+ % (pi.crdt.year, pi.crdt.month, pi.crdt.day,
|
|
|
+ pi.crdt.hour, pi.crdt.min, pi.crdt.sec))
|
|
|
+ s += ("\n Cyls: %d-%d Heads: %d-%d"
|
|
|
+ % (pi.mincylinder, pi.maxcylinder, pi.minhead, pi.maxhead))
|
|
|
+ return s
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def from_filename(cls, name):
|
|
|
+
|
|
|
+ ipf = cls(0, 0)
|
|
|
+
|
|
|
+ ipf.iid = ipf.lib.CAPSAddImage()
|
|
|
+ assert ipf.iid >= 0, "Could not create IPF image container"
|
|
|
+ cname = ct.c_char_p(name.encode())
|
|
|
+ res = ipf.lib.CAPSLockImage(ipf.iid, cname)
|
|
|
+ assert res == 0, "Could not open IPF image '%s'" % name
|
|
|
+ res = ipf.lib.CAPSLoadImage(ipf.iid, DI_LOCK.def_flags)
|
|
|
+ assert res == 0, "Could not load IPF image '%s'" % name
|
|
|
+ ipf.pi = CapsImageInfo()
|
|
|
+ res = ipf.lib.CAPSGetImageInfo(ct.byref(ipf.pi), ipf.iid)
|
|
|
+ assert res == 0
|
|
|
+ print(ipf)
|
|
|
+
|
|
|
+ return ipf
|
|
|
+
|
|
|
+
|
|
|
+ def get_track(self, cyl, head, writeout=False):
|
|
|
+ pi = self.pi
|
|
|
+ if head < pi.minhead or head > pi.maxhead:
|
|
|
+ return None
|
|
|
+ if cyl < pi.mincylinder or cyl > pi.maxcylinder:
|
|
|
+ return None
|
|
|
+
|
|
|
+ ti = CapsTrackInfoT2(2)
|
|
|
+ res = self.lib.CAPSLockTrack(ct.byref(ti), self.iid,
|
|
|
+ cyl, head, DI_LOCK.def_flags)
|
|
|
+ assert res == 0
|
|
|
+
|
|
|
+ if not ti.trackbuf:
|
|
|
+ return None # unformatted/empty
|
|
|
+ carray_type = ct.c_ubyte * ((ti.tracklen+7)//8)
|
|
|
+ carray = carray_type.from_address(
|
|
|
+ ct.addressof(ti.trackbuf.contents))
|
|
|
+ trackbuf = bitarray(endian='big')
|
|
|
+ trackbuf.frombytes(bytes(carray))
|
|
|
+ trackbuf = trackbuf[:ti.tracklen]
|
|
|
+
|
|
|
+ # Write splice is at trackbuf[ti.overlap]. Index is at trackbuf[0].
|
|
|
+ #for i in range(ti.sectorcnt):
|
|
|
+ # si = CapsSectorInfo()
|
|
|
+ # res = self.lib.CAPSGetInfo(ct.byref(si), self.iid,
|
|
|
+ # cyl, head, 1, i)
|
|
|
+ # assert res == 0
|
|
|
+ # # Data is at trackbuf[si.datastart:si.datastart + si.datasize]
|
|
|
+ #for i in range(ti.weakcnt):
|
|
|
+ # wi = CapsDataInfo()
|
|
|
+ # res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid,
|
|
|
+ # cyl, head, 2, i)
|
|
|
+ # assert res == 0
|
|
|
+ # # Weak data at trackbuf[wi.start:wi.start + wi.size]
|
|
|
+
|
|
|
+ assert ti.weakcnt == 0, "Can't yet handle weak data"
|
|
|
+
|
|
|
+ # We don't really have access to the bitrate. It depends on RPM.
|
|
|
+ # So we assume a rotation rate of 300 RPM (5 rev/sec).
|
|
|
+ bitrate = ti.tracklen * 5
|
|
|
+
|
|
|
+ timebuf = None
|
|
|
+ if ti.timebuf:
|
|
|
+ carray_type = ct.c_uint * ti.timelen
|
|
|
+ carray = carray_type.from_address(
|
|
|
+ ct.addressof(ti.timebuf.contents))
|
|
|
+ # Unpack the per-byte timing info into per-bitcell
|
|
|
+ timebuf = []
|
|
|
+ for i in carray:
|
|
|
+ for j in range(8):
|
|
|
+ timebuf.append(i)
|
|
|
+ # Pad the timing info with normal cell lengths as necessary
|
|
|
+ for j in range(len(carray)*8, ti.tracklen):
|
|
|
+ timebuf.append(1000)
|
|
|
+ # Clip the timing info, if necessary.
|
|
|
+ timebuf = timebuf[:ti.tracklen]
|
|
|
+
|
|
|
+ # TODO: Place overlap (write splice) at the correct position.
|
|
|
+ if ti.overlap != 0:
|
|
|
+ trackbuf = trackbuf[ti.overlap:] + trackbuf[:ti.overlap]
|
|
|
+ if timebuf:
|
|
|
+ timebuf = timebuf[ti.overlap:] + timebuf[:ti.overlap]
|
|
|
+
|
|
|
+ return Flux.from_bitarray(trackbuf, bitrate, timebuf)
|
|
|
+
|
|
|
+
|
|
|
+# Open and initialise the CAPS library.
|
|
|
+def open_libcaps():
|
|
|
+
|
|
|
+ # Get the OS-dependent list of valid CAPS library names.
|
|
|
+ _names = []
|
|
|
+ if platform.system() == "Linux":
|
|
|
+ _names = [ "libcapsimage.so.5", "libcapsimage.so.5.1",
|
|
|
+ "libcapsimage.so.4", "libcapsimage.so.4.2",
|
|
|
+ "libcapsimage.so" ]
|
|
|
+ elif platform.system() == "Darwin":
|
|
|
+ _names = [ "CAPSImage.framework/CAPSImage" ]
|
|
|
+ elif platform.system() == "Windows":
|
|
|
+ _names = [ "CAPSImg_x64.dll", "CAPSImg.dll" ]
|
|
|
+
|
|
|
+ # Get the absolute path to the root Greaseweazle folder.
|
|
|
+ path = os.path.dirname(os.path.abspath(__file__))
|
|
|
+ for _ in range(3):
|
|
|
+ path = os.path.join(path, os.pardir)
|
|
|
+ path = os.path.normpath(path)
|
|
|
+
|
|
|
+ # Create a search list of both relative and absolute library names.
|
|
|
+ names = []
|
|
|
+ for name in _names:
|
|
|
+ names.append(name)
|
|
|
+ names.append(os.path.join(path, name))
|
|
|
+
|
|
|
+ # Walk the search list, trying to open the CAPS library.
|
|
|
+ for name in names:
|
|
|
+ try:
|
|
|
+ lib = ct.cdll.LoadLibrary(name)
|
|
|
+ break
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ assert "lib" in locals(), "Could not find SPS/CAPS IPF decode library"
|
|
|
+ print(name)
|
|
|
+
|
|
|
+ # We have opened the library. Now initialise it.
|
|
|
+ res = lib.CAPSInit()
|
|
|
+ assert res == 0, "Failure initialising %s" % libname
|
|
|
+
|
|
|
+ return lib
|
|
|
+
|
|
|
+
|
|
|
+# Get a reference to the CAPS library. Open it if necessary.
|
|
|
+def get_libcaps():
|
|
|
+ global libcaps
|
|
|
+ if not 'libcaps' in globals():
|
|
|
+ libcaps = open_libcaps()
|
|
|
+ return libcaps
|
|
|
+
|
|
|
+
|
|
|
+# Local variables:
|
|
|
+# python-indent: 4
|
|
|
+# End:
|