Pārlūkot izejas kodu

Remove tools from the new firmware repository.

Keir Fraser 3 gadi atpakaļ
vecāks
revīzija
554e078967
53 mainītis faili ar 23 papildinājumiem un 6450 dzēšanām
  1. 4 39
      Makefile
  2. 10 6
      README.md
  3. 0 14
      gw
  4. 0 23
      scripts/49-greaseweazle.rules
  5. 0 256
      scripts/c_ext/optimised.c
  6. 0 6
      scripts/c_ext/setup.py
  7. 0 0
      scripts/greaseweazle/__init__.py
  8. 0 0
      scripts/greaseweazle/codec/__init__.py
  9. 0 0
      scripts/greaseweazle/codec/amiga/__init__.py
  10. 0 198
      scripts/greaseweazle/codec/amiga/amigados.py
  11. 0 240
      scripts/greaseweazle/codec/formats.py
  12. 0 0
      scripts/greaseweazle/codec/ibm/__init__.py
  13. 0 339
      scripts/greaseweazle/codec/ibm/fm.py
  14. 0 486
      scripts/greaseweazle/codec/ibm/mfm.py
  15. 0 19
      scripts/greaseweazle/error.py
  16. 0 147
      scripts/greaseweazle/flux.py
  17. 0 0
      scripts/greaseweazle/image/__init__.py
  18. 0 27
      scripts/greaseweazle/image/acorn.py
  19. 0 97
      scripts/greaseweazle/image/adf.py
  20. 0 16
      scripts/greaseweazle/image/d81.py
  21. 0 488
      scripts/greaseweazle/image/edsk.py
  22. 0 135
      scripts/greaseweazle/image/hfe.py
  23. 0 72
      scripts/greaseweazle/image/image.py
  24. 0 84
      scripts/greaseweazle/image/img.py
  25. 0 338
      scripts/greaseweazle/image/ipf.py
  26. 0 234
      scripts/greaseweazle/image/kryoflux.py
  27. 0 372
      scripts/greaseweazle/image/scp.py
  28. 0 25
      scripts/greaseweazle/optimised/__init__.py
  29. 0 0
      scripts/greaseweazle/tools/__init__.py
  30. 0 95
      scripts/greaseweazle/tools/bandwidth.py
  31. 0 64
      scripts/greaseweazle/tools/clean.py
  32. 0 66
      scripts/greaseweazle/tools/delays.py
  33. 0 61
      scripts/greaseweazle/tools/erase.py
  34. 0 98
      scripts/greaseweazle/tools/info.py
  35. 0 87
      scripts/greaseweazle/tools/pin.py
  36. 0 175
      scripts/greaseweazle/tools/read.py
  37. 0 37
      scripts/greaseweazle/tools/reset.py
  38. 0 62
      scripts/greaseweazle/tools/seek.py
  39. 0 134
      scripts/greaseweazle/tools/update.py
  40. 0 347
      scripts/greaseweazle/tools/util.py
  41. 0 212
      scripts/greaseweazle/tools/write.py
  42. 0 331
      scripts/greaseweazle/track.py
  43. 0 565
      scripts/greaseweazle/usb.py
  44. 0 109
      scripts/gw.py
  45. 0 42
      scripts/misc/artifact.py
  46. 0 14
      scripts/misc/hw_test.sh
  47. 0 50
      scripts/misc/ipf_align.py
  48. 0 96
      scripts/misc/scp_info.py
  49. 0 52
      scripts/misc/sw_test.sh
  50. 0 63
      scripts/misc/sysinfo.py
  51. 9 1
      scripts/mk_update.py
  52. 0 19
      scripts/setup.py
  53. 0 9
      scripts/setup.sh

+ 4 - 39
Makefile

@@ -2,7 +2,7 @@
 export FW_MAJOR := 0
 export FW_MINOR := 33
 
-TARGETS := all blinky clean dist windist mrproper f1_ocd ocd flash start serial pysetup
+TARGETS := all blinky clean dist mrproper f1_ocd ocd flash start serial
 .PHONY: $(TARGETS)
 
 ifneq ($(RULES_MK),y)
@@ -19,7 +19,7 @@ VER := v$(FW_MAJOR).$(FW_MINOR)
 
 SUBDIRS += src bootloader blinky_test
 
-all: scripts/greaseweazle/version.py
+all:
 	$(MAKE) -C src -f $(ROOT)/Rules.mk $(PROJ).elf $(PROJ).bin $(PROJ).hex
 	$(MAKE) bootloader=y -C bootloader -f $(ROOT)/Rules.mk \
 		Bootloader.elf Bootloader.bin Bootloader.hex
@@ -33,16 +33,11 @@ blinky:
 		Blinky.elf Blinky.bin Blinky.hex
 
 clean::
-	rm -rf scripts/greaseweazle/optimised/optimised* scripts/c_ext/build
-	rm -f *.hex *.upd scripts/greaseweazle/*.pyc
-	rm -f scripts/greaseweazle/version.py
+	rm -f *.hex *.upd
 	find . -name __pycache__ | xargs rm -rf
 
 dist:
 	rm -rf $(PROJ)-*
-	mkdir -p $(PROJ)-$(VER)/scripts/greaseweazle/image
-	mkdir -p $(PROJ)-$(VER)/scripts/greaseweazle/tools
-	mkdir -p $(PROJ)-$(VER)/scripts/misc
 	mkdir -p $(PROJ)-$(VER)/hex/alt
 	$(MAKE) clean
 	$(MAKE) mcu=stm32f1 all blinky
@@ -51,13 +46,6 @@ dist:
 	cp -a blinky_test/Blinky.hex $(PROJ)-$(VER)/hex/alt/Blinky_Test-F1-$(VER).hex
 	cp -a COPYING $(PROJ)-$(VER)/
 	cp -a README.md $(PROJ)-$(VER)/
-	cp -a gw $(PROJ)-$(VER)/
-	cp -a scripts/49-greaseweazle.rules $(PROJ)-$(VER)/scripts/
-	cp -a scripts/setup.sh $(PROJ)-$(VER)/scripts/
-	cp -a scripts/gw.py $(PROJ)-$(VER)/scripts/
-	cp -a scripts/greaseweazle $(PROJ)-$(VER)/scripts
-	cp -a scripts/c_ext $(PROJ)-$(VER)/scripts
-	cp -a scripts/misc/*.py $(PROJ)-$(VER)/scripts/misc/
 	cp -a RELEASE_NOTES $(PROJ)-$(VER)/
 	$(MAKE) clean
 	$(MAKE) mcu=stm32f7 all
@@ -72,31 +60,8 @@ dist:
 	$(MAKE) clean
 	$(ZIP) $(PROJ)-$(VER).zip $(PROJ)-$(VER)
 
-windist: pysetup
-	rm -rf $(PROJ)-$(VER) ipf ipf.zip
-	[ -e $(PROJ)-$(VER).zip ] || \
-	curl -L https://github.com/keirf/greaseweazle/releases/download/$(VER)/$(PROJ)-$(VER).zip --output $(PROJ)-$(VER).zip
-	$(UNZIP) $(PROJ)-$(VER).zip
-	cp -a scripts/setup.py $(PROJ)-$(VER)/scripts
-	cp -a scripts/greaseweazle/optimised/optimised* $(PROJ)-$(VER)/scripts/greaseweazle/optimised
-	cd $(PROJ)-$(VER)/scripts && $(PYTHON) setup.py build
-	cp -a $(PROJ)-$(VER)/scripts/build/exe.win*/* $(PROJ)-$(VER)/
-	rm -rf $(PROJ)-$(VER)/scripts $(PROJ)-$(VER)/*.py $(PROJ)-$(VER)/gw
-	curl -L http://softpres.org/_media/files:spsdeclib_5.1_windows.zip --output ipf.zip
-	$(UNZIP) -oipf ipf.zip
-	cp -a ipf/capsimg_binary/CAPSImg.dll $(PROJ)-$(VER)/
-	rm -rf ipf ipf.zip
-	$(ZIP) $(PROJ)-$(VER)-win.zip $(PROJ)-$(VER)
-
 mrproper: clean
-	rm -rf $(PROJ)-* ipf ipf.zip
-
-scripts/greaseweazle/version.py: Makefile
-	echo "major = $(FW_MAJOR)" >$@
-	echo "minor = $(FW_MINOR)" >>$@
-
-pysetup: scripts/greaseweazle/version.py
-	PYTHON=$(PYTHON) . ./scripts/setup.sh
+	rm -rf $(PROJ)-*
 
 BAUD=115200
 DEV=/dev/ttyUSB0

+ 10 - 6
README.md

@@ -1,23 +1,27 @@
-# Greaseweazle
+# Greaseweazle: Firmware
 
-*Tools and USB interface for accessing a floppy drive at the raw flux level.*
+*Device firmware for accessing a floppy drive at the raw flux level.*
 
 ![CI Badge][ci-badge]
 ![Downloads Badge][downloads-badge]
 ![Version Badge][version-badge]
 
+This repository contains the Greaseweazle device firmware and its binary
+releases. Find the tools repository [here][tools].
+
 ### [Purchase a ready-made Greaseweazle][rmb]
 ### [Download Greaseweazle][Downloads]
-### [Read the GitHub Wiki](https://github.com/keirf/greaseweazle/wiki)
+### [Read the Greaseweazle Wiki](https://github.com/keirf/greaseweazle/wiki)
 
 ### Redistribution
 
 Greaseweazle source code, and all binary releases, are freely redistributable
 in any form. Please see the [license](COPYING).
 
+[tools]: https://github.com/keirf/greaseweazle
 [rmb]: https://github.com/keirf/greaseweazle/wiki/Ready-Made-Boards
 [Downloads]: https://github.com/keirf/greaseweazle/wiki/Downloads
 
-[ci-badge]: https://github.com/keirf/greaseweazle/workflows/CI/badge.svg
-[downloads-badge]: https://img.shields.io/github/downloads/keirf/greaseweazle/total
-[version-badge]: https://img.shields.io/github/v/release/keirf/greaseweazle
+[ci-badge]: https://github.com/keirf/greaseweazle-firmware/workflows/CI/badge.svg
+[downloads-badge]: https://img.shields.io/github/downloads/keirf/greaseweazle-firmware/total
+[version-badge]: https://img.shields.io/github/v/release/keirf/greaseweazle-firmware

+ 0 - 14
gw

@@ -1,14 +0,0 @@
-#!/usr/bin/env python3
-
-import sys, os
-
-if sys.version_info < (3,0,0):
-    print('** FATAL ERROR: Greaseweazle requires Python 3')
-    sys.exit(1)
-
-# Update the search path and import the real script
-sys.path[0] = os.path.join(sys.path[0], "scripts")
-import gw
-
-# Execute the real script
-gw.main(sys.argv)

+ 0 - 23
scripts/49-greaseweazle.rules

@@ -1,23 +0,0 @@
-# UDEV Rules for Greaseweazle
-#
-# To install, type this command in a terminal:
-#   sudo cp 49-greaseweazle.rules /etc/udev/rules.d/.
-#
-# After this file is installed, physically unplug and reconnect Greaseweazle.
-#
-ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="Greaseweazle", \
-    ENV{ID_MM_DEVICE_IGNORE}="1"
-ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="Greaseweazle", \
-    ENV{MTP_NO_PROBE}="1"
-ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="Greaseweazle", \
-    SUBSYSTEMS=="usb", MODE:="0666"
-ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="Greaseweazle", \
-    KERNEL=="ttyACM*", MODE:="0666"
-ACTION=="add", \
-    ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="Greaseweazle", \
-    SYMLINK+="greaseweazle"
-#
-# If you share your linux system with other users, or just don't like the
-# idea of write permission for everybody, you can replace MODE:="0666" with
-# OWNER:="yourusername" to create the device owned by you, or with
-# GROUP:="somegroupname" and mange access using standard unix groups.

+ 0 - 256
scripts/c_ext/optimised.c

@@ -1,256 +0,0 @@
-
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-#include <stdio.h>
-#include <stdint.h>
-
-#define FLUXOP_INDEX   1
-#define FLUXOP_SPACE   2
-#define FLUXOP_ASTABLE 3
-
-/* bitarray.append(value) */
-static PyObject *append_s;
-static int bitarray_append(PyObject *bitarray, PyObject *value)
-{
-    PyObject *res = PyObject_CallMethodObjArgs(
-        bitarray, append_s, value, NULL);
-    if (res == NULL)
-        return 0;
-    Py_DECREF(res);
-    return 1;
-}
-
-/* Like PyList_Append() but steals a reference to @item. */
-static int PyList_Append_SR(PyObject *list, PyObject *item)
-{
-    int rc = PyList_Append(list, item);
-    Py_DECREF(item);
-    return rc;
-}
-
-static PyObject *
-flux_to_bitcells(PyObject *self, PyObject *args)
-{
-    /* Parameters */
-    PyObject *bit_array, *time_array, *revolutions;
-    PyObject *index_iter, *flux_iter;
-    double freq, clock_centre, clock_min, clock_max;
-    double pll_period_adj, pll_phase_adj;
-
-    /* Local variables */
-    PyObject *item;
-    double clock, new_ticks, ticks, to_index;
-    int zeros, nbits;
-
-    if (!PyArg_ParseTuple(args, "OOOOOdddddd",
-                          &bit_array, &time_array, &revolutions,
-                          &index_iter, &flux_iter,
-                          &freq, &clock_centre, &clock_min, &clock_max,
-                          &pll_period_adj, &pll_phase_adj))
-        return NULL;
-
-    nbits = 0;
-    ticks = 0.0;
-    clock = clock_centre;
-
-    /* to_index = next(index_iter) */
-    item = PyIter_Next(index_iter);
-    to_index = PyFloat_AsDouble(item);
-    Py_DECREF(item);
-    if (PyErr_Occurred())
-        return NULL;
-
-    /* for x in flux_iter: */
-    assert(PyIter_Check(flux_iter));
-    while ((item = PyIter_Next(flux_iter)) != NULL) {
-
-        double x = PyFloat_AsDouble(item);
-        Py_DECREF(item);
-        if (PyErr_Occurred())
-            return NULL;
-
-        /* Gather enough ticks to generate at least one bitcell. */
-        ticks += x / freq;
-        if (ticks < clock/2)
-            continue;
-
-        /* Clock out zero or more 0s, followed by a 1. */
-        for (zeros = 0; ; zeros++) {
-
-            /* Check if we cross the index mark. */
-            to_index -= clock;
-            if (to_index < 0) {
-                if (PyList_Append_SR(revolutions, PyLong_FromLong(nbits)) < 0)
-                    return NULL;
-                nbits = 0;
-                item = PyIter_Next(index_iter);
-                to_index += PyFloat_AsDouble(item);
-                Py_DECREF(item);
-                if (PyErr_Occurred())
-                    return NULL;
-            }
-
-            nbits += 1;
-            ticks -= clock;
-            if (PyList_Append_SR(time_array, PyFloat_FromDouble(clock)) < 0)
-                return NULL;
-            if (ticks < clock/2) {
-                if (!bitarray_append(bit_array, Py_True))
-                    return NULL;
-                break;
-            }
-
-            if (!bitarray_append(bit_array, Py_False))
-                return NULL;
-
-        }
-
-        /* PLL: Adjust clock frequency according to phase mismatch. */
-        if (zeros <= 3) {
-            /* In sync: adjust clock by a fraction of the phase mismatch. */
-            clock += ticks * pll_period_adj;
-        } else {
-            /* Out of sync: adjust clock towards centre. */
-            clock += (clock_centre - clock) * pll_period_adj;
-        }
-        /* Clamp the clock's adjustment range. */
-        if (clock < clock_min)
-            clock = clock_min;
-        else if (clock > clock_max)
-            clock = clock_max;
-        /* PLL: Adjust clock phase according to mismatch. */
-        new_ticks = ticks * (1.0 - pll_phase_adj);
-        if (PyList_SetItem(time_array, PyList_Size(time_array)-1,
-                           PyFloat_FromDouble(ticks - new_ticks)) < 0)
-            return NULL;
-        ticks = new_ticks;
-
-    }
-
-    Py_RETURN_NONE;
-}
-
-
-static int _read_28bit(uint8_t *p)
-{
-    int x;
-    x  = (p[0]       ) >>  1;
-    x |= (p[1] & 0xfe) <<  6;
-    x |= (p[2] & 0xfe) << 13;
-    x |= (p[3] & 0xfe) << 20;
-    return x;
-}
-
-static PyObject *
-decode_flux(PyObject *self, PyObject *args)
-{
-    /* Parameters */
-    Py_buffer bytearray;
-    PyObject *res = NULL;
-
-    /* bytearray buffer */
-    uint8_t *p;
-    Py_ssize_t l;
-
-    /* Local variables */
-    PyObject *flux, *index;
-    long val, ticks, ticks_since_index;
-    int i, opcode;
-
-    if (!PyArg_ParseTuple(args, "y*", &bytearray))
-        return NULL;
-    p = bytearray.buf;
-    l = bytearray.len;
-
-    /* assert dat[-1] == 0 */
-    if ((l == 0) || (p[l-1] != 0)) {
-        PyErr_SetString(PyExc_ValueError, "Flux is not NUL-terminated");
-        PyBuffer_Release(&bytearray);
-        return NULL;
-    }
-    /* len(dat) -= 1 */
-    l -= 1;
-
-    /* flux, index = [], [] */
-    flux = PyList_New(0);
-    index = PyList_New(0);
-    /* ticks, ticks_since_index = 0, 0 */
-    ticks = 0;
-    ticks_since_index = 0;
-
-    while (l != 0) {
-        i = *p++;
-        if (i == 255) {
-            if ((l -= 2) < 0)
-                goto oos;
-            opcode = *p++;
-            switch (opcode) {
-            case FLUXOP_INDEX:
-                if ((l -= 4) < 0)
-                    goto oos;
-                val = _read_28bit(p);
-                p += 4;
-                if (PyList_Append_SR(
-                        index, PyLong_FromLong(
-                            ticks_since_index + ticks + val)) < 0)
-                    goto out;
-                ticks_since_index = -(ticks + val);
-                break;
-            case FLUXOP_SPACE:
-                if ((l -= 4) < 0)
-                    goto oos;
-                ticks += _read_28bit(p);
-                p += 4;
-                break;
-            default:
-                PyErr_Format(PyExc_ValueError,
-                             "Bad opcode in flux stream (%d)", opcode);
-                goto out;
-            }
-        } else {
-            if (i < 250) {
-                l -= 1;
-                val = i;
-            } else {
-                if ((l -= 2) < 0)
-                    goto oos;
-                val = 250 + (i - 250) * 255;
-                val += *p++ - 1;
-            }
-            ticks += val;
-            if (PyList_Append_SR(flux, PyLong_FromLong(ticks)) < 0)
-                goto out;
-            ticks_since_index += ticks;
-            ticks = 0;
-        }
-    }
-
-    res = Py_BuildValue("OO", flux, index);
-
-out:
-    PyBuffer_Release(&bytearray);
-    Py_DECREF(flux);
-    Py_DECREF(index);
-    return res;
-
-oos:
-    PyErr_SetString(PyExc_ValueError, "Unexpected end of flux");
-    goto out;
-}
-
-
-static PyMethodDef modulefuncs[] = {
-    { "flux_to_bitcells", flux_to_bitcells, METH_VARARGS, NULL },
-    { "decode_flux", decode_flux, METH_VARARGS, NULL },
-    { NULL }
-};
-
-static PyModuleDef moduledef = {
-    PyModuleDef_HEAD_INIT, "optimised", 0, -1, modulefuncs,
-};
-
-PyMODINIT_FUNC PyInit_optimised(void)
-{
-    append_s = Py_BuildValue("s", "append");
-    return PyModule_Create(&moduledef);
-}

+ 0 - 6
scripts/c_ext/setup.py

@@ -1,6 +0,0 @@
-from distutils.core import setup, Extension
-
-module1 = Extension('optimised', sources = ['optimised.c'])
-
-setup(name = 'optimised',
-      ext_modules = [module1])

+ 0 - 0
scripts/greaseweazle/__init__.py


+ 0 - 0
scripts/greaseweazle/codec/__init__.py


+ 0 - 0
scripts/greaseweazle/codec/amiga/__init__.py


+ 0 - 198
scripts/greaseweazle/codec/amiga/amigados.py

@@ -1,198 +0,0 @@
-# greaseweazle/codec/amiga/amigados.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
-import itertools as it
-from bitarray import bitarray
-
-from greaseweazle.track import MasterTrack, RawTrack
-
-default_revs = 1.1
-
-sync_bytes = b'\x44\x89\x44\x89'
-sync = bitarray(endian='big')
-sync.frombytes(sync_bytes)
-
-bad_sector = b'-=[BAD SECTOR]=-' * 32
-
-class AmigaDOS:
-
-    time_per_rev = 0.2
-
-    def __init__(self, cyl, head):
-        self.tracknr = cyl*2 + head
-        self.sector = [None] * self.nsec
-        self.map = [None] * self.nsec
-
-    def summary_string(self):
-        nsec, nbad = self.nsec, self.nr_missing()
-        s = "AmigaDOS (%d/%d sectors)" % (nsec - nbad, nsec)
-        if nbad != 0:
-            s += " - %d sectors missing" % nbad
-        return s
-
-    # private
-    def exists(self, sec_id, togo):
-        return ((self.sector[sec_id] is not None)
-                or (self.map[self.nsec-togo] is not None))
-
-    # private
-    def add(self, sec_id, togo, label, data):
-        assert not self.exists(sec_id, togo)
-        self.sector[sec_id] = label, data
-        self.map[self.nsec-togo] = sec_id
-
-    def has_sec(self, sec_id):
-        return self.sector[sec_id] is not None
-
-    def nr_missing(self):
-        return len([sec for sec in self.sector if sec is None])
-
-    def get_adf_track(self):
-        tdat = bytearray()
-        for sec in self.sector:
-            tdat += sec[1] if sec is not None else bad_sector
-        return tdat
-
-    def set_adf_track(self, tdat):
-        totsize = self.nsec * 512
-        if len(tdat) < totsize:
-            tdat += bytes(totsize - len(tdat))
-        self.map = list(range(self.nsec))
-        for sec in self.map:
-            self.sector[sec] = bytes(16), tdat[sec*512:(sec+1)*512]
-        return totsize
-
-    def flux(self, *args, **kwargs):
-        return self.raw_track().flux(*args, **kwargs)
-
-
-    def decode_raw(self, track):
-        raw = RawTrack(clock = self.clock, data = track)
-        bits, _ = raw.get_all_data()
-
-        for offs in bits.itersearch(sync):
-
-            if self.nr_missing() == 0:
-                break
-
-            sec = bits[offs:offs+544*16].tobytes()
-            if len(sec) != 1088:
-                continue
-
-            header = decode(sec[4:12])
-            format, track, sec_id, togo = tuple(header)
-            if format != 0xff or track != self.tracknr \
-               or not(sec_id < self.nsec and 0 < togo <= self.nsec) \
-               or self.exists(sec_id, togo):
-                continue
-
-            label = decode(sec[12:44])
-            hsum, = struct.unpack('>I', decode(sec[44:52]))
-            if hsum != checksum(header + label):
-                continue
-
-            dsum, = struct.unpack('>I', decode(sec[52:60]))
-            data = decode(sec[60:1084])
-            gap = decode(sec[1084:1088])
-            if dsum != checksum(data):
-                continue;
-
-            self.add(sec_id, togo, label, data)
-
-
-    def raw_track(self):
-
-        # List of sector IDs missing from the sector map:
-        missing = iter([x for x in range(self.nsec) if not x in self.map])
-        # Sector map with the missing entries filled in:
-        full_map = [next(missing) if x is None else x for x in self.map]
-
-        # Post-index track gap.
-        t = encode(bytes(128 * (self.nsec//11)))
-
-        for nr, sec_id in zip(range(self.nsec), full_map):
-            sector = self.sector[sec_id]
-            label, data = (bytes(16), bad_sector) if sector is None else sector
-            header = bytes([0xff, self.tracknr, sec_id, self.nsec-nr])
-            t += sync_bytes
-            t += encode(header)
-            t += encode(label)
-            t += encode(struct.pack('>I', checksum(header + label)))
-            t += encode(struct.pack('>I', checksum(data)))
-            t += encode(data)
-            t += encode(bytes(2))
-
-        # Add the pre-index gap.
-        tlen = (int((self.time_per_rev / self.clock)) + 31) & ~31
-        t += bytes(tlen//8-len(t))
-
-        track = MasterTrack(
-            bits = mfm_encode(t),
-            time_per_rev = 0.2)
-        track.verify = self
-        track.verify_revs = default_revs
-        return track
-
-
-    def verify_track(self, flux):
-        cyl = self.tracknr // 2
-        head = self.tracknr & 1
-        readback_track = self.decode_track(cyl, head, flux)
-        return (readback_track.nr_missing() == 0
-                and self.sector == readback_track.sector)
-
-    @classmethod
-    def decode_track(cls, cyl, head, track):
-        ados = cls(cyl, head)
-        ados.decode_raw(track)
-        return ados
-
-
-class AmigaDOS_DD(AmigaDOS):
-    nsec = 11
-    clock = 14/7093790
-
-class AmigaDOS_HD(AmigaDOS):
-    nsec = 22
-    clock = AmigaDOS_DD.clock / 2
-
-
-def mfm_encode(dat):
-    y = 0
-    out = bytearray()
-    for x in dat:
-        y = (y<<8) | x
-        if (x & 0xaa) == 0:
-            y |= ~((y>>1)|(y<<1)) & 0xaaaa
-        y &= 255
-        out.append(y)
-    return bytes(out)
-    
-
-def encode(dat):
-    return bytes(it.chain(map(lambda x: (x >> 1) & 0x55, dat),
-                          map(lambda x: x & 0x55, dat)))
-
-
-def decode(dat):
-    length = len(dat)//2
-    return bytes(map(lambda x, y: (x << 1 & 0xaa) | (y & 0x55),
-                     it.islice(dat, 0, length),
-                     it.islice(dat, length, None)))
-
-
-def checksum(dat):
-    csum = 0
-    for i in range(0, len(dat), 4):
-        csum ^= struct.unpack('>I', dat[i:i+4])[0]
-    return (csum ^ (csum>>1)) & 0x55555555
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 240
scripts/greaseweazle/codec/formats.py

@@ -1,240 +0,0 @@
-# greaseweazle/codec/formats.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>.
-
-from collections import OrderedDict
-
-from greaseweazle.tools import util
-
-class Format:
-    adf_compatible = False
-    img_compatible = False
-    default_trackset = 'c=0-79:h=0-1'
-    max_trackset = 'c=0-81:h=0-1'
-    def __init__(self):
-        self.default_tracks = util.TrackSet(self.default_trackset)
-        self.max_tracks = util.TrackSet(self.max_trackset)
-        self.decode_track = self.fmt.decode_track
-
-class Format_Acorn_DFS_SS(Format):
-    img_compatible = True
-    default_trackset = 'c=0-39:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.fm as m
-        self.fmt = m.Acorn_DFS
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Acorn_DFS_DS(Format):
-    img_compatible = True
-    default_trackset = 'c=0-39:h=0-1'
-    max_trackset = 'c=0-81:h=0-1'
-    def __init__(self):
-        import greaseweazle.codec.ibm.fm as m
-        self.fmt = m.Acorn_DFS
-        self.default_revs = m.default_revs
-        super().__init__()
-
-class Format_Acorn_ADFS_160(Format):
-    img_compatible = True
-    default_trackset = 'c=0-39:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.Acorn_ADFS_640
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Acorn_ADFS_320(Format):
-    img_compatible = True
-    default_trackset = 'c=0-79:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.Acorn_ADFS_640
-        self.default_revs = m.default_revs
-        super().__init__()
-
-class Format_Acorn_ADFS_640(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.Acorn_ADFS_640
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Acorn_ADFS_800(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.Acorn_ADFS_800
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Acorn_ADFS_1600(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.Acorn_ADFS_1600
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Amiga_AmigaDOS_DD(Format):
-    adf_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.amiga.amigados as m
-        self.fmt = m.AmigaDOS_DD
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_Amiga_AmigaDOS_HD(Format):
-    adf_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.amiga.amigados as m
-        self.fmt = m.AmigaDOS_HD
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_IBM_180(Format):
-    img_compatible = True
-    default_trackset = 'c=0-39:h=0'
-    max_trackset = 'c=0-41:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_720
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_IBM_360(Format):
-    img_compatible = True
-    default_trackset = 'c=0-39:h=0-1'
-    max_trackset = 'c=0-41:h=0-1'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_720
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_IBM_720(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_720
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_IBM_800(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_800
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_IBM_1440(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_1440
-        self.default_revs = m.default_revs
-        super().__init__()
-
-class Format_IBM_1200(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.IBM_MFM_1200
-        self.default_revs = m.default_revs
-        super().__init__()
-
-class Format_AtariST_360(Format):
-    img_compatible = True
-    default_trackset = 'c=0-79:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_SS_9SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_AtariST_400(Format):
-    img_compatible = True
-    default_trackset = 'c=0-79:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_10SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_AtariST_440(Format):
-    img_compatible = True
-    default_trackset = 'c=0-79:h=0'
-    max_trackset = 'c=0-81:h=0'
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_11SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_AtariST_720(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_DS_9SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_AtariST_800(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_10SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-class Format_AtariST_880(Format):
-    img_compatible = True
-    def __init__(self):
-        import greaseweazle.codec.ibm.mfm as m
-        self.fmt = m.AtariST_11SPT
-        self.default_revs = m.default_revs
-        super().__init__()
-    
-    
-formats = OrderedDict({
-    'acorn.dfs.ss': Format_Acorn_DFS_SS,
-    'acorn.dfs.ds': Format_Acorn_DFS_DS,
-    'acorn.adfs.160': Format_Acorn_ADFS_160,
-    'acorn.adfs.320': Format_Acorn_ADFS_320,
-    'acorn.adfs.640': Format_Acorn_ADFS_640,
-    'acorn.adfs.800': Format_Acorn_ADFS_800,
-    'acorn.adfs.1600': Format_Acorn_ADFS_1600,
-    'amiga.amigados': Format_Amiga_AmigaDOS_DD,
-    'amiga.amigados_hd': Format_Amiga_AmigaDOS_HD,
-    'atarist.360': Format_AtariST_360,
-    'atarist.400': Format_AtariST_400,
-    'atarist.440': Format_AtariST_440,
-    'atarist.720': Format_AtariST_720,
-    'atarist.800': Format_AtariST_800,
-    'atarist.880': Format_AtariST_880,
-    'commodore.1581': Format_IBM_800,
-    'ibm.180': Format_IBM_180,
-    'ibm.360': Format_IBM_360,
-    'ibm.720': Format_IBM_720,
-    'ibm.1200': Format_IBM_1200,
-    'ibm.1440': Format_IBM_1440,
-})
-
-def print_formats(f = None):
-    s = ''
-    for k, v in formats.items():
-        if not f or f(k, v):
-            if s:
-                s += '\n'
-            s += '  ' + k
-    return s

+ 0 - 0
scripts/greaseweazle/codec/ibm/__init__.py


+ 0 - 339
scripts/greaseweazle/codec/ibm/fm.py

@@ -1,339 +0,0 @@
-# greaseweazle/codec/ibm/fm.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 binascii
-import copy, heapq, struct, functools
-import itertools as it
-from bitarray import bitarray
-import crcmod.predefined
-
-from greaseweazle.codec.ibm import mfm
-from greaseweazle.track import MasterTrack, RawTrack
-
-default_revs = 2
-
-def sync(dat, clk=0xc7):
-    x = 0
-    for i in range(8):
-        x <<= 1
-        x |= (clk >> (7-i)) & 1
-        x <<= 1
-        x |= (dat >> (7-i)) & 1
-    return bytes(struct.pack('>H', x))
-
-sync_prefix = bitarray(endian='big')
-sync_prefix.frombytes(b'\xaa\xaa' + sync(0xf8))
-sync_prefix = sync_prefix[:16+10]
-
-iam_sync_bytes = sync(0xfc, 0xd7)
-iam_sync = bitarray(endian='big')
-iam_sync.frombytes(b'\xaa\xaa' + iam_sync_bytes)
-
-crc16 = crcmod.predefined.Crc('crc-ccitt-false')
-
-sec_sz = mfm.sec_sz
-IDAM   = mfm.IDAM
-DAM    = mfm.DAM
-Sector = mfm.Sector
-IAM    = mfm.IAM
-    
-class IBM_FM:
-
-    IAM  = 0xfc
-    IDAM = 0xfe
-    DAM  = 0xfb
-    DDAM = 0xf8
-
-    gap_presync = 6
-
-    gapbyte = 0xff
-
-    def __init__(self, cyl, head):
-        self.cyl, self.head = cyl, head
-        self.sectors = []
-        self.iams = []
-
-    def summary_string(self):
-        nsec, nbad = len(self.sectors), self.nr_missing()
-        s = "IBM FM (%d/%d sectors)" % (nsec - nbad, nsec)
-        if nbad != 0:
-            s += " - %d sectors missing" % nbad
-        return s
-
-    def has_sec(self, sec_id):
-        return self.sectors[sec_id].crc == 0
-
-    def nr_missing(self):
-        return len(list(filter(lambda x: x.crc != 0, self.sectors)))
-
-    def flux(self, *args, **kwargs):
-        return self.raw_track().flux(*args, **kwargs)
-
-
-    def decode_raw(self, track):
-        track.cue_at_index()
-        raw = RawTrack(clock = self.clock, data = track)
-        bits, _ = raw.get_all_data()
-
-        areas = []
-        idam = None
-
-        ## 1. Calculate offsets within dump
-        
-        for offs in bits.itersearch(iam_sync):
-            offs += 16
-            areas.append(IAM(offs, offs+1*16))
-            self.has_iam = True
-
-        for offs in bits.itersearch(sync_prefix):
-            offs += 16
-            mark = decode(bits[offs:offs+1*16].tobytes())[0]
-            clock = decode(bits[offs-1:offs+1*16-1].tobytes())[0]
-            if clock != 0xc7:
-                continue
-            if mark == IBM_FM.IDAM:
-                s, e = offs, offs+7*16
-                b = decode(bits[s:e].tobytes())
-                c,h,r,n = struct.unpack(">x4B2x", b)
-                crc = crc16.new(b).crcValue
-                if idam is not None:
-                    areas.append(idam)
-                idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
-            elif mark == IBM_FM.DAM or mark == IBM_FM.DDAM:
-                if idam is None or idam.end - offs > 1000:
-                    areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
-                else:
-                    sz = 128 << idam.n
-                    s, e = offs, offs+(1+sz+2)*16
-                    b = decode(bits[s:e].tobytes())
-                    crc = crc16.new(b).crcValue
-                    dam = DAM(s, e, crc, mark=mark, data=b[1:-2])
-                    areas.append(Sector(idam, dam))
-                idam = None
-            else:
-                pass #print("Unknown mark %02x" % mark)
-
-        if idam is not None:
-            areas.append(idam)
-
-        # Convert to offsets within track
-        areas.sort(key=lambda x:x.start)
-        index = iter(raw.revolutions)
-        p, n = 0, next(index)
-        for a in areas:
-            if a.start >= n:
-                p = n
-                try:
-                    n = next(index)
-                except StopIteration:
-                    n = float('inf')
-            a.delta(p)
-        areas.sort(key=lambda x:x.start)
-
-        # Add to the deduped lists
-        for a in areas:
-            match = False
-            if isinstance(a, IAM):
-                list = self.iams
-            elif isinstance(a, Sector):
-                list = self.sectors
-            else:
-                continue
-            for s in list:
-                if abs(s.start - a.start) < 1000:
-                    match = True
-                    break
-            if match and isinstance(a, Sector) and s.crc != 0 and a.crc == 0:
-                self.sectors = [x for x in self.sectors if x != a]
-                match = False
-            if not match:
-                list.append(a)
-
-
-    def raw_track(self):
-
-        areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
-        t = bytes()
-
-        for a in areas:
-            start = a.start//16 - self.gap_presync
-            gap = max(start - len(t)//2, 0)
-            t += encode(bytes([self.gapbyte] * gap))
-            t += encode(bytes(self.gap_presync))
-            if isinstance(a, IAM):
-                t += iam_sync_bytes
-            elif isinstance(a, Sector):
-                idam = bytes([self.IDAM,
-                              a.idam.c, a.idam.h, a.idam.r, a.idam.n])
-                idam += struct.pack('>H', crc16.new(idam).crcValue)
-                t += sync(idam[0]) + encode(idam[1:])
-                start = a.dam.start//16 - self.gap_presync
-                gap = max(start - len(t)//2, 0)
-                t += encode(bytes([self.gapbyte] * gap))
-                t += encode(bytes(self.gap_presync))
-                dam = bytes([a.dam.mark]) + a.dam.data
-                dam += struct.pack('>H', crc16.new(dam).crcValue)
-                t += sync(dam[0]) + encode(dam[1:])
-
-        # Add the pre-index gap.
-        tlen = int((self.time_per_rev / self.clock) // 16)
-        gap = max(tlen - len(t)//2, 0)
-        t += encode(bytes([self.gapbyte] * gap))
-
-        track = MasterTrack(
-            bits = t,
-            time_per_rev = self.time_per_rev)
-        track.verify = self
-        track.verify_revs = default_revs
-        return track
-
-
-class IBM_FM_Formatted(IBM_FM):
-
-    gap_4a = 40 # Post-Index
-    gap_1  = 26 # Post-IAM
-    gap_2  = 11 # Post-IDAM
-
-    def __init__(self, cyl, head):
-
-        super().__init__(cyl, head)
-        self.raw_iams, self.raw_sectors = [], []
-
-    def decode_raw(self, track):
-        iams, sectors = self.iams, self.sectors
-        self.iams, self.sectors = self.raw_iams, self.raw_sectors
-        super().decode_raw(track)
-        self.iams, self.sectors = iams, sectors
-        for r in self.raw_sectors:
-            if r.idam.crc != 0:
-                continue
-            for s in self.sectors:
-                if (s.idam.c == r.idam.c and
-                    s.idam.h == r.idam.h and
-                    s.idam.r == r.idam.r and
-                    s.idam.n == r.idam.n):
-                    s.idam.crc = 0
-                    if r.dam.crc == 0 and s.dam.crc != 0:
-                        s.dam.crc = s.crc = 0
-                        s.dam.data = r.dam.data
-
-    def set_img_track(self, tdat):
-        pos = 0
-        self.sectors.sort(key = lambda x: x.idam.r)
-        totsize = functools.reduce(lambda x, y: x + (128<<y.idam.n),
-                                   self.sectors, 0)
-        if len(tdat) < totsize:
-            tdat += bytes(totsize - len(tdat))
-        for s in self.sectors:
-            s.crc = s.idam.crc = s.dam.crc = 0
-            size = 128 << s.idam.n
-            s.dam.data = tdat[pos:pos+size]
-            pos += size
-        self.sectors.sort(key = lambda x: x.start)
-        return totsize
-
-    def get_img_track(self):
-        tdat = bytearray()
-        sectors = self.sectors.copy()
-        sectors.sort(key = lambda x: x.idam.r)
-        for s in sectors:
-            tdat += s.dam.data
-        return tdat
-        
-    def verify_track(self, flux):
-        readback_track = IBM_FM_Formatted(self.cyl, self.head)
-        readback_track.clock = self.clock
-        readback_track.time_per_rev = self.time_per_rev
-        for x in self.iams:
-            readback_track.iams.append(copy.copy(x))
-        for x in self.sectors:
-            idam, dam = copy.copy(x.idam), copy.copy(x.dam)
-            idam.crc, dam.crc = 0xffff, 0xffff
-            readback_track.sectors.append(Sector(idam, dam))
-        readback_track.decode_raw(flux)
-        if readback_track.nr_missing() != 0:
-            return False
-        return self.sectors == readback_track.sectors
-
-
-class IBM_FM_Predefined(IBM_FM_Formatted):
-
-    cskew = 0
-    hskew = 0
-    interleave = 1
-    
-    def __init__(self, cyl, head):
-
-        super().__init__(cyl, head)
-
-        # Create logical sector map in rotational order
-        sec_map = [-1] * self.nsec
-        pos = (cyl*self.cskew + head*self.hskew) % self.nsec
-        for i in range(self.nsec):
-            while sec_map[pos] != -1:
-                pos = (pos + 1) % self.nsec
-            sec_map[pos] = i
-            pos = (pos + self.interleave) % self.nsec
-
-        pos = self.gap_4a
-        if self.gap_1 is not None:
-            self.iams = [IAM(pos*16,(pos+1)*16)]
-            pos += 1 + self.gap_1
-
-        for i in range(self.nsec):
-            pos += self.gap_presync
-            idam = IDAM(pos*16, (pos+7)*16, 0xffff,
-                        c=cyl, h=head, r=self.id0+sec_map[i], n = self.sz)
-            pos += 7 + self.gap_2 + self.gap_presync
-            size = 128 << self.sz
-            dam = DAM(pos*16, (pos+1+size+2)*16, 0xffff,
-                      mark=self.DAM, data=bytes(size))
-            self.sectors.append(Sector(idam, dam))
-            pos += 1 + size + 2 + self.gap_3
-
-    @classmethod
-    def decode_track(cls, cyl, head, track):
-        mfm = cls(cyl, head)
-        mfm.decode_raw(track)
-        return mfm
-
-
-class Acorn_DFS(IBM_FM_Predefined):
-
-    time_per_rev = 0.2
-    clock = 4e-6
-
-    gap_1  = 0 # No IAM
-    gap_3  = 21
-    nsec   = 10
-    id0    = 0
-    sz     = 1
-    cskew  = 3
-
-
-encode_list = []
-for x in range(256):
-    y = 0
-    for i in range(8):
-        y <<= 1
-        y |= 1
-        y <<= 1
-        y |= (x >> (7-i)) & 1
-    encode_list.append(y)
-
-def encode(dat):
-    out = bytearray()
-    for x in dat:
-        out += struct.pack('>H', encode_list[x])
-    return bytes(out)
-
-decode = mfm.decode
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 486
scripts/greaseweazle/codec/ibm/mfm.py

@@ -1,486 +0,0 @@
-# greaseweazle/codec/ibm/mfm.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 copy, heapq, struct, functools
-import itertools as it
-from bitarray import bitarray
-import crcmod.predefined
-
-from greaseweazle.track import MasterTrack, RawTrack
-
-default_revs = 2
-
-iam_sync_bytes = b'\x52\x24' * 3
-iam_sync = bitarray(endian='big')
-iam_sync.frombytes(iam_sync_bytes)
-
-sync_bytes = b'\x44\x89' * 3
-sync = bitarray(endian='big')
-sync.frombytes(sync_bytes)
-
-crc16 = crcmod.predefined.Crc('crc-ccitt-false')
-
-def sec_sz(n):
-    return 128 << n if n <= 7 else 128 << 8
-
-class TrackArea:
-    def __init__(self, start, end, crc=None):
-        self.start = start
-        self.end = end
-        self.crc = crc
-    def delta(self, delta):
-        self.start -= delta
-        self.end -= delta
-    def __eq__(self, x):
-        return (isinstance(x, type(self))
-                and self.start == x.start
-                and self.end == x.end
-                and self.crc == x.crc)
-
-class IDAM(TrackArea):
-    def __init__(self, start, end, crc, c, h, r, n):
-        super().__init__(start, end, crc)
-        self.c = c
-        self.h = h
-        self.r = r
-        self.n = n
-    def __str__(self):
-        return ("IDAM:%6d-%6d c=%02x h=%02x r=%02x n=%02x CRC:%04x"
-                % (self.start, self.end, self.c, self.h, self.r, self.n,
-                   self.crc))
-    def __eq__(self, x):
-        return (super().__eq__(x)
-                and self.c == x.c and self.h == x.h
-                and self.r == x.r and self.n == x.n)
-    def __copy__(self):
-        return IDAM(self.start, self.end, self.crc,
-                    self.c, self.h, self.r, self.n)
-
-class DAM(TrackArea):
-    def __init__(self, start, end, crc, mark, data=None):
-        super().__init__(start, end, crc)
-        self.mark = mark
-        self.data = data
-    def __str__(self):
-        return "DAM: %6d-%6d mark=%02x" % (self.start, self.end, self.mark)
-    def __eq__(self, x):
-        return (super().__eq__(x)
-                and self.mark == x.mark
-                and self.data == x.data)
-    def __copy__(self):
-        return DAM(self.start, self.end, self.crc, self.mark, self.data)
-
-class Sector(TrackArea):
-    def __init__(self, idam, dam):
-        super().__init__(idam.start, dam.end, idam.crc | dam.crc)
-        self.idam = idam
-        self.dam = dam
-    def __str__(self):
-        s = "Sec: %6d-%6d CRC:%04x\n" % (self.start, self.end, self.crc)
-        s += " " + str(self.idam) + "\n"
-        s += " " + str(self.dam)
-        return s
-    def delta(self, delta):
-        super().delta(delta)
-        self.idam.delta(delta)
-        self.dam.delta(delta)
-    def __eq__(self, x):
-        return (super().__eq__(x)
-                and self.idam == x.idam
-                and self.dam == x.dam)
-    
-class IAM(TrackArea):
-    def __str__(self):
-        return "IAM: %6d-%6d" % (self.start, self.end)
-    def __copy__(self):
-        return IAM(self.start, self.end)
-    
-class IBM_MFM:
-
-    IAM  = 0xfc
-    IDAM = 0xfe
-    DAM  = 0xfb
-    DDAM = 0xf8
-
-    gap_presync = 12
-
-    gapbyte = 0x4e
-
-    def __init__(self, cyl, head):
-        self.cyl, self.head = cyl, head
-        self.sectors = []
-        self.iams = []
-
-    def summary_string(self):
-        nsec, nbad = len(self.sectors), self.nr_missing()
-        s = "IBM MFM (%d/%d sectors)" % (nsec - nbad, nsec)
-        if nbad != 0:
-            s += " - %d sectors missing" % nbad
-        return s
-
-    def has_sec(self, sec_id):
-        return self.sectors[sec_id].crc == 0
-
-    def nr_missing(self):
-        return len(list(filter(lambda x: x.crc != 0, self.sectors)))
-
-    def flux(self, *args, **kwargs):
-        return self.raw_track().flux(*args, **kwargs)
-
-    def decode_raw(self, track):
-        track.cue_at_index()
-        raw = RawTrack(clock = self.clock, data = track)
-        bits, _ = raw.get_all_data()
-
-        areas = []
-        idam = None
-
-        ## 1. Calculate offsets within dump
-        
-        for offs in bits.itersearch(iam_sync):
-            mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
-            if mark == IBM_MFM.IAM:
-                areas.append(IAM(offs, offs+4*16))
-                self.has_iam = True
-
-        for offs in bits.itersearch(sync):
-
-            mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
-            if mark == IBM_MFM.IDAM:
-                s, e = offs, offs+10*16
-                b = decode(bits[s:e].tobytes())
-                c,h,r,n = struct.unpack(">4x4B2x", b)
-                crc = crc16.new(b).crcValue
-                if idam is not None:
-                    areas.append(idam)
-                idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
-            elif mark == IBM_MFM.DAM or mark == IBM_MFM.DDAM:
-                if idam is None or idam.end - offs > 1000:
-                    areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
-                else:
-                    sz = 128 << idam.n
-                    s, e = offs, offs+(4+sz+2)*16
-                    b = decode(bits[s:e].tobytes())
-                    crc = crc16.new(b).crcValue
-                    dam = DAM(s, e, crc, mark=mark, data=b[4:-2])
-                    areas.append(Sector(idam, dam))
-                idam = None
-            else:
-                pass #print("Unknown mark %02x" % mark)
-
-        if idam is not None:
-            areas.append(idam)
-
-        # Convert to offsets within track
-        areas.sort(key=lambda x:x.start)
-        index = iter(raw.revolutions)
-        p, n = 0, next(index)
-        for a in areas:
-            if a.start >= n:
-                p = n
-                try:
-                    n = next(index)
-                except StopIteration:
-                    n = float('inf')
-            a.delta(p)
-        areas.sort(key=lambda x:x.start)
-
-        # Add to the deduped lists
-        for a in areas:
-            match = False
-            if isinstance(a, IAM):
-                list = self.iams
-            elif isinstance(a, Sector):
-                list = self.sectors
-            else:
-                continue
-            for s in list:
-                if abs(s.start - a.start) < 1000:
-                    match = True
-                    break
-            if match and isinstance(a, Sector) and s.crc != 0 and a.crc == 0:
-                self.sectors = [x for x in self.sectors if x != a]
-                match = False
-            if not match:
-                list.append(a)
-
-
-    def raw_track(self):
-
-        areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
-        t = bytes()
-
-        for a in areas:
-            start = a.start//16 - self.gap_presync
-            gap = max(start - len(t)//2, 0)
-            t += encode(bytes([self.gapbyte] * gap))
-            t += encode(bytes(self.gap_presync))
-            if isinstance(a, IAM):
-                t += iam_sync_bytes
-                t += encode(bytes([self.IAM]))
-            elif isinstance(a, Sector):
-                t += sync_bytes
-                idam = bytes([0xa1, 0xa1, 0xa1, self.IDAM,
-                              a.idam.c, a.idam.h, a.idam.r, a.idam.n])
-                idam += struct.pack('>H', crc16.new(idam).crcValue)
-                t += encode(idam[3:])
-                start = a.dam.start//16 - self.gap_presync
-                gap = max(start - len(t)//2, 0)
-                t += encode(bytes([self.gapbyte] * gap))
-                t += encode(bytes(self.gap_presync))
-                t += sync_bytes
-                dam = bytes([0xa1, 0xa1, 0xa1, a.dam.mark]) + a.dam.data
-                dam += struct.pack('>H', crc16.new(dam).crcValue)
-                t += encode(dam[3:])
-
-        # Add the pre-index gap.
-        tlen = int((self.time_per_rev / self.clock) // 16)
-        gap = max(tlen - len(t)//2, 0)
-        t += encode(bytes([self.gapbyte] * gap))
-
-        track = MasterTrack(
-            bits = mfm_encode(t),
-            time_per_rev = self.time_per_rev)
-        track.verify = self
-        track.verify_revs = default_revs
-        return track
-
-
-class IBM_MFM_Formatted(IBM_MFM):
-
-    gap_4a = 80 # Post-Index
-    gap_1  = 50 # Post-IAM
-    gap_2  = 22 # Post-IDAM
-
-    def __init__(self, cyl, head):
-
-        super().__init__(cyl, head)
-        self.raw_iams, self.raw_sectors = [], []
-
-    def decode_raw(self, track):
-        iams, sectors = self.iams, self.sectors
-        self.iams, self.sectors = self.raw_iams, self.raw_sectors
-        super().decode_raw(track)
-        self.iams, self.sectors = iams, sectors
-        for r in self.raw_sectors:
-            if r.idam.crc != 0:
-                continue
-            for s in self.sectors:
-                if (s.idam.c == r.idam.c and
-                    s.idam.h == r.idam.h and
-                    s.idam.r == r.idam.r and
-                    s.idam.n == r.idam.n):
-                    s.idam.crc = 0
-                    if r.dam.crc == 0 and s.dam.crc != 0:
-                        s.dam.crc = s.crc = 0
-                        s.dam.data = r.dam.data
-
-    def set_img_track(self, tdat):
-        pos = 0
-        self.sectors.sort(key = lambda x: x.idam.r)
-        totsize = functools.reduce(lambda x, y: x + (128<<y.idam.n),
-                                   self.sectors, 0)
-        if len(tdat) < totsize:
-            tdat += bytes(totsize - len(tdat))
-        for s in self.sectors:
-            s.crc = s.idam.crc = s.dam.crc = 0
-            size = 128 << s.idam.n
-            s.dam.data = tdat[pos:pos+size]
-            pos += size
-        self.sectors.sort(key = lambda x: x.start)
-        return totsize
-
-    def get_img_track(self):
-        tdat = bytearray()
-        sectors = self.sectors.copy()
-        sectors.sort(key = lambda x: x.idam.r)
-        for s in sectors:
-            tdat += s.dam.data
-        return tdat
-        
-    def verify_track(self, flux):
-        readback_track = IBM_MFM_Formatted(self.cyl, self.head)
-        readback_track.clock = self.clock
-        readback_track.time_per_rev = self.time_per_rev
-        for x in self.iams:
-            readback_track.iams.append(copy.copy(x))
-        for x in self.sectors:
-            idam, dam = copy.copy(x.idam), copy.copy(x.dam)
-            idam.crc, dam.crc = 0xffff, 0xffff
-            readback_track.sectors.append(Sector(idam, dam))
-        readback_track.decode_raw(flux)
-        if readback_track.nr_missing() != 0:
-            return False
-        return self.sectors == readback_track.sectors
-
-
-class IBM_MFM_Predefined(IBM_MFM_Formatted):
-
-    cskew = 0
-    hskew = 0
-    interleave = 1
-    
-    def __init__(self, cyl, head):
-
-        super().__init__(cyl, head)
-
-        # Create logical sector map in rotational order
-        sec_map = [-1] * self.nsec
-        pos = (cyl*self.cskew + head*self.hskew) % self.nsec
-        for i in range(self.nsec):
-            while sec_map[pos] != -1:
-                pos = (pos + 1) % self.nsec
-            sec_map[pos] = i
-            pos = (pos + self.interleave) % self.nsec
-
-        pos = self.gap_4a
-        if self.gap_1 is not None:
-            self.iams = [IAM(pos*16,(pos+4)*16)]
-            pos += 4 + self.gap_1
-
-        for i in range(self.nsec):
-            pos += self.gap_presync
-            idam = IDAM(pos*16, (pos+10)*16, 0xffff,
-                        c=cyl, h=head, r=self.id0+sec_map[i], n = self.sz)
-            pos += 10 + self.gap_2 + self.gap_presync
-            size = 128 << self.sz
-            dam = DAM(pos*16, (pos+4+size+2)*16, 0xffff,
-                      mark=self.DAM, data=bytes(size))
-            self.sectors.append(Sector(idam, dam))
-            pos += 4 + size + 2 + self.gap_3
-
-    @classmethod
-    def decode_track(cls, cyl, head, track):
-        mfm = cls(cyl, head)
-        mfm.decode_raw(track)
-        return mfm
-
-
-class IBM_MFM_720(IBM_MFM_Predefined):
-
-    time_per_rev = 0.2
-    clock = 2e-6
-    
-    gap_3  = 84 # Post-DAM
-    nsec   = 9
-    id0    = 1
-    sz     = 2
-
-class IBM_MFM_800(IBM_MFM_720):
-
-    gap_3 = 30
-    nsec  = 10
-
-class IBM_MFM_1200(IBM_MFM_720):
-
-    time_per_rev = 60/360
-    clock = 1e-6
-    nsec   = 15
-
-class IBM_MFM_1440(IBM_MFM_720):
-
-    clock = 1e-6
-    nsec   = 18
-
-class AtariST_SS_9SPT(IBM_MFM_720):
-
-    gap_1 = None
-    cskew = 2
-
-class AtariST_DS_9SPT(IBM_MFM_720):
-
-    gap_1 = None
-    cskew = 4
-    hskew = 2
-
-class AtariST_10SPT(IBM_MFM_720):
-
-    gap_1 = None
-    gap_3 = 30
-    nsec  = 10
-
-class AtariST_11SPT(IBM_MFM_720):
-
-    clock = 2e-6 * 0.96 # long track
-    gap_1 = None
-    gap_3 = 3
-    nsec  = 11
-
-class Acorn_ADFS_640(IBM_MFM_Predefined):
-
-    time_per_rev = 0.2
-    clock = 2e-6
-
-    gap_3 = 57
-    nsec  = 16
-    id0   = 0
-    sz    = 1
-
-class Acorn_ADFS_800(IBM_MFM_Predefined):
-
-    time_per_rev = 0.2
-    clock = 2e-6
-
-    gap_3 = 116
-    nsec  = 5
-    id0   = 0
-    sz    = 3
-
-class Acorn_ADFS_1600(IBM_MFM_Predefined):
-
-    time_per_rev = 0.2
-    clock = 1e-6
-
-    gap_3 = 116
-    nsec  = 10
-    id0   = 0
-    sz    = 3
-
-
-def mfm_encode(dat):
-    y = 0
-    out = bytearray()
-    for x in dat:
-        y = (y<<8) | x
-        if (x & 0xaa) == 0:
-            y |= ~((y>>1)|(y<<1)) & 0xaaaa
-        y &= 255
-        out.append(y)
-    return bytes(out)
-
-encode_list = []
-for x in range(256):
-    y = 0
-    for i in range(8):
-        y <<= 2
-        y |= (x >> (7-i)) & 1
-    encode_list.append(y)
-
-def encode(dat):
-    out = bytearray()
-    for x in dat:
-        out += struct.pack('>H', encode_list[x])
-    return bytes(out)
-    
-decode_list = bytearray()
-for x in range(0x5555+1):
-    y = 0
-    for i in range(16):
-        if x&(1<<(i*2)):
-            y |= 1<<i
-    decode_list.append(y)
-
-def decode(dat):
-    out = bytearray()
-    for x,y in zip(dat[::2], dat[1::2]):
-        out.append(decode_list[((x<<8)|y)&0x5555])
-    return bytes(out)
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 19
scripts/greaseweazle/error.py

@@ -1,19 +0,0 @@
-# greaseweazle/error.py
-#
-# Error management and reporting.
-#
-# 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>.
-
-class Fatal(Exception):
-    pass
-
-def check(pred, desc):
-    if not pred:
-        raise Fatal(desc)
-    
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 147
scripts/greaseweazle/flux.py

@@ -1,147 +0,0 @@
-# greaseweazle/flux.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>.
-
-from greaseweazle import error
-
-
-class Flux:
-
-    def __init__(self, index_list, flux_list, sample_freq, index_cued=True):
-        self.index_list = index_list
-        self.list = flux_list
-        self.sample_freq = sample_freq
-        self.splice = 0
-        self.index_cued = index_cued
-
-
-    def __str__(self):
-        s = "\nFlux: %.2f MHz" % (self.sample_freq*1e-6)
-        s += ("\n Total: %u samples, %.2fms\n"
-              % (len(self.list), sum(self.list)*1000/self.sample_freq))
-        rev = 0
-        for t in self.index_list:
-            s += " Revolution %u: %.2fms\n" % (rev, t*1000/self.sample_freq)
-            rev += 1
-        return s[:-1]
-
-
-    def summary_string(self):
-        return ("Raw Flux (%u flux in %.2fms)"
-                % (len(self.list), sum(self.list)*1000/self.sample_freq))
-
-
-    def cue_at_index(self):
-
-        if self.index_cued:
-            return
-
-        # Clip the initial partial revolution.
-        to_index = self.index_list[0]
-        for i in range(len(self.list)):
-            to_index -= self.list[i]
-            if to_index < 0:
-                break
-        if to_index < 0:
-            self.list = [-to_index] + self.list[i+1:]
-        else: # we ran out of flux
-            self.list = []
-        self.index_list = self.index_list[1:]
-        self.index_cued = True
-
-
-    def flux_for_writeout(self):
-
-        error.check(self.index_cued,
-                    "Cannot write non-index-cued raw flux")
-        error.check(self.splice == 0 or len(self.index_list) > 1,
-                    "Cannot write single-revolution unaligned raw flux")
-        splice_at_index = (self.splice == 0)
-
-        # Copy the required amount of flux to a fresh list.
-        flux_list = []
-        to_index = self.index_list[0]
-        remain = to_index + self.splice
-        for f in self.list:
-            if f > remain:
-                break
-            flux_list.append(f)
-            remain -= f
-
-        if splice_at_index:
-            # Extend with "safe" 4us sample values, to avoid unformatted area
-            # at end of track if drive motor is a little slow.
-            four_us = max(self.sample_freq * 4e-6, 1)
-            if remain > four_us:
-                flux_list.append(remain)
-            for i in range(round(to_index/(10*four_us))):
-                flux_list.append(four_us)
-        elif remain > 0:
-            # End the write exactly where specified.
-            flux_list.append(remain)
-
-        return WriteoutFlux(to_index, flux_list, self.sample_freq,
-                            index_cued = True,
-                            terminate_at_index = (self.splice == 0))
-
-
-
-    def flux(self):
-        return self
-
-
-    def scale(self, factor):
-        """Scale up all flux and index timings by specified factor."""
-        self.sample_freq /= factor
-
-
-    @property
-    def ticks_per_rev(self):
-        """Mean time between index pulses, in sample ticks"""
-        index_list = self.index_list
-        if not self.index_cued:
-            index_list = index_list[1:]
-        return sum(index_list) / len(index_list)
-
-
-    @property
-    def time_per_rev(self):
-        """Mean time between index pulses, in seconds (float)"""
-        return self.ticks_per_rev / self.sample_freq
-
-
-class WriteoutFlux(Flux):
-
-    def __init__(self, ticks_to_index, flux_list, sample_freq,
-                 index_cued, terminate_at_index):
-        super().__init__([ticks_to_index], flux_list, sample_freq)
-        self.index_cued = index_cued
-        self.terminate_at_index = terminate_at_index
-
-
-    def __str__(self):
-        s = ("\nWriteoutFlux: %.2f MHz, %.2fms to index, %s\n"
-             " Total: %u samples, %.2fms"
-             % (self.sample_freq*1e-6,
-                self.index_list[0]*1000/self.sample_freq,
-                ("Write all", "Terminate at index")[self.terminate_at_index],
-                len(self.list), sum(self.list)*1000/self.sample_freq))
-        return s
-
-
-    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:

+ 0 - 0
scripts/greaseweazle/image/__init__.py


+ 0 - 27
scripts/greaseweazle/image/acorn.py

@@ -1,27 +0,0 @@
-# greaseweazle/image/acorn.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>.
-
-from greaseweazle.image.img import IMG
-
-class SSD(IMG):
-    default_format = 'acorn.dfs.ss'
-
-class DSD(IMG):
-    default_format = 'acorn.dfs.ds'
-
-class ADS(IMG):
-    default_format = 'acorn.adfs.160'
-    
-class ADM(IMG):
-    default_format = 'acorn.adfs.320'
-    
-class ADL(IMG):
-    default_format = 'acorn.adfs.640'
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 97
scripts/greaseweazle/image/adf.py

@@ -1,97 +0,0 @@
-# greaseweazle/image/adf.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>.
-
-from greaseweazle import error
-import greaseweazle.codec.amiga.amigados as amigados
-from .image import Image
-
-from greaseweazle.codec import formats
-
-class ADF(Image):
-
-    default_format = 'amiga.amigados'
-
-    def __init__(self, name, fmt):
-        self.to_track = dict()
-        error.check(fmt is not None and fmt.adf_compatible, """\
-ADF image requires compatible format specifier
-Compatible formats:\n%s"""
-                    % formats.print_formats(lambda k, v: v.adf_compatible))
-        self.filename = name
-        self.fmt = fmt
-
-
-    @classmethod
-    def from_file(cls, name, fmt):
-
-        with open(name, "rb") as f:
-            dat = f.read()
-
-        adf = cls(name, fmt)
-
-        while True:
-            nsec = fmt.fmt.nsec
-            error.check((len(dat) % (2*nsec*512)) == 0, "Bad ADF image length")
-            ncyl = len(dat) // (2*nsec*512)
-            if ncyl < 90:
-                break
-            error.check(nsec == 11, "Bad ADF image length")
-            fmt = adf.fmt = formats.Format_Amiga_AmigaDOS_HD()
-
-        pos = 0
-        for t in fmt.max_tracks:
-            tnr = t.cyl*2 + t.head
-            ados = fmt.fmt(t.cyl, t.head)
-            pos += ados.set_adf_track(dat[pos:])
-            adf.to_track[tnr] = ados
-
-        return adf
-
-
-    @classmethod
-    def to_file(cls, name, fmt=None):
-        return cls(name, fmt)
-
-
-    def get_track(self, cyl, side):
-        tnr = cyl * 2 + side
-        if not tnr in self.to_track:
-            return None
-        return self.to_track[tnr].raw_track()
-
-
-    def emit_track(self, cyl, side, track):
-        tnr = cyl * 2 + side
-        self.to_track[tnr] = track
-
-
-    def get_image(self):
-
-        tdat = bytearray()
-
-        ntracks = max(self.to_track, default=0) + 1
-
-        for tnr in range(ntracks):
-            t = self.to_track[tnr] if tnr in self.to_track else None
-            if t is not None and hasattr(t, 'get_adf_track'):
-                tdat += t.get_adf_track()
-            elif tnr < 160:
-                # Pad empty/damaged tracks.
-                tdat += amigados.bad_sector * self.fmt.fmt.nsec
-            else:
-                # Do not extend past 160 tracks unless there is data.
-                break
-
-        if ntracks < 160:
-            tdat += amigados.bad_sector * self.fmt.fmt.nsec * (160 - ntracks)
-
-        return tdat
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 16
scripts/greaseweazle/image/d81.py

@@ -1,16 +0,0 @@
-# greaseweazle/image/d81.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>.
-
-from greaseweazle.image.img import IMG
-
-class D81(IMG):
-    default_format = 'commodore.1581'
-    sides_swapped = True
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 488
scripts/greaseweazle/image/edsk.py

@@ -1,488 +0,0 @@
-# greaseweazle/image/edsk.py
-#
-# Some of the code here is heavily inspired by Simon Owen's SAMdisk:
-# https://simonowen.com/samdisk/
-#
-# 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 binascii, math, struct
-import itertools as it
-from bitarray import bitarray
-
-from greaseweazle import error
-from greaseweazle.codec.ibm import mfm
-from greaseweazle.track import MasterTrack, RawTrack
-from .image import Image
-
-class SR1:
-    SUCCESS                   = 0x00
-    CANNOT_FIND_ID_ADDRESS    = 0x01
-    WRITE_PROTECT_DETECTED    = 0x02
-    CANNOT_FIND_SECTOR_ID     = 0x04
-    RESERVED1                 = 0x08
-    OVERRUN                   = 0x10
-    CRC_ERROR                 = 0x20
-    RESERVED2                 = 0x40
-    END_OF_CYLINDER           = 0x80
-
-class SR2:
-    SUCCESS                   = 0x00
-    MISSING_ADDRESS_MARK      = 0x01
-    BAD_CYLINDER              = 0x02
-    SCAN_COMMAND_FAILED       = 0x04
-    SCAN_COMMAND_EQUAL        = 0x08
-    WRONG_CYLINDER_DETECTED   = 0x10
-    CRC_ERROR_IN_SECTOR_DATA  = 0x20
-    SECTOR_WITH_DELETED_DATA  = 0x40
-    RESERVED                  = 0x80
-
-class SectorErrors:
-    def __init__(self, sr1, sr2):
-        self.id_crc_error = (sr1 & SR1.CRC_ERROR) != 0
-        self.data_not_found = (sr2 & SR2.MISSING_ADDRESS_MARK) != 0
-        self.data_crc_error = (sr2 & SR2.CRC_ERROR_IN_SECTOR_DATA) != 0
-        self.deleted_dam = (sr2 & SR2.SECTOR_WITH_DELETED_DATA) != 0
-        if self.data_crc_error:
-            # uPD765 sets both id and data flags for data CRC errors
-            self.id_crc_error = False
-        if (# normal data
-            (sr1 == SR1.SUCCESS and sr2 == SR2.SUCCESS) or
-            # deleted data
-            (sr1 == SR1.SUCCESS and sr2 == SR2.SECTOR_WITH_DELETED_DATA) or
-            # end of track
-            (sr1 == SR1.END_OF_CYLINDER and sr2 == SR2.SUCCESS) or
-            # id crc error
-            (sr1 == SR1.CRC_ERROR and sr2 == SR2.SUCCESS) or
-            # normal data crc error
-            (sr1 == SR1.CRC_ERROR and sr2 == SR2.CRC_ERROR_IN_SECTOR_DATA) or
-            # deleted data crc error
-            (sr1 == SR1.CRC_ERROR and sr2 == (SR2.CRC_ERROR_IN_SECTOR_DATA |
-                                              SR2.SECTOR_WITH_DELETED_DATA)) or
-            # data field missing (some FDCs set AM in ST1)
-            (sr1 == SR1.CANNOT_FIND_ID_ADDRESS
-             and sr2 == SR2.MISSING_ADDRESS_MARK) or
-            # data field missing (some FDCs don't)
-            (sr1 == SR1.SUCCESS and sr2 == SR2.MISSING_ADDRESS_MARK) or
-            # CHRN mismatch
-            (sr1 == SR1.CANNOT_FIND_SECTOR_ID and sr2 == SR2.SUCCESS) or
-            # CHRN mismatch, including wrong cylinder
-            (sr1 == SR1.CANNOT_FIND_SECTOR_ID
-             and sr2 == SR2.WRONG_CYLINDER_DETECTED)):
-            pass
-        else:
-            print('Unusual status flags (ST1=%02X ST2=%02X)' % (sr1, sr2))
-            
-class EDSKTrack:
-
-    gap_presync = 12
-    gap_4a = 80 # Post-Index
-    gap_1  = 50 # Post-IAM
-    gap_2  = 22 # Post-IDAM
-
-    gapbyte = 0x4e
-    
-    def __init__(self):
-        self.time_per_rev = 0.2
-        self.clock = 2e-6
-        self.bits, self.weak, self.bytes = [], [], bytearray()
-
-    def raw_track(self):
-        track = MasterTrack(
-            bits = self.bits,
-            time_per_rev = self.time_per_rev,
-            weak = self.weak)
-        track.verify = self
-        track.verify_revs = 1
-        return track
-
-    def _find_sync(self, bits, sync, start):
-        for offs in bits.itersearch(sync):
-            if offs >= start:
-                return offs
-        return None
-    
-    def verify_track(self, flux):
-        flux.cue_at_index()
-        raw = RawTrack(clock = self.clock, data = flux)
-        bits, _ = raw.get_all_data()
-        weak_iter = it.chain(self.weak, [(self.verify_len+1,1)])
-        weak = next(weak_iter)
-
-        # Start checking from the IAM sync
-        dump_start = self._find_sync(bits, mfm.iam_sync, 0)
-        self_start = self._find_sync(self.bits, mfm.iam_sync, 0)
-
-        # Include the IAM pre-sync header
-        if dump_start is None:
-            return False
-        dump_start -= self.gap_presync * 16
-        self_start -= self.gap_presync * 16
-
-        while self_start is not None and dump_start is not None:
-
-            # Find the weak areas immediately before and after the current
-            # region to be checked.
-            s,n = None,None
-            while self_start > weak[0]:
-                s,n = weak
-                weak = next(weak_iter)
-
-            # If there is a weak area preceding us, move the start point to
-            # immediately follow the weak area.
-            if s is not None:
-                delta = self_start - (s + n + 16)
-                self_start -= delta
-                dump_start -= delta
-
-            # Truncate the region at the next weak area, or the last sector.
-            self_end = max(self_start, min(weak[0], self.verify_len+1))
-            dump_end = dump_start + self_end - self_start
-
-            # Extract the corresponding areas from the pristine track and
-            # from the dump, and check that they match.
-            if bits[dump_start:dump_end] != self.bits[self_start:self_end]:
-                return False
-
-            # Find the next A1A1A1 sync pattern
-            dump_start = self._find_sync(bits, mfm.sync, dump_end)
-            self_start = self._find_sync(self.bits, mfm.sync, self_end)
-
-        # Did we verify all regions in the pristine track?
-        return self_start is None
-
-class EDSK(Image):
-
-    read_only = True
-
-    def __init__(self):
-        self.to_track = dict()
-
-    # Find all weak ranges in the given sector data copies.
-    @staticmethod
-    def find_weak_ranges(dat, size):
-        orig = dat[:size]
-        s, w = size, []
-        # Find first mismatching byte across all copies
-        for i in range(1, len(dat)//size):
-            diff = [x^y for x, y in zip(orig, dat[size*i:size*(i+1)])]
-            weak = [idx for idx, val in enumerate(diff) if val != 0]
-            if weak:
-                s = min(s, weak[0])
-        # Look for runs of filler
-        i = s
-        while i < size:
-            j, x = i, orig[i]
-            while j < size and orig[j] == x:
-                j += 1
-            if j-i >= 16:
-                w.append((s,i-s))
-                s = j
-            i = j
-        # Append final weak area if any.
-        if s < size:
-            w.append((s,size-s))
-        return w
-
-    @staticmethod
-    def _build_8k_track(sectors):
-        if len(sectors) != 1:
-            return None
-        c,h,r,n,errs,data = sectors[0]
-        if n != 6:
-            return None
-        if errs.id_crc_error or errs.data_not_found or not errs.data_crc_error:
-            return None
-        # Magic longtrack value is for Coin-Op Hits. Taken from SAMdisk.
-        if len(data) > 6307:
-            data = data[:6307]
-        track = EDSKTrack()
-        t = track.bytes
-        # Post-index gap
-        t += mfm.encode(bytes([track.gapbyte] * 16))
-        # IAM
-        t += mfm.encode(bytes(track.gap_presync))
-        t += mfm.iam_sync_bytes
-        t += mfm.encode(bytes([mfm.IBM_MFM.IAM]))
-        t += mfm.encode(bytes([track.gapbyte] * 16))
-        # IDAM
-        t += mfm.encode(bytes(track.gap_presync))
-        t += mfm.sync_bytes
-        am = bytes([0xa1, 0xa1, 0xa1, mfm.IBM_MFM.IDAM, c, h, r, n])
-        crc = mfm.crc16.new(am).crcValue
-        am += struct.pack('>H', crc)
-        t += mfm.encode(am[3:])
-        t += mfm.encode(bytes([track.gapbyte] * track.gap_2))
-        # DAM
-        t += mfm.encode(bytes(track.gap_presync))
-        t += mfm.sync_bytes
-        dmark = (mfm.IBM_MFM.DDAM if errs.deleted_dam
-                 else mfm.IBM_MFM.DAM)
-        am = bytes([0xa1, 0xa1, 0xa1, dmark]) + data
-        t += mfm.encode(am[3:])
-        return track
-
-    @staticmethod
-    def _build_kbi19_track(sectors):
-        ids = [0,1,4,7,10,13,16,2,5,8,11,14,17,3,6,9,12,15,18]
-        if len(sectors) != len(ids):
-            return None
-        for s,id in zip(sectors,ids):
-            c,h,r,n,_,_ = s
-            if r != id or n != 2:
-                return None
-        def addcrc(t,n):
-            crc = mfm.crc16.new(mfm.decode(t[-n*2:])).crcValue
-            t += mfm.encode(struct.pack('>H', crc))
-        track = EDSKTrack()
-        t = track.bytes
-        # Post-index gap
-        t += mfm.encode(bytes([track.gapbyte] * 64))
-        # IAM
-        t += mfm.encode(bytes(track.gap_presync))
-        t += mfm.iam_sync_bytes
-        t += mfm.encode(bytes([mfm.IBM_MFM.IAM]))
-        t += mfm.encode(bytes([track.gapbyte] * 50))
-        for idx, s in enumerate(sectors):
-            c,h,r,n,errs,data = s
-            # IDAM
-            t += mfm.encode(bytes(track.gap_presync))
-            t += mfm.sync_bytes
-            t += mfm.encode(bytes([mfm.IBM_MFM.IDAM, c, h, r, n]))
-            addcrc(t, 8)
-            if r == 0:
-                t += mfm.encode(bytes([track.gapbyte] * 17))
-                t += mfm.encode(b' KBI ')
-            else:
-                t += mfm.encode(bytes([track.gapbyte] * 8))
-                t += mfm.encode(b' KBI ')
-                t += mfm.encode(bytes([track.gapbyte] * 9))
-            # DAM
-            t += mfm.encode(bytes(track.gap_presync))
-            t += mfm.sync_bytes
-            dmark = (mfm.IBM_MFM.DDAM if errs.deleted_dam
-                     else mfm.IBM_MFM.DAM)
-            t += mfm.encode(bytes([dmark]))
-            if idx%3 != 0:
-                t += mfm.encode(data[:61])
-            elif r == 0:
-                t += mfm.encode(data[:512])
-                addcrc(t,516)
-            else:
-                t += mfm.encode(data[0:0x10e])
-                addcrc(t,516)
-                t += mfm.encode(data[0x110:0x187])
-                addcrc(t,516)
-                t += mfm.encode(data[0x189:0x200])
-                addcrc(t,516)
-                t += mfm.encode(bytes([track.gapbyte] * 80))
-        return track
-
-    @classmethod
-    def from_file(cls, name):
-
-        with open(name, "rb") as f:
-            dat = f.read()
-
-        edsk = cls()
-
-        sig, creator, ncyls, nsides, track_sz = struct.unpack(
-            '<34s14s2BH', dat[:52])
-        if sig[:8] == b'MV - CPC':
-            extended = False
-        elif sig[:16] == b'EXTENDED CPC DSK':
-            extended = True
-        else:
-            raise error.Fatal('Unrecognised CPC DSK file: bad signature')
-
-        if extended:
-            track_sizes = list(dat[52:52+ncyls*nsides])
-            track_sizes = list(map(lambda x: x*256, track_sizes))
-        else:
-            track_sizes = [track_sz] * (ncyls * nsides)
-
-        o = 256 # skip disk header and track-size table
-        for track_size in track_sizes:
-            if track_size == 0:
-                continue
-            sig, cyl, head, sec_sz, nsecs, gap_3, filler = struct.unpack(
-                '<12s4x2B2x4B', dat[o:o+24])
-            error.check(sig == b'Track-Info\r\n',
-                        'EDSK: Missing track header')
-            error.check((cyl, head) not in edsk.to_track,
-                        'EDSK: Track specified twice')
-            bad_crc_clip_data = False
-            while True:
-                track = EDSKTrack()
-                t = track.bytes
-                # Post-index gap
-                t += mfm.encode(bytes([track.gapbyte] * track.gap_4a))
-                # IAM
-                t += mfm.encode(bytes(track.gap_presync))
-                t += mfm.iam_sync_bytes
-                t += mfm.encode(bytes([mfm.IBM_MFM.IAM]))
-                t += mfm.encode(bytes([track.gapbyte] * track.gap_1))
-                sh = dat[o+24:o+24+8*nsecs]
-                data_pos = o + 256 # skip track header and sector-info table
-                clippable, ngap3, sectors, idam_included = 0, 0, [], False
-                while sh:
-                    c, h, r, n, stat1, stat2, data_size = struct.unpack(
-                        '<6BH', sh[:8])
-                    sh = sh[8:]
-                    native_size = mfm.sec_sz(n)
-                    weak = []
-                    errs = SectorErrors(stat1, stat2)
-                    num_copies = 0 if errs.data_not_found else 1
-                    if not extended:
-                        data_size = mfm.sec_sz(sec_sz)
-                    sec_data = dat[data_pos:data_pos+data_size]
-                    data_pos += data_size
-                    if (extended
-                        and data_size > native_size
-                        and errs.data_crc_error
-                        and (data_size % native_size == 0
-                             or data_size == 49152)):
-                        num_copies = (3 if data_size == 49152
-                                      else data_size // native_size)
-                        data_size //= num_copies
-                        weak = cls().find_weak_ranges(sec_data, data_size)
-                        sec_data = sec_data[:data_size]
-                    sectors.append((c,h,r,n,errs,sec_data))
-                    # IDAM
-                    if not idam_included:
-                        t += mfm.encode(bytes(track.gap_presync))
-                        t += mfm.sync_bytes
-                        am = bytes([0xa1, 0xa1, 0xa1, mfm.IBM_MFM.IDAM,
-                                    c, h, r, n])
-                        crc = mfm.crc16.new(am).crcValue
-                        if errs.id_crc_error:
-                            crc ^= 0x5555
-                        am += struct.pack('>H', crc)
-                        t += mfm.encode(am[3:])
-                        t += mfm.encode(bytes([track.gapbyte] * track.gap_2))
-                    # DAM
-                    gap_included, idam_included = False, False
-                    if errs.id_crc_error or errs.data_not_found:
-                        continue
-                    t += mfm.encode(bytes(track.gap_presync))
-                    t += mfm.sync_bytes
-                    track.weak += [((s+len(t)//2+1)*16, n*16) for s,n in weak]
-                    dmark = (mfm.IBM_MFM.DDAM if errs.deleted_dam
-                             else mfm.IBM_MFM.DAM)
-                    if errs.data_crc_error:
-                        if sh:
-                            # Look for next IDAM
-                            idam = bytes([0]*12 + [0xa1]*3
-                                         + [mfm.IBM_MFM.IDAM])
-                            idx = sec_data.find(idam)
-                        else:
-                            # Last sector: Look for GAP3
-                            idx = sec_data.find(bytes([track.gapbyte]*8))
-                        if idx > 0:
-                            # 2 + gap_3 = CRC + GAP3 (because gap_included)
-                            clippable += data_size - idx + 2 + gap_3
-                            if bad_crc_clip_data:
-                                data_size = idx
-                                sec_data = sec_data[:data_size]
-                                gap_included = True
-                    elif data_size < native_size:
-                        # Pad short data
-                        sec_data += bytes(native_size - data_size)
-                    elif data_size > native_size:
-                        # Clip long data if it includes pre-sync 00 bytes
-                        if (sec_data[-13] != 0
-                            and all([v==0 for v in sec_data[-12:]])):
-                            # Includes next pre-sync: Clip it.
-                            sec_data = sec_data[:-12]
-                        if sh:
-                            # Look for next IDAM
-                            idam = bytes([0]*12 + [0xa1]*3 + [mfm.IBM_MFM.IDAM]
-                                         + list(sh[:4]))
-                            idx = sec_data.find(idam)
-                            if idx > native_size:
-                                # Sector data includes next IDAM. Output it
-                                # here and skip it on next iteration.
-                                t += mfm.encode(bytes([dmark]))
-                                t += mfm.encode(sec_data[:idx+12])
-                                t += mfm.sync_bytes
-                                t += mfm.encode(sec_data[idx+12+3:])
-                                idam_included = True
-                                continue
-                        # Long data includes CRC and GAP
-                        gap_included = True
-                    if gap_included:
-                        t += mfm.encode(bytes([dmark]))
-                        t += mfm.encode(sec_data)
-                        continue
-                    am = bytes([0xa1, 0xa1, 0xa1, dmark]) + sec_data
-                    crc = mfm.crc16.new(am).crcValue
-                    if errs.data_crc_error:
-                        crc ^= 0x5555
-                    am += struct.pack('>H', crc)
-                    t += mfm.encode(am[3:])
-                    if sh:
-                        # GAP3 for all but last sector
-                        t += mfm.encode(bytes([track.gapbyte] * gap_3))
-                        ngap3 += 1
-
-                # Special track handlers
-                special_track = cls()._build_8k_track(sectors)
-                if special_track is None:
-                    special_track = cls()._build_kbi19_track(sectors)
-                if special_track is not None:
-                    track = special_track
-                    break
-
-                # The track may be too long to fit: Check for overhang.
-                tracklen = int((track.time_per_rev / track.clock) / 16)
-                overhang = int(len(t)//2 - tracklen*0.99)
-                if overhang <= 0:
-                    break
-
-                # Some EDSK tracks with Bad CRC contain a raw dump following
-                # the DAM. This can usually be clipped.
-                if clippable and not bad_crc_clip_data:
-                    bad_crc_clip_data = True
-                    continue
-
-                # Some EDSK images have bogus GAP3 values. Shrink it if
-                # necessary.
-                new_gap_3 = -1
-                if ngap3 != 0:
-                    new_gap_3 = gap_3 - math.ceil(overhang / ngap3)
-                error.check(new_gap_3 >= 0,
-                            'EDSK: Track %d.%d is too long '
-                            '(%d bits @ GAP3=%d; %d bits @ GAP3=0)'
-                            % (cyl, head, len(t)*8, gap_3,
-                               (len(t)//2-gap_3*ngap3)*16))
-                #print('EDSK: GAP3 reduced (%d -> %d)' % (gap_3, new_gap_3))
-                gap_3 = new_gap_3
-
-            # Pre-index gap
-            track.verify_len = len(track.bytes)*8
-            tracklen = int((track.time_per_rev / track.clock) / 16)
-            gap = max(40, tracklen - len(t)//2)
-            track.bytes += mfm.encode(bytes([track.gapbyte] * gap))
-
-            # Add the clock buts
-            track.bits = bitarray(endian='big')
-            track.bits.frombytes(mfm.mfm_encode(track.bytes))
-
-            # Register the track
-            edsk.to_track[cyl,head] = track
-            o += track_size
-
-        return edsk
-
-
-    def get_track(self, cyl, side):
-        if (cyl,side) not in self.to_track:
-            return None
-        return self.to_track[cyl,side].raw_track()
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 135
scripts/greaseweazle/image/hfe.py

@@ -1,135 +0,0 @@
-# greaseweazle/image/hfe.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
-
-from greaseweazle import error
-from greaseweazle.track import MasterTrack, RawTrack
-from bitarray import bitarray
-from .image import Image
-
-class HFE(Image):
-
-    def __init__(self):
-        self.bitrate = 250 # XXX real bitrate?
-        # Each track is (bitlen, rawbytes).
-        # rawbytes is a bytes() object in little-endian bit order.
-        self.to_track = dict()
-
-
-    @classmethod
-    def from_file(cls, name):
-
-        with open(name, "rb") as f:
-            dat = f.read()
-
-        (sig, f_rev, n_cyl, n_side, t_enc, bitrate,
-         _, _, _, tlut_base) = struct.unpack("<8s4B2H2BH", dat[:20])
-        error.check(sig != b"HXCHFEV3", "HFEv3 is not supported")
-        error.check(sig == b"HXCPICFE" and f_rev <= 1, "Not a valid HFE file")
-        error.check(0 < n_cyl, "HFE: Invalid #cyls")
-        error.check(0 < n_side < 3, "HFE: Invalid #sides")
-        error.check(bitrate != 0, "HFE: Invalid bitrate")
-
-        hfe = cls()
-        hfe.bitrate = bitrate
-
-        tlut = dat[tlut_base*512:tlut_base*512+n_cyl*4]
-        
-        for cyl in range(n_cyl):
-            for side in range(n_side):
-                offset, length = struct.unpack("<2H", tlut[cyl*4:(cyl+1)*4])
-                todo = length // 2
-                tdat = bytes()
-                while todo:
-                    d_off = offset*512 + side*256
-                    d_nr = 256 if todo > 256 else todo
-                    tdat += dat[d_off:d_off+d_nr]
-                    todo -= d_nr
-                    offset += 1
-                hfe.to_track[cyl,side] = (len(tdat)*8, tdat)
-
-        return hfe
-
-
-    def get_track(self, cyl, side):
-        if (cyl,side) not in self.to_track:
-            return None
-        bitlen, rawbytes = self.to_track[cyl,side]
-        tdat = bitarray(endian='little')
-        tdat.frombytes(rawbytes)
-        track = MasterTrack(
-            bits = tdat[:bitlen],
-            time_per_rev = bitlen / (2000*self.bitrate))
-        return track
-
-
-    def emit_track(self, cyl, side, 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())
-
-
-    def get_image(self):
-
-        n_side = 1
-        n_cyl = max(self.to_track.keys(), default=(0), key=lambda x:x[0])[0]
-        n_cyl += 1
-
-        # We dynamically build the Track-LUT and -Data arrays.
-        tlut = bytearray()
-        tdat = bytearray()
-
-        # Stuff real data into the image.
-        for i in range(n_cyl):
-            s0 = self.to_track[i,0] if (i,0) in self.to_track else None
-            s1 = self.to_track[i,1] if (i,1) in self.to_track else None
-            if s0 is None and s1 is None:
-                # Dummy data for empty cylinders. Assumes 300RPM.
-                nr_bytes = 100 * self.bitrate
-                tlut += struct.pack("<2H", len(tdat)//512 + 2, nr_bytes)
-                tdat += bytes([0x88] * (nr_bytes+0x1ff & ~0x1ff))
-            else:
-                # At least one side of this cylinder is populated.
-                if s1 is not None:
-                    n_side = 2
-                bc = [s0 if s0 is not None else (0,bytes()),
-                      s1 if s1 is not None else (0,bytes())]
-                nr_bytes = max(len(t[1]) for t in bc)
-                nr_blocks = (nr_bytes + 0xff) // 0x100
-                tlut += struct.pack("<2H", len(tdat)//512 + 2, 2 * nr_bytes)
-                for b in range(nr_blocks):
-                    for t in bc:
-                        slice = t[1][b*256:(b+1)*256]
-                        tdat += slice + bytes([0x88] * (256 - len(slice)))
-
-        # Construct the image header.
-        header = struct.pack("<8s4B2H2BH",
-                             b"HXCPICFE",
-                             0,
-                             n_cyl,
-                             n_side,
-                             0xff, # unknown encoding
-                             self.bitrate,
-                             0,    # rpm (unused)
-                             0xff, # unknown interface
-                             1,    # rsvd
-                             1)    # track list offset
-
-        # Pad the header and TLUT to 512-byte blocks.
-        header += bytes([0xff] * (0x200 - len(header)))
-        tlut += bytes([0xff] * (0x200 - len(tlut)))
-
-        return header + tlut + tdat
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 72
scripts/greaseweazle/image/image.py

@@ -1,72 +0,0 @@
-# greaseweazle/image/image.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
-
-from greaseweazle import error
-
-class Image:
-
-    read_only = False
-
-    ## Context manager for image objects created using .to_file()
-
-    def __enter__(self):
-        self.file = open(self.filename, "wb")
-        return self
-
-    def __exit__(self, type, value, tb):
-        try:
-            if type is None:
-                # No error: Normal writeout.
-                self.file.write(self.get_image())
-        finally:
-            # Always close the file.
-            self.file.close()
-        if type is not None:
-            # An error occurred: We remove the target file.
-            os.remove(self.filename)
-
-    ## Default .to_file() constructor
-    @classmethod
-    def to_file(cls, name, fmt=None):
-        error.check(not cls.read_only,
-                    "%s: Cannot create %s image files" % (name, cls.__name__))
-        obj = cls()
-        obj.filename = name
-        obj.fmt = fmt
-        return obj
-
-    # Maximum non-empty cylinder on each head, or -1 if no cylinders exist.
-    # Returns a list of integers, indexed by head.
-    def max_cylinder(self):
-        r = list()
-        for h in range(2):
-            for c in range(100, -2, -1):
-                if c < 0 or self.get_track(c,h) is not None:
-                    r.append(c)
-                    break
-        return r
-
-    ## Above methods and class variables can be overridden by subclasses.
-    ## Additionally, subclasses must provide following public interfaces:
-
-    ## Read support:
-    # def from_file(cls, name)
-    # def get_track(self, cyl, side)
-
-    ## Write support (if not cls.read_only):
-    # def emit_track(self, cyl, side, track)
-    ## Plus either:
-    # def get_image(self)
-    ## Or:
-    # __enter__ / __exit__
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 84
scripts/greaseweazle/image/img.py

@@ -1,84 +0,0 @@
-# greaseweazle/image/img.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>.
-
-from greaseweazle import error
-from greaseweazle.codec.ibm import mfm
-from .image import Image
-
-from greaseweazle.codec import formats
-
-class IMG(Image):
-
-    sides_swapped = False
-    
-    def __init__(self, name, fmt):
-        self.to_track = dict()
-        error.check(fmt is not None and fmt.img_compatible, """\
-Sector image requires compatible format specifier
-Compatible formats:\n%s"""
-                    % formats.print_formats(
-                        lambda k, v: v.img_compatible))
-        self.filename = name
-        self.fmt = fmt
-
-
-    @classmethod
-    def from_file(cls, name, fmt):
-
-        with open(name, "rb") as f:
-            dat = f.read()
-
-        img = cls(name, fmt)
-
-        pos = 0
-        for t in fmt.max_tracks:
-            cyl, head = t.cyl, t.head
-            if img.sides_swapped:
-                head ^= 1
-            track = fmt.fmt(cyl, head)
-            pos += track.set_img_track(dat[pos:])
-            img.to_track[cyl,head] = track
-
-        return img
-
-
-    @classmethod
-    def to_file(cls, name, fmt=None):
-        return cls(name, fmt)
-
-
-    def get_track(self, cyl, side):
-        if (cyl,side) not in self.to_track:
-            return None
-        return self.to_track[cyl,side].raw_track()
-
-
-    def emit_track(self, cyl, side, track):
-        self.to_track[cyl,side] = track
-
-
-    def get_image(self):
-
-        tdat = bytearray()
-
-        n_side = 2
-        n_cyl = max(self.to_track.keys(), default=(0), key=lambda x:x[0])[0]
-        n_cyl += 1
-
-        for cyl in range(n_cyl):
-            for head in range(n_side):
-                if self.sides_swapped:
-                    head ^= 1
-                if (cyl,head) in self.to_track:
-                    tdat += self.to_track[cyl,head].get_img_track()
-
-        return tdat
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 338
scripts/greaseweazle/image/ipf.py

@@ -1,338 +0,0 @@
-# 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, sys
-import platform
-import ctypes as ct
-import itertools as it
-from bitarray import bitarray
-from greaseweazle.track import MasterTrack, RawTrack
-from greaseweazle import error
-from .image import Image
-
-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 IPFTrack(MasterTrack):
-
-    verify_revs = 2
-    tolerance = 100
-
-    @staticmethod
-    def strong_data(sector, weak):
-        """Return list of sector data areas excluding weak sections."""
-        def range_next(i):
-            s,l = next(i)
-            return s, s+l
-        weak_tol = 16 # Skip this number of bits after a weak area
-        weak_iter = it.chain(weak, [(1<<30,1)])
-        ws,we = -1,-1
-        sector_iter = iter(sector)
-        s,e = range_next(sector_iter)
-        try:
-            while True:
-                while we <= s:
-                    ws,we = range_next(weak_iter)
-                    we += weak_tol
-                if ws < e:
-                    if s < ws:
-                        yield (s,ws-s)
-                    s = we
-                else:
-                    yield (s,e-s)
-                    s = e
-                if s >= e:
-                    s,e = range_next(sector_iter)
-        except StopIteration:
-            pass
-
-    def verify_track(self, flux):
-        flux.cue_at_index()
-        raw = RawTrack(clock = self.time_per_rev/len(self.bits), data = flux)
-        raw_bits, _ = raw.get_all_data()
-        for s,l in IPFTrack.strong_data(self.sectors, self.weak):
-            sector = self.bits[s:s+l]
-            # Search within an area +/- the pre-defined # bitcells tolerance
-            raw_area = raw_bits[max(self.splice + s - self.tolerance, 0)
-                                : self.splice + s + l + self.tolerance]
-            # All we care about is at least one match (this is a bit fuzzy)
-            if next(raw_area.itersearch(sector), None) is None:
-                return False
-        return True
-
-class IPF(Image):
-
-    read_only = True
-
-    def __init__(self):
-        self.lib = get_libcaps()
-
-    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_file(cls, name):
-
-        ipf = cls()
-
-        ipf.iid = ipf.lib.CAPSAddImage()
-        error.check(ipf.iid >= 0, "Could not create IPF image container")
-        cname = ct.c_char_p(name.encode())
-        res = ipf.lib.CAPSLockImage(ipf.iid, cname)
-        error.check(res == 0, "Could not open IPF image '%s'" % name)
-        res = ipf.lib.CAPSLoadImage(ipf.iid, DI_LOCK.def_flags)
-        error.check(res == 0, "Could not load IPF image '%s'" % name)
-        ipf.pi = CapsImageInfo()
-        res = ipf.lib.CAPSGetImageInfo(ct.byref(ipf.pi), ipf.iid)
-        error.check(res == 0, "Could not get info for IPF '%s'" % name)
-        print(ipf)
-
-        return ipf
-
-
-    def get_track(self, cyl, head):
-        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)
-        error.check(res == 0, "Could not lock IPF track %d.%d" % (cyl, head))
-
-        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]
-
-        data = []
-        for i in range(ti.sectorcnt):
-            si = CapsSectorInfo()
-            res = self.lib.CAPSGetInfo(ct.byref(si), self.iid,
-                                       cyl, head, 1, i)
-            error.check(res == 0, "Couldn't get sector info")
-            # Adjust the range start to be splice- rather than index-relative
-            data.append(((si.datastart - ti.overlap) % ti.tracklen,
-                         si.datasize))
-
-        weak = []
-        for i in range(ti.weakcnt):
-            wi = CapsDataInfo()
-            res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid,
-                                       cyl, head, 2, i)
-            error.check(res == 0, "Couldn't get weak data info")
-            # Adjust the range start to be splice- rather than index-relative
-            weak.append(((wi.start - ti.overlap) % ti.tracklen, wi.size))
-
-        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]
-
-        # Rotate the track to start at the splice rather than the index.
-        if ti.overlap:
-            trackbuf = trackbuf[ti.overlap:] + trackbuf[:ti.overlap]
-            if timebuf:
-                timebuf = timebuf[ti.overlap:] + timebuf[:ti.overlap]
-            
-        # 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).
-        rpm = 300
-
-        track = IPFTrack(
-            bits = trackbuf,
-            time_per_rev = 60/rpm,
-            bit_ticks = timebuf,
-            splice = ti.overlap,
-            weak = weak)
-        track.verify = track
-        track.sectors = data
-        return track
-
-
-# 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
-    
-    error.check("lib" in locals(), """\
-Could not find SPS/CAPS IPF decode library
-For installation instructions please read the wiki:
-<https://github.com/keirf/Greaseweazle/wiki/IPF-Images>""")
-    
-    # We have opened the library. Now initialise it.
-    res = lib.CAPSInit()
-    error.check(res == 0, "Failure initialising IPF library '%s'" % name)
-
-    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:

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

@@ -1,234 +0,0 @@
-# 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, fmt=None):
-        return cls(name)
-
-    @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 emit_track(self, cyl, side, 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()
-
-        # HxC crashes or fails to load non-index-cued stream files.
-        # So let's give it what it wants.
-        flux.cue_at_index()
-
-        factor = sck / flux.sample_freq
-        dat = bytearray()
-
-        # Start the data stream with a dummy index if our Flux is index cued.
-        if flux.index_cued:
-            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)
-        # A dummy cell so that we definitely have *something* after the
-        # final OOB.Index, so that all parsers should register the Index.
-        emit(round(sck*12e-6)) # 12us
-
-        # 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' % (cyl, side)
-        with open(name, 'wb') as f:
-                f.write(dat)
-
-
-    def __enter__(self):
-        return self
-    def __exit__(self, type, value, tb):
-        pass
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 372
scripts/greaseweazle/image/scp.py

@@ -1,372 +0,0 @@
-# greaseweazle/image/scp.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, functools
-
-from greaseweazle import error
-from greaseweazle.flux import Flux
-from .image import Image
-
-
-# Names for disktype byte in SCP file header
-DiskType = {
-    'amiga':       0x04,
-    'c64':         0x00,
-    'atari800-sd': 0x10,
-    'atari800-dd': 0x11,
-    'atari800-ed': 0x12,
-    'atarist-ss':  0x14,
-    'atarist-ds':  0x15,
-    'appleII':     0x20,
-    'appleIIpro':  0x21,
-    'apple-400k':  0x24,
-    'apple-800k':  0x25,
-    'apple-1m44':  0x26,
-    'ibmpc-320k':  0x30,
-    'ibmpc-720k':  0x31,
-    'ibmpc-1m2':   0x32,
-    'ibmpc-1m44':  0x33,
-    'trs80_sssd':  0x40,
-    'trs80_ssdd':  0x41,
-    'trs80_dssd':  0x42,
-    'trs80_dsdd':  0x43,
-    'ti-99/4a':    0x50,
-    'roland-d20':  0x60,
-    'amstrad-cpc': 0x70,
-    'other-320k':  0x80,
-    'other-1m2':   0x81,
-    'other-720k':  0x84,
-    'other-1m44':  0x85,
-    'tape-gcr1':   0xe0,
-    'tape-gcr2':   0xe1,
-    'tape-mfm':    0xe2,
-    'hdd-mfm':     0xf0,
-    'hdd-rll':     0xf1
-}
-
-
-class SCPOpts:
-    """legacy_ss: Set to True to generate (incorrect) legacy single-sided
-    SCP image.
-    """
-
-    def __init__(self):
-        self.legacy_ss = False
-        self._disktype = 0x80 # Other
-
-    @property
-    def disktype(self):
-        return self._disktype
-    @disktype.setter
-    def disktype(self, disktype):
-        try:
-            self._disktype = DiskType[disktype.lower()]
-        except KeyError:
-            try:
-                self._disktype = int(disktype, 0)
-            except ValueError:
-                raise error.Fatal("Bad SCP disktype: '%s'" % disktype)
-
-
-class SCPTrack:
-
-    def __init__(self, tdh, dat, splice=None):
-        self.tdh = tdh
-        self.dat = dat
-        self.splice = splice
-
-
-class SCP(Image):
-
-    # 40MHz
-    sample_freq = 40000000
-
-
-    def __init__(self):
-        self.opts = SCPOpts()
-        self.nr_revs = None
-        self.to_track = dict()
-        self.index_cued = True
-
-    
-    def side_count(self):
-        s = [0,0] # non-empty tracks on each side
-        for tnr in self.to_track:
-            s[tnr&1] += 1
-        return s
-
-
-    @classmethod
-    def from_file(cls, name):
-
-        splices = None
-
-        with open(name, "rb") as f:
-            dat = f.read()
-
-        header = struct.unpack("<3s9BI", dat[0:16])
-        sig, _, disk_type, nr_revs, _, _, flags, _, single_sided, _, _ = header
-        error.check(sig == b"SCP", "SCP: Bad signature")
-
-        index_cued = flags & 1 or nr_revs == 1
-
-        # Some tools generate a short TLUT. We handle this by truncating the
-        # TLUT at the first Track Data Header.
-        trk_offs = struct.unpack("<168I", dat[16:0x2b0])
-        for i in range(168):
-            try:
-                off = trk_offs[i]
-            except IndexError:
-                break
-            if off == 0 or off >= 0x2b0:
-                continue
-            off = off//4 - 4
-            error.check(off >= 0, "SCP: Bad Track Table")
-            trk_offs = trk_offs[:off]
-
-        # Parse the extension block introduced by github:markusC64/g64conv.
-        # b'EXTS', length, <length bytes: Extension Area>
-        # Extension Area contains consecutive chunks of the form:
-        # ID, length, <length bytes: ID-specific data>
-        ext_sig, ext_len = struct.unpack('<4sI', dat[0x2b0:0x2b8])
-        min_tdh = min(filter(lambda x: x != 0, trk_offs), default=0)
-        if ext_sig == b'EXTS' and 0x2b8 + ext_len <= min_tdh:
-            pos, end = 0x2b8, 0x2b8 + ext_len
-            while end - pos >= 8:
-                chk_sig, chk_len = struct.unpack('<4sI', dat[pos:pos+8])
-                pos += 8
-                # WRSP: WRite SPlice information block.
-                # Data is comprised of >= 169 32-bit values:
-                #  0: Flags (currently unused; must be zero)
-                #  N: Write splice/overlap position for track N, in SCP ticks
-                #     (zero if the track is unused)
-                if chk_sig == b'WRSP' and chk_len >= 169*4:
-                    # Write-splice positions for writing out SCP tracks
-                    # correctly to disk.
-                    splices = struct.unpack('<168I', dat[pos+4:pos+169*4])
-                pos += chk_len
-
-        scp = cls()
-        scp.nr_revs = nr_revs
-        if not index_cued:
-            scp.nr_revs -= 1
-
-        for trknr in range(len(trk_offs)):
-            
-            trk_off = trk_offs[trknr]
-            if trk_off == 0:
-                continue
-
-            # Parse the SCP track header and extract the flux data.
-            thdr = dat[trk_off:trk_off+4+12*nr_revs]
-            sig, tnr = struct.unpack("<3sB", thdr[:4])
-            error.check(sig == b"TRK", "SCP: Missing track signature")
-            error.check(tnr == trknr, "SCP: Wrong track number in header")
-            thdr = thdr[4:] # Remove TRK header
-            if not index_cued: # Remove first partial revolution
-                thdr = thdr[12:]
-            s_off, = struct.unpack("<I", thdr[8:12])
-            _, e_nr, e_off = struct.unpack("<3I", thdr[-12:])
-
-            e_off += e_nr*2
-            if s_off == e_off:
-                # FluxEngine creates dummy TDHs for empty tracks.
-                # Bail on them here.
-                continue
-
-            tdat = dat[trk_off+s_off:trk_off+e_off]
-            track = SCPTrack(thdr, tdat)
-            if splices is not None:
-                track.splice = splices[trknr]
-            scp.to_track[trknr] = track
-
-        s = scp.side_count()
-
-        # C64 images with halftracks are genberated by Supercard Pro using
-        # consecutive track numbers. That needs fixup here for our layout.
-        # We re-use the legacy-single-sided fixup below.
-        if (single_sided == 0 and disk_type == 0
-            and s[1] and s[0]==s[1]+1 and s[0] < 42):
-            single_sided = 1
-            print('SCP: Importing C64 image with halftracks')
-
-        # Some tools produce (or used to produce) single-sided images using
-        # consecutive entries in the TLUT. This needs fixing up.
-        if single_sided and s[0] and s[1]:
-            new_dict = dict()
-            for tnr in scp.to_track:
-                new_dict[tnr*2+single_sided-1] = scp.to_track[tnr]
-            scp.to_track = new_dict
-            print('SCP: Imported legacy single-sided image')
-
-        return scp
-
-
-    def get_track(self, cyl, side):
-        tracknr = cyl * 2 + side
-        if not tracknr in self.to_track:
-            return None
-        track = self.to_track[tracknr]
-        tdh, dat = track.tdh, track.dat
-
-        index_list = []
-        while tdh:
-            ticks, _, _ = struct.unpack("<3I", tdh[:12])
-            index_list.append(ticks)
-            tdh = tdh[12:]
-        
-        # Decode the SCP flux data into a simple list of flux times.
-        flux_list = []
-        val = 0
-        for i in range(0, len(dat), 2):
-            x = dat[i]*256 + dat[i+1]
-            if x == 0:
-                val += 65536
-                continue
-            flux_list.append(val + x)
-            val = 0
-
-        flux = Flux(index_list, flux_list, SCP.sample_freq)
-        flux.splice = track.splice if track.splice is not None else 0
-        return flux
-
-
-    def emit_track(self, cyl, side, track):
-        """Converts @track into a Supercard Pro Track and appends it to
-        the current image-in-progress.
-        """
-
-        flux = track.flux()
-
-        # External tools and emulators generally seem to work best (or only)
-        # with index-cued SCP image files. So let's make sure we give them
-        # what they want.
-        flux.cue_at_index()
-
-        if not flux.index_cued:
-            self.index_cued = False
-
-        nr_revs = len(flux.index_list)
-        if not self.nr_revs:
-            self.nr_revs = nr_revs
-        else:
-            assert self.nr_revs == nr_revs
-        
-        factor = SCP.sample_freq / flux.sample_freq
-
-        tdh, dat = bytearray(), bytearray()
-        len_at_index = rev = 0
-        to_index = flux.index_list[0]
-        rem = 0.0
-
-        for x in flux.list:
-
-            # Does the next flux interval cross the index mark?
-            while to_index < x:
-                # Append to the TDH for the previous full revolution
-                tdh += struct.pack("<III",
-                                   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
-                len_at_index = len(dat)
-                rev += 1
-                if rev >= nr_revs:
-                    # We're done: We simply discard any surplus flux samples
-                    self.to_track[cyl*2+side] = SCPTrack(tdh, dat)
-                    return
-                to_index += flux.index_list[rev]
-
-            # Process the current flux sample into SCP "bitcell" format
-            to_index -= x
-            y = x * factor + rem
-            val = round(y)
-            if (val & 65535) == 0:
-                val += 1
-            rem = y - val
-            while val >= 65536:
-                dat.append(0)
-                dat.append(0)
-                val -= 65536
-            dat.append(val>>8)
-            dat.append(val&255)
-
-        # Header for last track(s) in case we ran out of flux timings.
-        while rev < nr_revs:
-            tdh += struct.pack("<III",
-                               round(flux.index_list[rev]*factor),
-                               (len(dat) - len_at_index) // 2,
-                               4 + nr_revs*12 + len_at_index)
-            len_at_index = len(dat)
-            rev += 1
-
-        self.to_track[cyl*2+side] = SCPTrack(tdh, dat)
-
-
-    def get_image(self):
-
-        # Work out the single-sided byte code
-        s = self.side_count()
-        if s[0] and s[1]:
-            single_sided = 0
-        elif s[0]:
-            single_sided = 1
-        else:
-            single_sided = 2
-
-        to_track = self.to_track
-        if single_sided and self.opts.legacy_ss:
-            print('SCP: Generated legacy single-sided image')
-            to_track = dict()
-            for tnr in self.to_track:
-                to_track[tnr//2] = self.to_track[tnr]
-
-        ntracks = max(to_track, default=0) + 1
-
-        # Generate the TLUT and concatenate all the tracks together.
-        trk_offs = bytearray()
-        trk_dat = bytearray()
-        for tnr in range(ntracks):
-            if tnr in to_track:
-                track = to_track[tnr]
-                trk_offs += struct.pack("<I", 0x2b0 + len(trk_dat))
-                trk_dat += struct.pack("<3sB", b"TRK", tnr)
-                trk_dat += track.tdh + track.dat
-            else:
-                trk_offs += struct.pack("<I", 0)
-        error.check(len(trk_offs) <= 0x2a0, "SCP: Too many tracks")
-        trk_offs += bytes(0x2a0 - len(trk_offs))
-
-        # Calculate checksum over all data (except 16-byte image header).
-        csum = 0
-        for x in trk_offs:
-            csum += x
-        for x in trk_dat:
-            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
-                             self.opts.disktype,
-                             self.nr_revs, 0, ntracks-1,
-                             flags,
-                             0,         # 16-bit cell width
-                             single_sided,
-                             0,         # 25ns capture
-                             csum & 0xffffffff)
-
-        # Concatenate it all together and send it back.
-        return header + trk_offs + trk_dat
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 25
scripts/greaseweazle/optimised/__init__.py

@@ -1,25 +0,0 @@
-# greaseweazle/optimised/__init__.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
-
-gw_opt = os.environ.get('GW_OPT')
-enabled = gw_opt is None or gw_opt.lower().startswith('y')
-if enabled:
-    try:
-        from .optimised import *
-    except ModuleNotFoundError:
-        enabled = False
-        print('*** WARNING: Optimised data routines not found: '
-              'Run scripts/setup.sh')
-else:
-    print('*** WARNING: Optimised data routines disabled (GW_OPT=%s)'
-          % gw_opt)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 0
scripts/greaseweazle/tools/__init__.py


+ 0 - 95
scripts/greaseweazle/tools/bandwidth.py

@@ -1,95 +0,0 @@
-# greaseweazle/tools/bandwidth.py
-#
-# Greaseweazle control script: Measure USB bandwidth.
-#
-# 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>.
-
-description = "Report the available USB bandwidth for the Greaseweazle device."
-
-import struct, sys
-
-from timeit import default_timer as timer
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def generate_random_buffer(nr, seed):
-    dat = bytearray()
-    r = seed
-    for i in range(nr):
-        dat.append(r&255)
-        if r & 1:
-            r = (r>>1) ^ 0x80000062
-        else:
-            r >>= 1
-    return dat
-
-def measure_bandwidth(usb, args):
-    print()
-    print("%19s%-7s/   %-7s/   %-7s" % ("", "Min.", "Mean", "Max."))
-
-    seed = 0x12345678
-    w_nr = r_nr = 1000000
-    buf = generate_random_buffer(w_nr, seed)
-
-    start = timer()
-    ack = usb.sink_bytes(buf, seed)
-    end = timer()
-    av_w_bw = (w_nr * 8) / ((end-start) * 1e6)
-    min_w_bw, max_w_bw = usb.bw_stats()
-    print("Write Bandwidth: %8.3f / %8.3f / %8.3f Mbps"
-          % (min_w_bw, av_w_bw, max_w_bw))
-    if ack != 0:
-        print("ERROR: USB write data garbled (Host -> Device)")
-        return
-    
-    start = timer()
-    sbuf = usb.source_bytes(r_nr, seed)
-    end = timer()
-    av_r_bw = (r_nr * 8) / ((end-start) * 1e6)
-    min_r_bw, max_r_bw = usb.bw_stats()
-    print("Read Bandwidth:  %8.3f / %8.3f / %8.3f Mbps"
-          % (min_r_bw, av_r_bw, max_r_bw))
-    if sbuf is not None and sbuf != buf:
-        print("ERROR: USB read data garbled (Device -> Host)")
-        return
-
-    est_min_bw = 0.9 * min(min_r_bw, min_w_bw)
-    print()
-    print("Estimated Consistent Min. Bandwidth: %.3f Mbps" % est_min_bw)
-    max_flux_rate = ((est_min_bw * 0.9) * 1e6) / 8
-    
-    twobyte_us = 249/72 # Smallest time requiring a 2-byte transmission code
-    req_min_bw = 16 / twobyte_us # Bandwidth (Mbps) to transmit above time
-    if req_min_bw > est_min_bw:
-        print(" -> **WARNING** BELOW REQUIRED MIN.: %.3f Mbps" % req_min_bw)
-    else:
-        print(" -> Max. Flux Rate: %.3f Msamples/sec"
-              % (max_flux_rate / 1e6))
-        print(" -> Min. Ave. Flux: %.3f us"
-              % (1e6 / max_flux_rate))
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        usb = util.usb_open(args.device)
-        measure_bandwidth(usb, args)
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 64
scripts/greaseweazle/tools/clean.py

@@ -1,64 +0,0 @@
-# greaseweazle/tools/clean.py
-#
-# Greaseweazle control script: Scrub drive heads with a cleaning disk.
-#
-# Uses a zig-zag pattern, after Dave Dunfield's ImageDisk and
-# Phil Pemberton's Magpie/DiscFerret.
-#
-# 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>.
-
-description = "Clean a drive in a zig-zag pattern using a cleaning disk."
-
-import sys, time
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def seek(cyl, usb, args):
-    c = min(cyl, args.cyls - 1)
-    print("%d " % c, end='', flush=True)
-    usb.seek(c, 0)
-
-def clean(usb, args):
-    step = max(args.cyls // 8, 2)
-    for p in range(args.passes):
-        print('Pass %d: ' % p, end='', flush=True)
-        for cyl in range(0, args.cyls, step):
-            seek(cyl + step - 1, usb, args)
-            time.sleep(args.linger / 1000)
-            seek(cyl, usb, args)
-            time.sleep(args.linger / 1000)
-        print()
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    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("--cyls", type=int, default=80,
-                        help="number of drive cylinders")
-    parser.add_argument("--passes", type=int, default=3,
-                        help="number of passes across the cleaning disk")
-    parser.add_argument("--linger", type=int, default=100,
-                        help="linger time per step, milliseconds")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        usb = util.usb_open(args.device)
-        util.with_drive_selected(clean, usb, args)
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 66
scripts/greaseweazle/tools/delays.py

@@ -1,66 +0,0 @@
-# greaseweazle/tools/delays.py
-#
-# Greaseweazle control script: Get/Set Delay Timers.
-#
-# 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>.
-
-description = "Display (and optionally modify) Greaseweazle \
-drive-delay parameters."
-
-import sys
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--select", type=int,
-                        help="delay after drive select (usecs)")
-    parser.add_argument("--step", type=int,
-                        help="delay between head steps (usecs)")
-    parser.add_argument("--settle", type=int,
-                        help="settle delay after seek (msecs)")
-    parser.add_argument("--motor", type=int,
-                        help="delay after motor on (msecs)")
-    parser.add_argument("--watchdog", type=int,
-                        help="quiescent time until drives reset (msecs)")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-
-        usb = util.usb_open(args.device)
-
-        if args.select:
-            usb.select_delay = args.select
-        if args.step:
-            usb.step_delay = args.step
-        if args.settle:
-            usb.seek_settle_delay = args.settle
-        if args.motor:
-            usb.motor_delay = args.motor
-        if args.watchdog:
-            usb.watchdog_delay = args.watchdog
-
-        print("Select Delay: %uus" % usb.select_delay)
-        print("Step Delay: %uus" % usb.step_delay)
-        print("Settle Time: %ums" % usb.seek_settle_delay)
-        print("Motor Delay: %ums" % usb.motor_delay)
-        print("Watchdog: %ums" % usb.watchdog_delay)
-
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 61
scripts/greaseweazle/tools/erase.py

@@ -1,61 +0,0 @@
-# greaseweazle/tools/erase.py
-#
-# Greaseweazle control script: Erase a Disk.
-#
-# 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>.
-
-description = "Erase a disk."
-
-import sys
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def erase(usb, args):
-
-    # @drive_ticks is the time in Greaseweazle ticks between index pulses.
-    # We will adjust the flux intervals per track to allow for this.
-    drive_ticks = usb.read_track(2).ticks_per_rev
-
-    for t in args.tracks:
-        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()
-
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    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,
-                        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:
-        print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 98
scripts/greaseweazle/tools/info.py

@@ -1,98 +0,0 @@
-# greaseweazle/tools/info.py
-#
-# Greaseweazle control script: Displat info about tools, firmware, and drive.
-#
-# 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>.
-
-description = "Display information about the Greaseweazle setup."
-
-import sys, serial
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-from greaseweazle import version
-
-model_id = { 1: { 0: 'F1',
-                  1: 'F1 Plus',
-                  2: 'F1 Plus (Unbuffered)' },
-             4: { 0: 'V4',
-                  1: 'V4 Slim' },
-             7: { 0: 'F7 v1',
-                  1: 'F7 Plus (Ant Goffart, v1)',
-                  2: 'F7 Lightning',
-                  3: 'F7 v2)',
-                  4: 'F7 Plus (Ant Goffart, v2)',
-                  5: 'F7 Lightning Plus',
-                  6: 'F7 Slim',
-                  7: 'F7 v3 "Thunderbolt"' } }
-
-speed_id = { 0: 'Full Speed (12 Mbit/s)',
-             1: 'High Speed (480 Mbit/s)' }
-
-def print_info_line(name, value, tab=0):
-    print(''.ljust(tab) + (name + ':').ljust(12-tab) + value)
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--bootloader", action="store_true",
-                        help="display bootloader info (F7 only)")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    print_info_line('Host Tools', 'v%d.%d' % (version.major, version.minor))
-
-    print('Greaseweazle:')
-
-    try:
-        usb = util.usb_open(args.device, mode_check=False)
-    except serial.SerialException:
-        print('  Not found')
-        sys.exit(0)
-
-    mode_switched = (usb.jumperless_update
-                     and usb.update_mode != args.bootloader
-                     and not (usb.update_mode and usb.update_jumpered))
-    if mode_switched:
-        usb = util.usb_reopen(usb, args.bootloader)
-        
-    port = usb.port_info
-
-    if port.device:
-        print_info_line('Device', port.device, tab=2)
-
-    try:
-        model = model_id[usb.hw_model][usb.hw_submodel]
-    except KeyError:
-        model = 'Unknown (0x%02X%02X)' % (usb.hw_model, usb.hw_submodel)
-    print_info_line('Model', model, tab=2)
-
-    fwver = 'v%d.%d' % (usb.major, usb.minor)
-    if usb.update_mode:
-        fwver += ' (Update Bootloader)'
-    print_info_line('Firmware', fwver, tab=2)
-
-    print_info_line('Serial', port.serial_number if port.serial_number
-                    else 'Unknown', tab=2)
-
-    try:
-        speed = speed_id[usb.usb_speed]
-    except KeyError:
-        speed = 'Unknown (0x%02X)' % usb.usb_speed
-    print_info_line('USB Rate', speed, tab=2)
-
-    if mode_switched:
-        usb = util.usb_reopen(usb, not args.bootloader)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 87
scripts/greaseweazle/tools/pin.py

@@ -1,87 +0,0 @@
-# greaseweazle/tools/pin.py
-#
-# Greaseweazle control script: Set a floppy interface pin to specified level.
-#
-# 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>.
-
-description = "Change the setting of a user-modifiable interface pin."
-
-import sys, argparse
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def level(letter):
-    levels = { 'H': True, 'L': False }
-    if not letter.upper() in levels:
-        raise argparse.ArgumentTypeError("invalid pin level: '%s'" % letter)
-    return levels[letter.upper()]
-
-def pin_set(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options] pin level')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("pin", type=int, help="pin number")
-    parser.add_argument("level", type=level, help="pin level (H,L)")
-    parser.description = description
-    parser.prog += ' pin set'
-    args = parser.parse_args(argv[3:])
-
-    try:
-        usb = util.usb_open(args.device)
-        usb.set_pin(args.pin, args.level)
-        print("Pin %u is set %s" %
-              (args.pin, ("Low (0v)", "High (5v)")[args.level]))
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-def _pin_get(usb, args, **_kwargs):
-    """Get the specified pin value.
-    """
-    value = usb.get_pin(args.pin)
-    print("Pin %u is %s" %
-          (args.pin, ("Low (0v)", "High (5v)")[value]))
-
-def pin_get(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options] pin')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--drive", type=util.drive_letter, default='A',
-                        help="drive to read (A,B,0,1,2)")
-    parser.add_argument("pin", type=int, help="pin number")
-    parser.description = description
-    parser.prog += ' pin get'
-    args = parser.parse_args(argv[3:])
-
-    try:
-        usb = util.usb_open(args.device)
-        util.with_drive_selected(_pin_get, usb, args, motor=False)
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-def usage(argv):
-    print("usage: gw pin get|set [-h] ...")
-    print("  get|set  Get or set a pin")
-    sys.exit(1)
-
-def main(argv):
-
-    if len(argv) < 3:
-        usage(argv)
-
-    if argv[2] == 'get':
-        pin_get(argv)
-    elif argv[2] == 'set':
-        pin_set(argv)
-    else:
-        usage(argv)
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 175
scripts/greaseweazle/tools/read.py

@@ -1,175 +0,0 @@
-# greaseweazle/tools/read.py
-#
-# Greaseweazle control script: Read Disk to Image.
-#
-# 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>.
-
-description = "Read a disk to the specified image file."
-
-import sys
-import importlib
-
-from greaseweazle.tools import util
-from greaseweazle import error
-from greaseweazle import usb as USB
-from greaseweazle.flux import Flux
-from greaseweazle.codec import formats
-
-
-def open_image(args, image_class):
-    image = image_class.to_file(args.file, args.fmt_cls)
-    if args.rate is not None:
-        image.bitrate = args.rate
-    for opt, val in args.file_opts.items():
-        error.check(hasattr(image, 'opts') and hasattr(image.opts, opt),
-                    "%s: Invalid file option: %s" % (args.file, opt))
-        setattr(image.opts, opt, val)
-    return image
-
-
-def read_and_normalise(usb, args, revs, ticks=0):
-    flux = usb.read_track(revs=revs, ticks=ticks)
-    if args.rpm is not None:
-        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, args.ticks)
-    if decoder is None:
-        return flux
-    dat = decoder(cyl, head, flux)
-    if dat.nr_missing() != 0:
-        for retry in range(args.retries):
-            print("T%u.%u: %s - Retrying (%d)"
-                  % (cyl, head, dat.summary_string(), retry+1))
-            flux = read_and_normalise(usb, args, max(args.revs, 3))
-            dat.decode_raw(flux)
-            if dat.nr_missing() == 0:
-                break
-    return dat
-
-
-def print_summary(args, summary):
-    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 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: " % (head, sec), end="")
-            for cyl in args.tracks.cyls:
-                s = summary[cyl,head]
-                if sec > s.nsec:
-                    print(" ", end="")
-                else:
-                    tot_sec += 1
-                    if s.has_sec(sec): good_sec += 1
-                    print("." if s.has_sec(sec) else "X", end="")
-            print()
-    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.
-    """
-
-    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:
-        cyl, head = t.cyl, t.head
-        usb.seek(t.physical_cyl, head)
-        dat = read_with_retry(usb, args, cyl, head, decoder)
-        s = "T%u.%u: %s" % (cyl, head, dat.summary_string())
-        if hasattr(dat, 'nr_missing') and dat.nr_missing() != 0:
-            s += " - Giving up"
-        print(s)
-        summary[cyl,head] = dat
-        image.emit_track(cyl, head, dat)
-
-    if decoder is not None:
-        print_summary(args, summary)
-
-
-def main(argv):
-
-    epilog = "FORMAT options:\n" + formats.print_formats()
-    parser = util.ArgumentParser(usage='%(prog)s [options] file',
-                                 epilog=epilog)
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--drive", type=util.drive_letter, default='A',
-                        help="drive to read (A,B,0,1,2)")
-    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,
-                        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")
-    parser.add_argument("--retries", type=int, default=3,
-                        help="number of retries on decode failure")
-    parser.add_argument("file", help="output filename")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    args.file, args.file_opts = util.split_opts(args.file)
-
-    try:
-        usb = util.usb_open(args.device)
-        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, def_tracks, args.fmt_cls = None, None, None
-        if args.format:
-            try:
-                args.fmt_cls = formats.formats[args.format]()
-            except KeyError as ex:
-                raise error.Fatal("""\
-Unknown format '%s'
-Known formats:\n%s"""
-                                  % (args.format, formats.print_formats()))
-            decoder = args.fmt_cls.decode_track
-            def_tracks = args.fmt_cls.default_tracks
-            if args.revs is None: args.revs = args.fmt_cls.default_revs
-        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=" % 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)
-    except USB.CmdError as err:
-        print("Command Failed: %s" % err)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 37
scripts/greaseweazle/tools/reset.py

@@ -1,37 +0,0 @@
-# greaseweazle/tools/reset.py
-#
-# Greaseweazle control script: Reset to power-on defaults.
-#
-# 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>.
-
-description = "Reset the Greaseweazle device to power-on default state."
-
-import sys
-
-from greaseweazle.tools import util
-from greaseweazle import usb as USB
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options]')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        usb = util.usb_open(args.device)
-        usb.power_on_reset()
-    except USB.CmdError as error:
-        print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 62
scripts/greaseweazle/tools/seek.py

@@ -1,62 +0,0 @@
-# greaseweazle/tools/seek.py
-#
-# Greaseweazle control script: Seek to specified cylinder.
-#
-# 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>.
-
-description = "Seek to the specified cylinder."
-
-import struct, sys
-
-from greaseweazle.tools import util
-from greaseweazle import error
-from greaseweazle import usb as USB
-from greaseweazle.flux import Flux
-
-
-def seek(usb, args, **_kwargs):
-    """Seeks to the cylinder specified in args.
-    """
-    usb.seek(args.cylinder, 0)
-
-
-def main(argv):
-
-    parser = util.ArgumentParser(usage='%(prog)s [options] cylinder')
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--drive", type=util.drive_letter, default='A',
-                        help="drive to read (A,B,0,1,2)")
-    parser.add_argument("--force", action="store_true",
-                        help="allow extreme cylinders with no prompt")
-    parser.add_argument("--motor-on", action="store_true",
-                        help="seek with drive motor activated")
-    parser.add_argument("cylinder", type=int, help="cylinder to seek")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        struct.pack('b', args.cylinder)
-    except struct.error:
-        raise error.Fatal("Cylinder %d out of range" % args.cylinder)
-    if not 0 <= args.cylinder <= 83 and not args.force:
-        answer = input("Seek to extreme cylinder %d, Yes/No? " % args.cylinder)
-        if answer != "Yes":
-            return
-    
-    try:
-        usb = util.usb_open(args.device)
-        util.with_drive_selected(seek, usb, args, motor=args.motor_on)
-    except USB.CmdError as err:
-        print("Command Failed: %s" % err)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 134
scripts/greaseweazle/tools/update.py

@@ -1,134 +0,0 @@
-# greaseweazle/tools/update.py
-#
-# Greaseweazle control script: Firmware Update.
-#
-# 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>.
-
-description = "Update the Greaseweazle device firmware to current version."
-
-import sys, serial, struct, os
-import crcmod.predefined
-
-from greaseweazle.tools import util
-from greaseweazle import version
-from greaseweazle import usb as USB
-
-# update_firmware:
-# Updates the Greaseweazle firmware using the specified Update File.
-def update_firmware(usb, args):
-
-    req_type = b'BL' if args.bootloader else b'GW'
-
-    filename = args.file
-    if filename is None:
-        # 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)
-        filename = os.path.join(path, "Greaseweazle-v%d.%d.upd"
-                                % (version.major, version.minor))
-    
-    # Read and verify the entire update catalogue.
-    with open(filename, "rb") as f:
-        dat = f.read()
-    if struct.unpack('4s', dat[:4])[0] != b'GWUP':
-        print('%s: Not a valid UPD file' % (filename))
-        return
-    crc32 = crcmod.predefined.Crc('crc-32-mpeg')
-    crc32.update(dat)
-    if crc32.crcValue != 0:
-        print('%s: UPD file is corrupt' % (filename))
-        return
-    dat = dat[4:-4]
-
-    # Search the catalogue for a match on our Weazle's hardware type.
-    while dat:
-        upd_len, hw_model = struct.unpack("<2H", dat[:4])
-        upd_type, major, minor = struct.unpack("2s2B", dat[upd_len-4:upd_len])
-        if ((hw_model, upd_type, major, minor)
-            == (usb.hw_model, req_type, version.major, version.minor)):
-            # Match: Pull out the embedded update file.
-            dat = dat[4:upd_len+4]
-            break
-        # Skip to the next catalogue entry.
-        dat = dat[upd_len+4:]
-
-    if not dat:
-        print("%s: F%u v%u.%u %s update not found"
-              % (filename, usb.hw_model,
-                 version.major, version.minor,
-                 'bootloader' if args.bootloader else 'firmware'))
-        return
-
-    # Check the matching update file's footer.
-    sig, maj, min, hw_model = struct.unpack("<2s2BH", dat[-8:-2])
-    if len(dat) & 3 != 0 or sig != req_type or hw_model != usb.hw_model:
-        print("%s: Bad update file" % (filename))
-        return
-    crc16 = crcmod.predefined.Crc('crc-ccitt-false')
-    crc16.update(dat)
-    if crc16.crcValue != 0:
-        print("%s: Bad CRC" % (filename))
-        return
-
-    # Perform the update.
-    print("Updating %s to v%u.%u..."
-          % ("Bootloader" if args.bootloader else "Main Firmware", maj, min))
-    if args.bootloader:
-        ack = usb.update_bootloader(dat)
-        if ack != 0:
-            print("""\
-** UPDATE FAILED: Please retry immediately or your Weazle may need
-        full reflashing via a suitable programming adapter!""")
-            return
-        print("Done.")
-    else:
-        ack = usb.update_firmware(dat)
-        if ack != 0:
-            print("** UPDATE FAILED: Please retry!")
-            return
-        print("Done.")
-    
-        if usb.jumperless_update:
-            util.usb_reopen(usb, is_update=False)
-        else:
-            print("** Disconnect Greaseweazle and remove the Update Jumper")
-
-
-def main(argv):
-
-    parser = util.ArgumentParser(allow_abbrev=False, usage='%(prog)s [options] [file]')
-    parser.add_argument("file", nargs="?", help="update filename")
-    parser.add_argument("--device", help="greaseweazle device name")
-    parser.add_argument("--bootloader", action="store_true",
-                        help="update the bootloader (use with caution!)")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        usb = util.usb_open(args.device, is_update=not args.bootloader)
-        update_firmware(usb, args)
-    except USB.CmdError as error:
-        if error.code == USB.Ack.OutOfSRAM and args.bootloader:
-            # Special warning for Low-Density F1 devices. The new bootloader
-            # cannot be fully buffered in the limited RAM available.
-            print("ERROR: Bootloader update unsupported on this device "
-                  "(insufficient SRAM)")
-        elif error.code == USB.Ack.OutOfFlash and not args.bootloader:
-            print("ERROR: New firmware is too large for this device "
-                  "(insufficient Flash memory)")
-        else:
-            print("Command Failed: %s" % error)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 347
scripts/greaseweazle/tools/util.py

@@ -1,347 +0,0 @@
-# greaseweazle/tools/util.py
-#
-# Greaseweazle control script: Utility functions.
-#
-# 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 argparse, os, sys, serial, struct, time, re, platform
-import importlib
-import serial.tools.list_ports
-from collections import OrderedDict
-
-from greaseweazle import version
-from greaseweazle import error
-from greaseweazle import usb as USB
-
-
-class CmdlineHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
-                           argparse.RawDescriptionHelpFormatter):
-    def _get_help_string(self, action):
-        help = action.help
-        if '%no_default' in help:
-            return help.replace('%no_default', '')
-        if ('%(default)' in help
-            or action.default is None
-            or action.default is False
-            or action.default is argparse.SUPPRESS):
-            return help
-        return help + ' (default: %(default)s)'
-
-
-class ArgumentParser(argparse.ArgumentParser):
-    def __init__(self, formatter_class=CmdlineHelpFormatter, *args, **kwargs):
-        return super().__init__(formatter_class=formatter_class,
-                                *args, **kwargs)
-
-def drive_letter(letter):
-    types = {
-        'A': (USB.BusType.IBMPC, 0),
-        'B': (USB.BusType.IBMPC, 1),
-        '0': (USB.BusType.Shugart, 0),
-        '1': (USB.BusType.Shugart, 1),
-        '2': (USB.BusType.Shugart, 2)
-    }
-    if not letter.upper() in types:
-        raise argparse.ArgumentTypeError("invalid drive letter: '%s'" % letter)
-    return types[letter.upper()]
-
-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 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.head, self.cyl = next(self.l)
-            return self
-    
-    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 re.match('h[01].off$', k):
-                h = int(re.match('h([01]).off$', k).group(1))
-                m = re.match('([+-][\d])$', v)
-                if m is None: raise ValueError()
-                self.h_off[h] = int(m.group(1))
-            elif k == 'step':
-                self.step = int(v)
-                if self.step <= 0: raise ValueError()
-            else:
-                raise ValueError()
-        
-    def __str__(self):
-        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%d.off=%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 split_opts(seq):
-    """Splits a name from its list of options."""
-    parts = seq.split('::')
-    name, opts = parts[0], dict()
-    for x in map(lambda x: x.split(':'), parts[1:]):
-        for y in x:
-            try:
-                opt, val = y.split('=')
-            except ValueError:
-                opt, val = y, True
-            if opt:
-                opts[opt] = val
-    return name, opts
-
-
-image_types = OrderedDict(
-    { '.adf': 'ADF',
-      '.ads': ('ADS','acorn'),
-      '.adm': ('ADM','acorn'),
-      '.adl': ('ADL','acorn'),
-      '.d81': 'D81',
-      '.dsd': ('DSD','acorn'),
-      '.dsk': 'EDSK',
-      '.hfe': 'HFE',
-      '.ima': 'IMG',
-      '.img': 'IMG',
-      '.ipf': 'IPF',
-      '.raw': 'KryoFlux',
-      '.scp': 'SCP',
-      '.ssd': ('SSD','acorn'),
-      '.st' : 'IMG' })
-
-def get_image_class(name):
-    if os.path.isdir(name):
-        typespec = 'KryoFlux'
-    else:
-        _, ext = os.path.splitext(name)
-        error.check(ext.lower() in image_types,
-                    """\
-%s: Unrecognised file suffix '%s'
-Known suffixes: %s"""
-                    % (name, ext, ', '.join(image_types)))
-        typespec = image_types[ext.lower()]
-    if isinstance(typespec, tuple):
-        typename, classname = typespec
-    else:
-        typename, classname = typespec, typespec.lower()
-    mod = importlib.import_module('greaseweazle.image.' + classname)
-    return mod.__dict__[typename]
-
-
-def with_drive_selected(fn, usb, args, *_args, **_kwargs):
-    usb.set_bus_type(args.drive[0])
-    try:
-        usb.drive_select(args.drive[1])
-        usb.drive_motor(args.drive[1], _kwargs.pop('motor', True))
-        fn(usb, args, *_args, **_kwargs)
-    except KeyboardInterrupt:
-        print()
-        usb.reset()
-        raise
-    finally:
-        usb.drive_motor(args.drive[1], False)
-        usb.drive_deselect()
-
-
-def valid_ser_id(ser_id):
-    return ser_id and ser_id.upper().startswith("GW")
-
-def score_port(x, old_port=None):
-    score = 0
-    if x.manufacturer == "Keir Fraser" and x.product == "Greaseweazle":
-        score = 20
-    elif x.vid == 0x1209 and x.pid == 0x4d69:
-        # Our very own properly-assigned PID. Guaranteed to be us.
-        score = 20
-    elif x.vid == 0x1209 and x.pid == 0x0001:
-        # Our old shared Test PID. It's not guaranteed to be us.
-        score = 10
-    if score > 0 and valid_ser_id(x.serial_number):
-        # A valid serial id is a good sign unless this is a reopen, and
-        # the serials don't match!
-        if not old_port or not valid_ser_id(old_port.serial_number):
-            score = 20
-        elif x.serial_number == old_port.serial_number:
-            score = 30
-        else:
-            score = 0
-    if old_port and old_port.location:
-        # If this is a reopen, location field must match. A match is not
-        # sufficient in itself however, as Windows may supply the same
-        # location for multiple USB ports (this may be an interaction with
-        # BitDefender). Hence we do not increase the port's score here.
-        if not x.location or x.location != old_port.location:
-            score = 0
-    return score
-
-def find_port(old_port=None):
-    best_score, best_port = 0, None
-    for x in serial.tools.list_ports.comports():
-        score = score_port(x, old_port)
-        if score > best_score:
-            best_score, best_port = score, x
-    if best_port:
-        return best_port.device
-    raise serial.SerialException('Cannot find the Greaseweazle device')
-
-def port_info(devname):
-    for x in serial.tools.list_ports.comports():
-        if x.device == devname:
-            return x
-    return None
-
-def usb_reopen(usb, is_update):
-    mode = { False: 1, True: 0 }
-    try:
-        usb.switch_fw_mode(mode[is_update])
-    except (serial.SerialException, struct.error):
-        # Mac and Linux raise SerialException ("... returned no data")
-        # Win10 pyserial returns a short read which fails struct.unpack
-        pass
-    usb.ser.close()
-    for i in range(10):
-        time.sleep(0.5)
-        try:
-            devicename = find_port(usb.port_info)
-            new_ser = serial.Serial(devicename)
-        except serial.SerialException:
-            # Device not found
-            pass
-        else:
-            new_usb = USB.Unit(new_ser)
-            new_usb.port_info = port_info(devicename)
-            new_usb.jumperless_update = usb.jumperless_update
-            return new_usb
-    raise serial.SerialException('Could not reopen port after mode switch')
-
-
-def print_update_instructions(usb):
-    print("To perform an Update:")
-    if not usb.jumperless_update:
-        print(" - Disconnect from USB")
-        print(" - Install the Update Jumper at pins %s"
-              % ("RXI-TXO" if usb.hw_model != 1 else "DCLK-GND"))
-        print(" - Reconnect to USB")
-    print(" - Run \"gw update\" to install firmware v%u.%u" %
-          (version.major, version.minor))
-
-def usb_open(devicename, is_update=False, mode_check=True):
-
-    if devicename is None:
-        devicename = find_port()
-    
-    usb = USB.Unit(serial.Serial(devicename))
-    usb.port_info = port_info(devicename)
-    is_win7 = (platform.system() == 'Windows' and platform.release() == '7')
-    usb.jumperless_update = ((usb.hw_model, usb.hw_submodel) != (1, 0)
-                             and not is_win7)
-
-    if not mode_check:
-        return usb
-
-    if usb.update_mode and not is_update:
-        if usb.jumperless_update and not usb.update_jumpered:
-            usb = usb_reopen(usb, is_update)
-            if not usb.update_mode:
-                return usb
-        print("ERROR: Greaseweazle is in Firmware Update Mode")
-        print(" - The only available action is \"gw update\"")
-        if usb.update_jumpered:
-            print(" - For normal operation disconnect from USB and remove "
-                  "the Update Jumper at pins %s"
-                  % ("RXI-TXO" if usb.hw_model != 1 else "DCLK-GND"))
-        else:
-            print(" - Main firmware is erased: You *must* perform an update!")
-        sys.exit(1)
-
-    if is_update and not usb.update_mode:
-        if usb.jumperless_update:
-            usb = usb_reopen(usb, is_update)
-            error.check(usb.update_mode, """\
-Greaseweazle F7 did not change to Firmware Update Mode as requested.
-If the problem persists, install the Update Jumper at pins RXI-TXO.""")
-            return usb
-        print("ERROR: Greaseweazle is not in Firmware Update Mode")
-        print_update_instructions(usb)
-        sys.exit(1)
-
-    if not usb.update_mode and usb.update_needed:
-        print("ERROR: Greaseweazle firmware v%u.%u is unsupported"
-              % (usb.major, usb.minor))
-        print_update_instructions(usb)
-        sys.exit(1)
-
-    return usb
-    
-
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 212
scripts/greaseweazle/tools/write.py

@@ -1,212 +0,0 @@
-# greaseweazle/tools/write.py
-#
-# Greaseweazle control script: Write Image to Disk.
-#
-# 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>.
-
-description = "Write a disk from the specified image file."
-
-import sys
-
-from greaseweazle.tools import util
-from greaseweazle import error, track
-from greaseweazle import usb as USB
-from greaseweazle.codec import formats
-
-# Read and parse the image file.
-def open_image(args, image_class):
-    try:
-        image = image_class.from_file(args.file)
-        args.raw_image_class = True
-    except TypeError:
-        image = image_class.from_file(args.file, args.fmt_cls)
-        args.raw_image_class = False
-    return image
-
-# write_from_image:
-# Writes the specified image file to floppy disk.
-def write_from_image(usb, args, image):
-
-    # Measure drive RPM.
-    # We will adjust the flux intervals per track to allow for this.
-    drive = usb.read_track(2)
-    del drive.list
-
-    verified_count, not_verified_count = 0, 0
-
-    for t in args.tracks:
-
-        cyl, head = t.cyl, t.head
-
-        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, head),
-              end="", flush=True)
-        usb.seek(t.physical_cyl, head)
-
-        if track is None:
-            usb.erase_track(drive.ticks_per_rev * 1.1)
-            continue
-
-        if args.raw_image_class and args.fmt_cls is not None:
-            track = args.fmt_cls.decode_track(cyl, head, track).raw_track()
-
-        if args.precomp is not None:
-            track.precomp = args.precomp.track_precomp(cyl)
-        flux = track.flux_for_writeout()
-
-        # @factor adjusts flux times for speed variations between the
-        # read-in and write-out drives.
-        factor = drive.ticks_per_rev / flux.index_list[0]
-
-        # Convert the flux samples to Greaseweazle sample frequency.
-        rem = 0.0
-        flux_list = []
-        for x in flux.list:
-            y = x * factor + rem
-            val = round(y)
-            rem = y - val
-            flux_list.append(val)
-
-        # Encode the flux times for Greaseweazle, and write them out.
-        verified = False
-        for retry in range(args.retries+1):
-            if retry != 0:
-                print("T%u.%u: Verify Failure - Retry (%d)"
-                      % (cyl, head, retry))
-            usb.write_track(flux_list = flux_list,
-                            cue_at_index = flux.index_cued,
-                            terminate_at_index = flux.terminate_at_index)
-            try:
-                no_verify = args.no_verify or track.verify is None
-            except AttributeError: # track.verify undefined
-                no_verify = True
-            if no_verify:
-                not_verified_count += 1
-                verified = True
-                break
-            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
-                break
-            if retry == 0:
-                print()
-        error.check(verified, "Failed to verify Track %u.%u" % (cyl, head))
-
-    print()
-    if not_verified_count == 0:
-        print("All tracks verified")
-    else:
-        if verified_count == 0:
-            s = "No tracks verified "
-        else:
-            s = ("%d tracks verified; %d tracks *not* verified "
-                 % (verified_count, not_verified_count))
-        s += ("(Reason: Verify %s)"
-              % ("unavailable", "disabled")[args.no_verify])
-        print(s)
-
-
-class PrecompSpec:
-    def __str__(self):
-        s = "Precomp %s" % track.Precomp.TYPESTRING[self.type]
-        for e in self.list:
-            s += ", %d-:%dns" % e
-        return s
-
-    def track_precomp(self, cyl):
-        for c,s in reversed(self.list):
-            if cyl >= c:
-                return track.Precomp(self.type, s)
-        return None
-
-    def importspec(self, spec):
-        self.list = []
-        self.type = track.Precomp.MFM
-        for x in spec.split(':'):
-            k,v = x.split('=')
-            if k == 'type':
-                self.type = track.Precomp.TYPESTRING.index(v.upper())
-            else:
-                self.list.append((int(k), int(v)))
-        self.list.sort()
-
-    def __init__(self, spec):
-        try:
-            self.importspec(spec)
-        except:
-            raise ValueError
-        
-
-def main(argv):
-
-    epilog = "FORMAT options:\n" + formats.print_formats()
-    parser = util.ArgumentParser(usage='%(prog)s [options] file',
-                                 epilog=epilog)
-    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("--format", help="disk format")
-    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",
-                        help="disable verify")
-    parser.add_argument("--retries", type=int, default=3,
-                        help="number of retries on verify failure")
-    parser.add_argument("--precomp", type=PrecompSpec,
-                        help="write precompensation")
-    parser.add_argument("file", help="input filename")
-    parser.description = description
-    parser.prog += ' ' + argv[1]
-    args = parser.parse_args(argv[2:])
-
-    try:
-        image_class = util.get_image_class(args.file)
-        if not args.format and hasattr(image_class, 'default_format'):
-            args.format = image_class.default_format
-        def_tracks, args.fmt_cls = None, None
-        if args.format:
-            try:
-                args.fmt_cls = formats.formats[args.format]()
-            except KeyError as ex:
-                raise error.Fatal("""\
-Unknown format '%s'
-Known formats:\n%s"""
-                                  % (args.format, formats.print_formats()))
-            def_tracks = args.fmt_cls.default_tracks
-        if def_tracks is None:
-            def_tracks = util.TrackSet('c=0-81:h=0-1')
-        if args.tracks is not None:
-            def_tracks.update_from_trackspec(args.tracks.trackspec)
-        args.tracks = def_tracks
-        usb = util.usb_open(args.device)
-        image = open_image(args, image_class)
-        s = str(args.tracks)
-        if args.precomp is not None:
-            s += "; %s" % args.precomp
-        print("Writing %s" % s)
-        util.with_drive_selected(write_from_image, usb, args, image)
-    except USB.CmdError as err:
-        print("Command Failed: %s" % err)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 331
scripts/greaseweazle/track.py

@@ -1,331 +0,0 @@
-# greaseweazle/track.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 binascii
-import itertools as it
-from bitarray import bitarray
-from greaseweazle.flux import Flux, WriteoutFlux
-from greaseweazle import optimised
-
-# Precompensation to apply to a MasterTrack for writeout.
-class Precomp:
-    MFM = 0
-    FM  = 1
-    GCR = 2
-    TYPESTRING = [ 'MFM', 'FM', 'GCR' ]
-    def __str__(self):
-        return "Precomp: %s, %dns" % (Precomp.TYPESTRING[self.type], self.ns)
-    def __init__(self, type, ns):
-        self.type = type
-        self.ns = ns
-    def apply(self, bits, bit_ticks, scale):
-        t = self.ns * scale
-        if self.type == Precomp.MFM:
-            for i in bits.itersearch(bitarray('10100', endian='big')):
-                bit_ticks[i+2] -= t
-                bit_ticks[i+3] += t
-            for i in bits.itersearch(bitarray('00101', endian='big')):
-                bit_ticks[i+2] += t
-                bit_ticks[i+3] -= t
-        # This is primarily for GCR and FM which permit adjacent 1s (and
-        # have correspondingly slower bit times). However it may be useful
-        # for illegal MFM sequences too, especially on Amiga (custom syncwords,
-        # 4us-bitcell tracks). Normal MFM should not trigger these patterns.
-        for i in bits.itersearch(bitarray('110', endian='big')):
-            bit_ticks[i+1] -= t
-            bit_ticks[i+2] += t
-        for i in bits.itersearch(bitarray('011', endian='big')):
-            bit_ticks[i+1] += t
-            bit_ticks[i+2] -= t
-
-
-# A pristine representation of a track, from a codec and/or a perfect image.
-class MasterTrack:
-
-    @property
-    def bitrate(self):
-        return len(self.bits) / self.time_per_rev
-
-    # bits: Track bitcell data, aligned to the write splice (bitarray or bytes)
-    # time_per_rev: Time per revolution, in seconds (float)
-    # bit_ticks: Per-bitcell time values, in unitless 'ticks'
-    # splice: Location of the track splice, in bitcells, after the index
-    # weak: List of (start, length) weak ranges
-    def __init__(self, bits, time_per_rev, bit_ticks=None, splice=0, weak=[]):
-        if isinstance(bits, bytes):
-            self.bits = bitarray(endian='big')
-            self.bits.frombytes(bits)
-        else:
-            self.bits = bits
-        self.time_per_rev = time_per_rev
-        self.bit_ticks = bit_ticks
-        self.splice = splice
-        self.weak = weak
-        self.precomp = None
-        self.force_random_weak = True
-
-    def __str__(self):
-        s = "\nMaster Track: splice @ %d\n" % self.splice
-        s += (" %d bits, %.1f kbit/s"
-              % (len(self.bits), self.bitrate))
-        if self.bit_ticks:
-            s += " (variable)"
-        s += ("\n %.1f ms / rev (%.1f rpm)"
-              % (self.time_per_rev * 1000, 60 / self.time_per_rev))
-        if len(self.weak) > 0:
-            s += "\n %d weak range" % len(self.weak)
-            if len(self.weak) > 1: s += "s"
-            s += ": " + ", ".join(str(n) for _,n in self.weak) + " bits"
-        #s += str(binascii.hexlify(self.bits.tobytes()))
-        return s
-
-    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, cue_at_index=True):
-
-        # We're going to mess with the track data, so take a copy.
-        bits = self.bits.copy()
-        bitlen = len(bits)
-
-        # Also copy the bit_ticks array (or create a dummy one), and remember
-        # the total ticks that it contains.
-        bit_ticks = self.bit_ticks.copy() if self.bit_ticks else [1] * bitlen
-        ticks_to_index = sum(bit_ticks)
-
-        # Weak regions need special processing for correct flux representation.
-        for s,n in self.weak:
-            e = s + n
-            assert 0 < s < e < bitlen
-            pattern = bitarray(endian="big")
-            if n < 400 or self.force_random_weak:
-                # Short weak regions are written with no flux transitions.
-                # Actually we insert a flux transition every 32 bitcells, else
-                # we risk triggering Greaseweazle's No Flux Area generator.
-                pattern.frombytes(b"\x80\x00\x00\x00")
-                bits[s:e] = (pattern * (n//32+1))[:n]
-            else:
-                # Long weak regions we present a fuzzy clock bit in an
-                # otherwise normal byte (16 bits MFM). The byte may be
-                # interpreted as
-                # MFM 0001001010100101 = 12A5 = byte 0x43, or
-                # MFM 0001001010010101 = 1295 = byte 0x47
-                pattern.frombytes(b"\x12\xA5")
-                bits[s:e] = (pattern * (n//16+1))[:n]
-                for i in range(0, n-10, 16):
-                    x, y = bit_ticks[s+i+10], bit_ticks[s+i+11]
-                    bit_ticks[s+i+10], bit_ticks[s+i+11] = x+y*0.5, y*0.5
-            # To prevent corrupting a preceding sync word by effectively
-            # starting the weak region early, we start with a 1 if we just
-            # clocked out a 0.
-            bits[s] = not bits[s-1]
-            # Similarly modify the last bit of the weak region.
-            bits[e-1] = not(bits[e-2] or bits[e])
-
-        if cue_at_index or not for_writeout:
-            # 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 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.
-            pos = (self.splice - 4) % bitlen
-            # We stretch by 10 percent, which is way more than enough.
-            rep = bitlen // (10 * 32)
-            bit_ticks = bit_ticks[:pos] + bit_ticks[pos-32:pos] * rep
-            bits = bits[:pos] + bits[pos-32:pos] * rep
-        else:
-            # Splice is not at the index. We will write more than one
-            # revolution, and terminate the second revolution at the splice.
-            # For the first revolution we repeat the track header *backwards*
-            # to the very start of the write. This is in case the drive motor
-            # spins slower than expected and the write ends before the original
-            # splice position.
-            # Thus if the drive spins slow, the track gets a longer header.
-            bit_ticks += bit_ticks[:self.splice-4]
-            bits += bits[:self.splice-4]
-            pos = self.splice+4
-            fill_pattern = bits[pos:pos+32]
-            while pos >= 32:
-                pos -= 32
-                bits[pos:pos+32] = fill_pattern
-
-        if for_writeout and self.precomp is not None:
-            self.precomp.apply(bits, bit_ticks,
-                               ticks_to_index / (self.time_per_rev*1e9))
-
-        # Convert the stretched track data into flux.
-        bit_ticks_i = iter(bit_ticks)
-        flux_list = []
-        flux_ticks = 0
-        for bit in bits:
-            flux_ticks += next(bit_ticks_i)
-            if bit:
-                flux_list.append(flux_ticks)
-                flux_ticks = 0
-        if flux_ticks and for_writeout:
-            flux_list.append(flux_ticks)
-
-        # Package up the flux for return.
-        if for_writeout:
-            flux = WriteoutFlux(ticks_to_index, flux_list,
-                                ticks_to_index / self.time_per_rev,
-                                index_cued = cue_at_index,
-                                terminate_at_index = splice_at_index)
-        else:
-            flux = Flux([ticks_to_index]*2, flux_list*2,
-                        ticks_to_index / self.time_per_rev,
-                        index_cued = True)
-            flux.splice = sum(bit_ticks[:self.splice])
-
-        return flux
-
-
-# Track data generated from flux.
-class RawTrack:
-
-    def __init__(self, clock, data):
-        self.clock = clock
-        self.clock_max_adj = 0.10
-        self.pll_period_adj = 0.05
-        self.pll_phase_adj = 0.60
-        self.bitarray = bitarray(endian='big')
-        self.timearray = []
-        self.revolutions = []
-        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 (%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]
-
-
-    def get_revolution(self, nr):
-        start = sum(self.revolutions[:nr])
-        end = start + self.revolutions[nr]
-        return self.bitarray[start:end], self.timearray[start:end]
-
-
-    def get_all_data(self):
-        return self.bitarray, self.timearray
-
-
-    def import_flux_data(self, data):
-
-        flux = data.flux()
-        freq = flux.sample_freq
-
-        clock = self.clock
-        clock_min = self.clock * (1 - self.clock_max_adj)
-        clock_max = self.clock * (1 + self.clock_max_adj)
-
-        index_iter = it.chain(iter(map(lambda x: x/freq, flux.index_list)),
-                              [float('inf')])
-
-        # Make sure there's enough time in the flux list to cover all
-        # revolutions by appending a "large enough" final flux value.
-        tail = max(0, sum(flux.index_list) - sum(flux.list) + clock*freq*2)
-        flux_iter = it.chain(flux.list, [tail])
-
-        try:
-            optimised.flux_to_bitcells(
-                self.bitarray, self.timearray, self.revolutions,
-                index_iter, flux_iter,
-                freq, clock, clock_min, clock_max,
-                self.pll_period_adj, self.pll_phase_adj)
-        except AttributeError:
-            flux_to_bitcells(
-                self.bitarray, self.timearray, self.revolutions,
-                index_iter, flux_iter,
-                freq, clock, clock_min, clock_max,
-                self.pll_period_adj, self.pll_phase_adj)
-
-            
-def flux_to_bitcells(bit_array, time_array, revolutions,
-                     index_iter, flux_iter,
-                     freq, clock_centre, clock_min, clock_max,
-                     pll_period_adj, pll_phase_adj):
-
-    nbits = 0
-    ticks = 0.0
-    clock = clock_centre
-    to_index = next(index_iter)
-
-    for x in flux_iter:
-
-        # Gather enough ticks to generate at least one bitcell.
-        ticks += x / freq
-        if ticks < clock/2:
-            continue
-
-        # Clock out zero or more 0s, followed by a 1.
-        zeros = 0
-        while True:
-
-            # Check if we cross the index mark.
-            to_index -= clock
-            if to_index < 0:
-                revolutions.append(nbits)
-                nbits = 0
-                to_index += next(index_iter)
-
-            nbits += 1
-            ticks -= clock
-            time_array.append(clock)
-            if ticks >= clock/2:
-                zeros += 1
-                bit_array.append(False)
-            else:
-                bit_array.append(True)
-                break
-
-        # PLL: Adjust clock frequency according to phase mismatch.
-        if zeros <= 3:
-            # In sync: adjust clock by a fraction of the phase mismatch.
-            clock += ticks * pll_period_adj
-        else:
-            # Out of sync: adjust clock towards centre.
-            clock += (clock_centre - clock) * pll_period_adj
-        # Clamp the clock's adjustment range.
-        clock = min(max(clock, clock_min), clock_max)
-        # PLL: Adjust clock phase according to mismatch.
-        new_ticks = ticks * (1 - pll_phase_adj)
-        time_array[-1] += ticks - new_ticks
-        ticks = new_ticks
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 565
scripts/greaseweazle/usb.py

@@ -1,565 +0,0 @@
-# greaseweazle/usb.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
-import itertools as it
-from greaseweazle import version
-from greaseweazle import error
-from greaseweazle.flux import Flux
-from greaseweazle import optimised
-
-EARLIEST_SUPPORTED_FIRMWARE = (0, 25)
-
-## Control-Path command set
-class ControlCmd:
-    ClearComms      = 10000
-    Normal          =  9600
-
-
-## Command set
-class Cmd:
-    GetInfo         =  0
-    Update          =  1
-    Seek            =  2
-    Head            =  3
-    SetParams       =  4
-    GetParams       =  5
-    Motor           =  6
-    ReadFlux        =  7
-    WriteFlux       =  8
-    GetFluxStatus   =  9
-    GetIndexTimes   = 10
-    SwitchFwMode    = 11
-    Select          = 12
-    Deselect        = 13
-    SetBusType      = 14
-    SetPin          = 15
-    Reset           = 16
-    EraseFlux       = 17
-    SourceBytes     = 18
-    SinkBytes       = 19
-    GetPin          = 20
-    TestMode        = 21
-    str = {
-        GetInfo: "GetInfo",
-        Update: "Update",
-        Seek: "Seek",
-        Head: "Head",
-        SetParams: "SetParams",
-        GetParams: "GetParams",
-        Motor: "Motor",
-        ReadFlux: "ReadFlux",
-        WriteFlux: "WriteFlux",
-        GetFluxStatus: "GetFluxStatus",
-        GetIndexTimes: "GetIndexTimes",
-        SwitchFwMode: "SwitchFwMode",
-        Select: "Select",
-        Deselect: "Deselect",
-        SetBusType: "SetBusType",
-        SetPin: "SetPin",
-        Reset: "Reset",
-        EraseFlux: "EraseFlux",
-        SourceBytes: "SourceBytes",
-        SinkBytes: "SinkBytes",
-        GetPin: "GetPin",
-        TestMode: "TestMode"
-    }
-
-
-## Command responses/acknowledgements
-class Ack:
-    Okay            =  0
-    BadCommand      =  1
-    NoIndex         =  2
-    NoTrk0          =  3
-    FluxOverflow    =  4
-    FluxUnderflow   =  5
-    Wrprot          =  6
-    NoUnit          =  7
-    NoBus           =  8
-    BadUnit         =  9
-    BadPin          = 10
-    BadCylinder     = 11
-    OutOfSRAM       = 12
-    OutOfFlash      = 13
-    str = {
-        Okay: "Okay",
-        BadCommand: "Bad Command",
-        NoIndex: "No Index",
-        NoTrk0: "Track 0 not found",
-        FluxOverflow: "Flux Overflow",
-        FluxUnderflow: "Flux Underflow",
-        Wrprot: "Disk is Write Protected",
-        NoUnit: "No drive unit selected",
-        NoBus: "No bus type (eg. Shugart, IBM/PC) specified",
-        BadUnit: "Invalid unit number",
-        BadPin: "Invalid pin",
-        BadCylinder: "Invalid cylinder",
-        OutOfSRAM: "Out of SRAM",
-        OutOfFlash: "Out of Flash"
-    }
-
-
-
-## Cmd.GetInfo indexes
-class GetInfo:
-    Firmware        = 0
-    BandwidthStats  = 1
-
-
-## Cmd.{Get,Set}Params indexes
-class Params:
-    Delays          = 0
-
-
-## Cmd.SetBusType values
-class BusType:
-    Invalid         = 0
-    IBMPC           = 1
-    Shugart         = 2
-
-
-## Flux read stream opcodes, preceded by 0xFF byte
-class FluxOp:
-    Index           = 1
-    Space           = 2
-    Astable         = 3
-
-
-## CmdError: Encapsulates a command acknowledgement.
-class CmdError(Exception):
-
-    def __init__(self, cmd, code):
-        self.cmd = cmd
-        self.code = code
-
-    def cmd_str(self):
-        return Cmd.str.get(self.cmd[0], "UnknownCmd")
-        
-    def errcode_str(self):
-        if self.code == Ack.BadCylinder:
-            s = Ack.str[Ack.BadCylinder]
-            return s + " %d" % struct.unpack('2Bb', self.cmd)[2]
-        return Ack.str.get(self.code, "Unknown Error (%u)" % self.code)
-
-    def __str__(self):
-        return "%s: %s" % (self.cmd_str(), self.errcode_str())
-
-
-class Unit:
-
-    ## Unit information, instance variables:
-    ##  major, minor: Greaseweazle firmware version number
-    ##  max_cmd:      Maximum Cmd number accepted by this unit
-    ##  sample_freq:  Resolution of all time values passed to/from this unit
-    ##  update_mode:  True iff the Greaseweazle unit is in update mode
-
-    ## Unit(ser):
-    ## Accepts a Pyserial instance for Greaseweazle communications.
-    def __init__(self, ser):
-        self.ser = ser
-        self.reset()
-        # Copy firmware info to instance variables (see above for definitions).
-        self._send_cmd(struct.pack("3B", Cmd.GetInfo, 3, GetInfo.Firmware))
-        x = struct.unpack("<4BI3B21x", self.ser.read(32))
-        (self.major, self.minor, is_main_firmware,
-         self.max_cmd, self.sample_freq, self.hw_model,
-         self.hw_submodel, self.usb_speed) = x
-        self.version = (self.major, self.minor)
-        # Old firmware doesn't report HW type but runs on STM32F1 only.
-        if self.hw_model == 0:
-            self.hw_model = 1
-        # Check whether firmware is in update mode: limited command set if so.
-        self.update_mode = (is_main_firmware == 0)
-        if self.update_mode:
-            self.update_jumpered = (self.sample_freq & 1)
-            del self.sample_freq
-            return
-        # We are running main firmware: Check whether an update is needed.
-        # We can use only the GetInfo command if the firmware is out of date.
-        self.update_needed = (self.version < EARLIEST_SUPPORTED_FIRMWARE or
-                              self.version > (version.major, version.minor))
-        if self.update_needed:
-            return
-        # Initialise the delay properties with current firmware values.
-        self._send_cmd(struct.pack("4B", Cmd.GetParams, 4, Params.Delays, 10))
-        (self._select_delay, self._step_delay,
-         self._seek_settle_delay, self._motor_delay,
-         self._watchdog_delay) = struct.unpack("<5H", self.ser.read(10))
-
-
-    ## reset:
-    ## Resets communications with Greaseweazle.
-    def reset(self):
-        self.ser.reset_output_buffer()
-        self.ser.baudrate = ControlCmd.ClearComms
-        self.ser.baudrate = ControlCmd.Normal
-        self.ser.reset_input_buffer()
-        self.ser.close()
-        self.ser.open()
-
-
-    ## _send_cmd:
-    ## Send given command byte sequence to Greaseweazle.
-    ## Raise a CmdError if command fails.
-    def _send_cmd(self, cmd):
-        self.ser.write(cmd)
-        (c,r) = struct.unpack("2B", self.ser.read(2))
-        error.check(c == cmd[0], "Command returned garbage (%02x != %02x)"
-                    % (c, cmd[0]))
-        if r != 0:
-            raise CmdError(cmd, r)
-
-
-    ## seek:
-    ## Seek the selected drive's heads to the specified track (cyl, head).
-    def seek(self, cyl, head):
-        self._send_cmd(struct.pack("2Bb", Cmd.Seek, 3, cyl))
-        self._send_cmd(struct.pack("3B", Cmd.Head, 3, head))
-
-
-    ## set_bus_type:
-    ## Set the floppy bus type.
-    def set_bus_type(self, type):
-        self._send_cmd(struct.pack("3B", Cmd.SetBusType, 3, type))
-
-
-    ## set_pin:
-    ## Set a pin level.
-    def set_pin(self, pin, level):
-        self._send_cmd(struct.pack("4B", Cmd.SetPin, 4, pin, int(level)))
-
-
-    ## get_pin:
-    ## Get a pin level.
-    def get_pin(self, pin):
-        self._send_cmd(struct.pack("3B", Cmd.GetPin, 3, pin))
-        v, = struct.unpack("B", self.ser.read(1))
-        return v
-
-
-    ## power_on_reset:
-    ## Re-initialise to power-on defaults.
-    def power_on_reset(self):
-        self._send_cmd(struct.pack("2B", Cmd.Reset, 2))
-
-
-    ## drive_select:
-    ## Select the specified drive unit.
-    def drive_select(self, unit):
-        self._send_cmd(struct.pack("3B", Cmd.Select, 3, unit))
-
-
-    ## drive_deselect:
-    ## Deselect currently-selected drive unit (if any).
-    def drive_deselect(self):
-        self._send_cmd(struct.pack("2B", Cmd.Deselect, 2))
-
-
-    ## drive_motor:
-    ## Turn the specified drive's motor on/off.
-    def drive_motor(self, unit, state):
-        self._send_cmd(struct.pack("4B", Cmd.Motor, 4, unit, int(state)))
-
-
-    ## switch_fw_mode:
-    ## Switch between update bootloader and main firmware.
-    def switch_fw_mode(self, mode):
-        self._send_cmd(struct.pack("3B", Cmd.SwitchFwMode, 3, int(mode)))
-
-
-    ## update_firmware:
-    ## Update Greaseweazle to the given new firmware.
-    def update_firmware(self, dat):
-        self._send_cmd(struct.pack("<2BI", Cmd.Update, 6, len(dat)))
-        self.ser.write(dat)
-        (ack,) = struct.unpack("B", self.ser.read(1))
-        return ack
-
-
-    ## update_bootloader:
-    ## Update Greaseweazle with the given new bootloader.
-    def update_bootloader(self, dat):
-        self._send_cmd(struct.pack("<2B2I", Cmd.Update, 10,
-                                   len(dat), 0xdeafbee3))
-        self.ser.write(dat)
-        (ack,) = struct.unpack("B", self.ser.read(1))
-        return ack
-
-
-    ## _decode_flux:
-    ## Decode the Greaseweazle data stream into a list of flux samples.
-    def _decode_flux(self, dat):
-        flux, index = [], []
-        assert dat[-1] == 0
-        dat_i = it.islice(dat, 0, len(dat)-1)
-        ticks, ticks_since_index = 0, 0
-        def _read_28bit():
-            val =  (next(dat_i) & 254) >>  1
-            val += (next(dat_i) & 254) <<  6
-            val += (next(dat_i) & 254) << 13
-            val += (next(dat_i) & 254) << 20
-            return val
-        try:
-            while True:
-                i = next(dat_i)
-                if i == 255:
-                    opcode = next(dat_i)
-                    if opcode == FluxOp.Index:
-                        val = _read_28bit()
-                        index.append(ticks_since_index + ticks + val)
-                        ticks_since_index = -(ticks + val)
-                    elif opcode == FluxOp.Space:
-                        ticks += _read_28bit()
-                    else:
-                        raise error.Fatal("Bad opcode in flux stream (%d)"
-                                          % opcode)
-                else:
-                    if i < 250:
-                        val = i
-                    else:
-                        val = 250 + (i - 250) * 255
-                        val += next(dat_i) - 1
-                    ticks += val
-                    flux.append(ticks)
-                    ticks_since_index += ticks
-                    ticks = 0
-        except StopIteration:
-            pass
-        return flux, index
-
-
-    ## _encode_flux:
-    ## Convert the given flux timings into an encoded data stream.
-    def _encode_flux(self, flux):
-        nfa_thresh = round(150e-6 * self.sample_freq)  # 150us
-        nfa_period = round(1.25e-6 * self.sample_freq) # 1.25us
-        dat = bytearray()
-        def _write_28bit(x):
-            dat.append(1 | (x<<1) & 255)
-            dat.append(1 | (x>>6) & 255)
-            dat.append(1 | (x>>13) & 255)
-            dat.append(1 | (x>>20) & 255)
-        # Emit a dummy final flux value. This is never written to disk because
-        # the write is aborted immediately the final flux is loaded into the
-        # WDATA timer. The dummy flux is sacrificial, ensuring that the real
-        # final flux gets written in full.
-        dummy_flux = round(100e-6 * self.sample_freq)
-        for val in it.chain(flux, [dummy_flux]):
-            if val == 0:
-                pass
-            elif val < 250:
-                dat.append(val)
-            elif val > nfa_thresh:
-                dat.append(255)
-                dat.append(FluxOp.Space)
-                _write_28bit(val)
-                dat.append(255)
-                dat.append(FluxOp.Astable)
-                _write_28bit(nfa_period)
-            else:
-                high = (val-250) // 255
-                if high < 5:
-                    dat.append(250 + high)
-                    dat.append(1 + (val-250) % 255)
-                else:
-                    dat.append(255)
-                    dat.append(FluxOp.Space)
-                    _write_28bit(val - 249)
-                    dat.append(249)
-        dat.append(0) # End of Stream
-        return dat
-
-
-    ## _read_track:
-    ## Private helper which issues command requests to Greaseweazle.
-    def _read_track(self, revs, ticks):
-
-        # Request and read all flux timings for this track.
-        dat = bytearray()
-        self._send_cmd(struct.pack("<2BIH", Cmd.ReadFlux, 8,
-                                   ticks, revs+1))
-        while True:
-            dat += self.ser.read(1)
-            dat += self.ser.read(self.ser.in_waiting)
-            if dat[-1] == 0:
-                break
-
-        # Check flux status. An exception is raised if there was an error.
-        self._send_cmd(struct.pack("2B", Cmd.GetFluxStatus, 2))
-
-        return dat
-
-
-    ## read_track:
-    ## Read and decode flux and index timings for the current track.
-    def read_track(self, revs, ticks=0, nr_retries=5):
-
-        retry = 0
-        while True:
-            try:
-                dat = self._read_track(revs, ticks)
-            except CmdError as error:
-                # An error occurred. We may retry on transient overflows.
-                if error.code == Ack.FluxOverflow and retry < nr_retries:
-                    retry += 1
-                else:
-                    raise error
-            else:
-                # Success!
-                break
-
-        try:
-            # Decode the flux list and read the index-times list.
-            flux_list, index_list = optimised.decode_flux(dat)
-        except AttributeError:
-            flux_list, index_list = self._decode_flux(dat)
-
-        # Success: Return the requested full index-to-index revolutions.
-        return Flux(index_list, flux_list, self.sample_freq, index_cued=False)
-
-
-    ## write_track:
-    ## Write the given flux stream to the current track via Greaseweazle.
-    def write_track(self, flux_list, terminate_at_index,
-                    cue_at_index=True, nr_retries=5):
-
-        # Create encoded data stream.
-        dat = self._encode_flux(flux_list)
-        
-        retry = 0
-        while True:
-            try:
-                # Write the flux stream to the track via Greaseweazle.
-                self._send_cmd(struct.pack("4B", Cmd.WriteFlux, 4,
-                                           int(cue_at_index),
-                                           int(terminate_at_index)))
-                self.ser.write(dat)
-                self.ser.read(1) # Sync with Greaseweazle
-                self._send_cmd(struct.pack("2B", Cmd.GetFluxStatus, 2))
-            except CmdError as error:
-                # An error occurred. We may retry on transient underflows.
-                if error.code == Ack.FluxUnderflow and retry < nr_retries:
-                    retry += 1
-                else:
-                    raise error
-            else:
-                # Success!
-                break
-
-
-    ## erase_track:
-    ## Erase the current track via Greaseweazle.
-    def erase_track(self, ticks):
-        self._send_cmd(struct.pack("<2BI", Cmd.EraseFlux, 6, int(ticks)))
-        self.ser.read(1) # Sync with Greaseweazle
-        self._send_cmd(struct.pack("2B", Cmd.GetFluxStatus, 2))
-
-
-    ## source_bytes:
-    ## Command Greaseweazle to source 'nr' garbage bytes.
-    def source_bytes(self, nr, seed):
-        try:
-            self._send_cmd(struct.pack("<2B2I", Cmd.SourceBytes, 10, nr, seed))
-            dat = self.ser.read(nr)
-        except CmdError as error:
-            if error.code != Ack.BadCommand:
-                raise
-            # Firmware v0.28 and earlier
-            self._send_cmd(struct.pack("<2BI", Cmd.SourceBytes, 6, nr))
-            self.ser.read(nr)
-            dat = None
-        return dat
-
-    ## sink_bytes:
-    ## Command Greaseweazle to sink given data buffer.
-    def sink_bytes(self, dat, seed):
-        try:
-            self._send_cmd(struct.pack("<2BII", Cmd.SinkBytes, 10,
-                                       len(dat), seed))
-        except CmdError as error:
-            if error.code != Ack.BadCommand:
-                raise
-            # Firmware v0.28 and earlier
-            self._send_cmd(struct.pack("<2BI", Cmd.SinkBytes, 6, len(dat)))
-        self.ser.write(dat)
-        (ack,) = struct.unpack("B", self.ser.read(1))
-        return ack
-
-
-    ## bw_stats:
-    ## Get min/max bandwidth for previous source/sink command. Mbps (float).
-    def bw_stats(self):
-        self._send_cmd(struct.pack("3B", Cmd.GetInfo, 3,
-                                   GetInfo.BandwidthStats))
-        min_bytes, min_usecs, max_bytes, max_usecs = struct.unpack(
-            "<4I16x", self.ser.read(32))
-        min_bw = (8 * min_bytes) / min_usecs
-        max_bw = (8 * max_bytes) / max_usecs
-        return min_bw, max_bw
-
-    
-    ##
-    ## Delay-property public getters and setters:
-    ##  select_delay:      Delay (usec) after asserting drive select
-    ##  step_delay:        Delay (usec) after issuing a head-step command
-    ##  seek_settle_delay: Delay (msec) after completing a head-seek operation
-    ##  motor_delay:       Delay (msec) after turning on drive spindle motor
-    ##  watchdog_delay:    Timeout (msec) since last command upon which all
-    ##                     drives are deselected and spindle motors turned off
-    ##
-
-    def _set_delays(self):
-        self._send_cmd(struct.pack("<3B5H", Cmd.SetParams,
-                                   3+5*2, Params.Delays,
-                                   self._select_delay, self._step_delay,
-                                   self._seek_settle_delay,
-                                   self._motor_delay, self._watchdog_delay))
-
-    @property
-    def select_delay(self):
-        return self._select_delay
-    @select_delay.setter
-    def select_delay(self, select_delay):
-        self._select_delay = select_delay
-        self._set_delays()
-
-    @property
-    def step_delay(self):
-        return self._step_delay
-    @step_delay.setter
-    def step_delay(self, step_delay):
-        self._step_delay = step_delay
-        self._set_delays()
-
-    @property
-    def seek_settle_delay(self):
-        return self._seek_settle_delay
-    @seek_settle_delay.setter
-    def seek_settle_delay(self, seek_settle_delay):
-        self._seek_settle_delay = seek_settle_delay
-        self._set_delays()
-
-    @property
-    def motor_delay(self):
-        return self._motor_delay
-    @motor_delay.setter
-    def motor_delay(self, motor_delay):
-        self._motor_delay = motor_delay
-        self._set_delays()
-
-    @property
-    def watchdog_delay(self):
-        return self._watchdog_delay
-    @watchdog_delay.setter
-    def watchdog_delay(self, watchdog_delay):
-        self._watchdog_delay = watchdog_delay
-        self._set_delays()
-
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 109
scripts/gw.py

@@ -1,109 +0,0 @@
-#!/usr/bin/env python3
-
-# gw.py
-#
-# Greaseweazle control script.
-#
-# 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 sys, time
-import importlib
-
-from greaseweazle import version
-if hasattr(version, 'commit'):
-    print("""*** TEST/PRE-RELEASE: commit %s
-*** Use these tools and firmware ONLY for test and development!!"""
-          % version.commit)
-
-missing_modules = []
-
-try:
-    import bitarray
-except ImportError:
-    missing_modules.append("bitarray")
-    
-try:
-    import crcmod
-except ImportError:
-    missing_modules.append("crcmod")
-    
-try:
-    import serial.tools.list_ports
-except ImportError:
-    missing_modules.append("pyserial")
-
-if missing_modules:
-    print("""\
-** Missing Python modules: %s
-For installation instructions please read the wiki:
-<https://github.com/keirf/Greaseweazle/wiki/Software-Installation>"""
-          % ', '.join(missing_modules))
-    sys.exit(1)
-
-actions = [ 'info',
-            'read',
-            'write',
-            'erase',
-            'clean',
-            'seek',
-            'delays',
-            'update',
-            'pin',
-            'reset',
-            'bandwidth' ]
-argv = sys.argv
-
-def usage():
-    print("Usage: %s [--time] [action] [-h] ..." % (argv[0]))
-    print("  --time      Print elapsed time after action is executed")
-    print("  -h, --help  Show help message for specified action")
-    print("Actions:")
-    for a in actions:
-        mod = importlib.import_module('greaseweazle.tools.' + a)
-        print('  %-12s%s' % (a, mod.__dict__['description']))
-    sys.exit(1)
-
-backtrace = False
-start_time = None
-
-while len(argv) > 1 and argv[1].startswith('--'):
-    if argv[1] == '--bt':
-        backtrace = True
-    elif argv[1] == '--time':
-        start_time = time.time()
-    else:
-        usage()
-    argv = [argv[0]] + argv[2:]
-
-if len(argv) < 2 or argv[1] not in actions:
-    usage()
-
-mod = importlib.import_module('greaseweazle.tools.' + argv[1])
-main = mod.__dict__['main']
-try:
-    res = main(argv)
-    if res is None:
-        res = 0
-except (IndexError, AssertionError, TypeError, KeyError):
-    raise
-except KeyboardInterrupt:
-    if backtrace: raise
-    res = 1
-except Exception as err:
-    if backtrace: raise
-    print("** FATAL ERROR:")
-    print(err)
-    res = 1
-
-if start_time is not None:
-    elapsed = time.time() - start_time
-    print("Time elapsed: %.2f seconds" % elapsed)
-
-sys.exit(res)
-    
-# Local variables:
-# python-indent: 4
-# End:

+ 0 - 42
scripts/misc/artifact.py

@@ -1,42 +0,0 @@
-#!/usr/bin/env python3
-# Create a public download link for any of my Github Actions artifacts.
-# Optionally download the zip file.
-# Written & released by Keir Fraser <keir.xen@gmail.com>
-
-import re, sys
-import requests
-
-# Latest artifact webpage:
-# https://nightly.link/keirf/FlashFloppy/workflows/ci/master
-
-# Github Actions artifact link and corrsponding public link:
-# https://github.com/keirf/FlashFloppy/suites/1623687905/artifacts/29859161
-# https://nightly.link/keirf/FlashFloppy/actions/artifacts/29859161
-
-def print_usage():
-    print('artifact.py [-d] <github_artifact_url>')
-    sys.exit(0)
-
-if not(2 <= len(sys.argv) <= 3):
-    print_usage()
-if len(sys.argv) == 3:
-    if sys.argv[1] != '-d':
-        print_usage()
-    download = True
-    url = sys.argv[2]
-else:
-    download = False
-    url = sys.argv[1]
-
-url = re.sub('github.com', 'nightly.link', url)
-url = re.sub('suites/\d+', 'actions', url)
-url += '.zip'
-print(url)
-
-if download:
-    res = requests.get(url)
-    content_disposition = res.headers['Content-Disposition']
-    zipname = re.search('filename=([^ ]+.zip);', content_disposition).group(1)
-    print('Downloading to: ' + zipname)
-    with open(zipname, 'wb') as f:
-        f.write(res.content)

+ 0 - 14
scripts/misc/hw_test.sh

@@ -1,14 +0,0 @@
-#!/bin/bash
-
-# Creates a random Amiga ADF, writes the first three cylinders of a disk,
-# dumps those cylinders back, and checks against original ADF.
-
-dd if=/dev/urandom of=a.adf bs=512 count=1760
-disk-analyse -e 2 a.adf b.adf
-disk-analyse a.adf a.scp
-./gw write --tracks=c=0-2 a.scp
-./gw read --revs=1 --tracks=c=0-2 b.scp
-disk-analyse -e 2 b.scp c.adf
-diff b.adf c.adf
-md5sum b.adf c.adf
-rm -f a.adf b.adf c.adf a.scp b.scp

+ 0 - 50
scripts/misc/ipf_align.py

@@ -1,50 +0,0 @@
-# ipf_align.py
-# 
-# Align all tracks in an IPF image to the same offset from index mark.
-# 
-# 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, sys, crcmod.predefined
-
-def main(argv):
-    crc32 = crcmod.predefined.Crc('crc-32')
-    offset = 1024
-    if len(argv) == 4:
-        offset = int(argv[3])
-    elif len(argv) != 3:
-        print("%s <input_file> <output_file> [<offset>]" % argv[0])
-        return
-    with open(argv[1], "rb") as f:
-        in_dat = bytearray(f.read())
-    out_dat = bytearray()
-    while in_dat:
-        # Decode the common record header
-        id, length, crc = struct.unpack(">4s2I", in_dat[:12])
-        # Consume the record from the input array
-        record = in_dat[:length]
-        in_dat = in_dat[length:]
-        # Check the CRC
-        record[8:12] = bytes(4)
-        assert crc == crc32.new(record).crcValue, "CRC mismatch"
-        # Modify the record as necessary
-        if id == b'IMGE':
-            trkbits, = struct.unpack(">I", record[48:52])
-            if trkbits > offset:
-                record[32:40] = struct.pack(">2I", offset//8, offset)
-        # Re-calculate the CRC
-        record[8:12] = struct.pack(">I", crc32.new(record).crcValue)
-        # DATA chunk has extra data to copy
-        if id == b'DATA':
-            size, bsize, dcrc, datchunk = struct.unpack(">4I", record[12:28])
-            record += in_dat[:size]
-            in_dat = in_dat[size:]
-        # Write the full modified record into the output array
-        out_dat += record
-    with open(argv[2], "wb") as f:
-        f.write(out_dat)
-    
-if __name__ == "__main__":
-    main(sys.argv)

+ 0 - 96
scripts/misc/scp_info.py

@@ -1,96 +0,0 @@
-import struct, sys
-import matplotlib.pyplot as plt
-
-NO_DAT    = 0
-PRINT_DAT = 1
-PLOT_DAT  = 2
-
-def decode_flux(tdat):
-    flux, fluxl = 0, []
-    while tdat:
-        f, = struct.unpack(">H", tdat[:2])
-        tdat = tdat[2:]
-        if f == 0:
-            flux += 65536
-        else:
-            flux += f
-            fluxl.append(flux / 40)
-            flux = 0
-    return fluxl
-
-def dump_track(dat, trk_offs, trknr, show_dat):
-    print("Track %u:" % trknr)
-
-    trk_off = trk_offs[trknr]
-    if trk_off == 0:
-        print("Empty")
-        return
-    
-    # Parse the SCP track header and extract the flux data.
-    thdr = dat[trk_off:trk_off+4+12*nr_revs]
-    sig, tnr, _, _, s_off = struct.unpack("<3sB3I", thdr[:16])
-    assert sig == b"TRK"
-    assert tnr == trknr
-    p_idx, tot = [], 0.0
-    for i in range(nr_revs):
-        t,n,off = struct.unpack("<3I", thdr[4+i*12:4+(i+1)*12])
-        flux = decode_flux(dat[trk_off+off:trk_off+off+n*2])
-        print("Rev %u: time=%.2fus nr_flux=%u tot_flux=%.2fus"
-              % (i, t/40, n, sum(flux)))
-        tot += t/40
-        p_idx.append(tot)
-    if not show_dat:
-        return
-    
-    _, e_nr, e_off = struct.unpack("<3I", thdr[-12:])
-    fluxl = decode_flux(dat[trk_off+s_off:trk_off+e_off+e_nr*2])
-    tot = 0.0
-    i = 0
-    px, py = [0], [0]
-    for x in fluxl:
-        if show_dat == PRINT_DAT:
-            bad = ""
-            if (x < 3.6) or ((x > 4.4) and (x < 5.4)) \
-               or ((x > 6.6) and (x < 7.2)) or (x > 8.8):
-                bad = "BAD"
-            print("%d: %f %s" % (i, x, bad))
-        elif x < 50:
-            px.append(tot/1000)
-            py.append(x)
-        i += 1
-        tot += x
-    print("Total: %uus (%uus per rev)" % (int(tot), tot//nr_revs))
-    if show_dat == PLOT_DAT:
-        plt.xlabel("Time (ms)")
-        plt.ylabel("Flux (us)")
-        plt.gcf().set_size_inches(12, 8)
-        plt.axvline(x=0, ymin=0.95, color='r')
-        for t in p_idx:
-            plt.axvline(x=t/1000, ymin=0.95, color='r')
-        plt.scatter(px, py, s=1)
-        plt.show()
-
-argv = sys.argv
-
-plot = (argv[1] == '--plot')
-if plot:
-    argv = argv[1:]
-
-with open(argv[1], "rb") as f:
-    dat = f.read()
-
-header = struct.unpack("<3s9BI", dat[0:16])
-(sig, _, _, nr_revs, s_trk, e_trk, flags, _, ss, _, _) = header
-assert sig == b"SCP"
-nr_sides = 1 if ss else 2
-        
-trk_offs = struct.unpack("<168I", dat[16:0x2b0])
-
-print("Revolutions: %u" % nr_revs)
-
-if len(argv) == 3:
-    dump_track(dat, trk_offs, int(argv[2]), PLOT_DAT if plot else PRINT_DAT)
-else:
-    for i in range(s_trk, e_trk+1):
-        dump_track(dat, trk_offs, i, NO_DAT)
-

+ 0 - 52
scripts/misc/sw_test.sh

@@ -1,52 +0,0 @@
-#!/bin/bash
-
-# Creates a random Amiga ADF, writes the first three cylinders of a disk,
-# dumps those cylinders back, and checks against original ADF.
-dd if=/dev/urandom of=b.adf bs=512 count=1760
-disk-analyse -e 2 b.adf a.adf
-rm -f b.adf
-
-# Write and verify ADF, Read ADF
-./gw --bt write --tracks="c=0-2" a.adf
-./gw --bt read --revs=1 --tracks="c=0-2" b.adf
-disk-analyse -e 2 b.adf c.adf
-diff a.adf c.adf
-md5sum a.adf c.adf
-rm -f b.adf c.adf
-
-# Write SCP, Read SCP
-disk-analyse a.adf a.scp
-./gw --bt write --tracks="c=0-2" a.scp
-./gw --bt read --revs=1 --tracks="c=0-2" b.scp
-disk-analyse -e 2 b.scp b.adf
-diff a.adf b.adf
-md5sum a.adf b.adf
-rm -f b.adf a.scp b.scp
-
-# Write IPF, Read HFE
-disk-analyse a.adf a.ipf
-./gw --bt write --tracks="c=0-2" a.ipf
-./gw --bt read --revs=1 --tracks="c=0-2" b.hfe
-disk-analyse -e 2 b.hfe b.adf
-diff a.adf b.adf
-md5sum a.adf b.adf
-rm -f b.adf a.ipf b.hfe
-
-# Write HFE, Read HFE
-disk-analyse a.adf a.hfe
-./gw --bt write --tracks="c=0-2" a.hfe
-./gw --bt read --revs=1 --tracks="c=0-2" b.hfe
-disk-analyse -e 2 b.hfe b.adf
-diff a.adf b.adf
-md5sum a.adf b.adf
-
-# Read Kryoflux
-mkdir a
-./gw --bt read --revs=1 --tracks="c=0-2" a/
-disk-analyse -e 2 a/ b.adf
-diff a.adf b.adf
-md5sum a.adf b.adf
-rm -f b.adf c.adf a.hfe b.hfe
-rm -rf a
-
-rm -f a.adf

+ 0 - 63
scripts/misc/sysinfo.py

@@ -1,63 +0,0 @@
-import platform, sys
-import serial.tools.list_ports
-
-# MacOS Catalina:
-#  platform.system == "Darwin"
-#  Attrs: device, manufacturer, product, vid, pid, location, serial_number
-#
-# Ubuntu 18.04:
-#  platform.system == "Linux"
-#  Attrs: device, manufacturer, product, vid, pid, location, serial_number
-#
-# Windows 7:
-#  platform.system, platform.release == "Windows", "7"
-#  Attrs: device, vid, pid, location, serial_number
-#   (manufacturer == "InterBiometrics")
-#
-# Windows 10:
-#  platform.system, platform.release == "Windows", "10"
-#  Attrs: device, vid, pid, location, serial_number
-#   (manufacturer == "Microsoft")
-
-print("platform.system: %s" % platform.system())
-print("platform.version: %s" % platform.version())
-print("platform.release: %s" % platform.release())
-
-ports = serial.tools.list_ports.comports()
-i = 0
-for port in ports:
-    i += 1
-    print("Port %d:" % i)
-    if port.device:
-        print("    device: '%s'" % port.device)
-    if port.name:
-        print("    name: '%s'" % port.name)
-    if port.hwid:
-        print("    hwid: '%s'" % port.hwid)
-    if port.manufacturer:
-        print("    manufacturer: '%s'" % port.manufacturer)
-    if port.product:
-        print("    product: '%s'" % port.product)
-    if port.vid:
-        print("    vid: %04x" % port.vid)
-    if port.pid:
-        print("    pid: %04x" % port.pid)
-    if port.location:
-        print("    location: '%s'" % port.location)
-    if port.serial_number:
-        print("    serial_number: '%s'" % port.serial_number)
-    if port.interface:
-        print("    interface: '%s'" % port.interface)
-
-if len(sys.argv) < 2 or sys.argv[1] != "loop":
-    sys.exit(0)
-
-# Loop checking that .location is always valid for a Weazle
-while True:
-    for port in serial.tools.list_ports.comports():
-        if port.vid == 0x1209:
-            if not port.location:
-                print("BAD", flush=True)
-            else:
-                print(".", end="", flush=True)
-

+ 9 - 1
scripts/mk_update.py

@@ -28,7 +28,15 @@
 import crcmod.predefined
 import re, struct, sys
 
-from greaseweazle import version
+class Version:
+    def __init__(self, major, minor):
+        self.major, self.minor = major, minor
+
+with open('Makefile', 'r') as f:
+    l = f.read()
+    major = int(re.search('FW_MAJOR := (\d+)', l).group(1))
+    minor = int(re.search('FW_MINOR := (\d+)', l).group(1))
+    version = Version(major, minor)
 
 name_to_hw_model = { 'stm32f1': 1,
                      'stm32f7': 7,

+ 0 - 19
scripts/setup.py

@@ -1,19 +0,0 @@
-from cx_Freeze import setup, Executable
-from greaseweazle import version
-
-buildOptions = dict(
-    packages = ['greaseweazle'],
-    excludes = ['tkinter', 'test', 'distutils', 'email'],
-    include_msvcr = True)
-
-base = 'Console'
-
-executables = [
-    Executable('gw.py', base=base)
-]
-
-setup(name='Greaseweazle',
-      version = f'{version.major}.{version.minor}',
-      description = '',
-      options = dict(build_exe = buildOptions),
-      executables = executables)

+ 0 - 9
scripts/setup.sh

@@ -1,9 +0,0 @@
-#!/bin/bash
-PYTHON="${PYTHON:-python3}"
-if [ ! -d ./scripts/c_ext ]; then
-    echo "** Please run setup.sh from within the Greaseweazle folder";
-    echo "** eg: ./scripts/setup.sh";
-    exit 1;
-fi ;
-$PYTHON -m pip install --user bitarray crcmod pyserial
-(cd ./scripts/c_ext && $PYTHON setup.py install --install-platlib=../greaseweazle/optimised)