Skip to content

Commit

Permalink
Support North Star FM
Browse files Browse the repository at this point in the history
  • Loading branch information
keirf committed Jul 7, 2024
1 parent d79507d commit cf6bd63
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/greaseweazle/codec/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ def mk_trackdef(format_name: str) -> TrackDef:
return c64_gcr.C64GCRDef(format_name)
if format_name in ['hp.mmfm']:
return hp_mmfm.HPMMFMDef(format_name)
if format_name in ['northstar.mfm']:
return northstar.NorthStarMFMDef(format_name)
if format_name in ['northstar']:
return northstar.NorthStarDef(format_name)
if format_name in ['apple2.gcr']:
return apple2_gcr.Apple2GCRDef(format_name)
if format_name in ['bitcell']:
Expand Down
62 changes: 46 additions & 16 deletions src/greaseweazle/codec/northstar/northstar.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
from bitarray import bitarray
import crcmod.predefined
import itertools as it
from enum import Enum

from greaseweazle import error
from greaseweazle.codec import codec
from greaseweazle.codec.ibm.ibm import decode, encode, mfm_encode
from greaseweazle.codec.ibm.ibm import decode, encode, fm_encode, mfm_encode
from greaseweazle.track import MasterTrack, PLL, PLLTrack
from greaseweazle.flux import HasFlux

Expand All @@ -24,22 +25,41 @@
mfm_sync = bitarray(endian='big')
mfm_sync.frombytes(mfm_encode(encode(b'\x00\xfb')))

fm_sync = bitarray(endian='big')
fm_sync.frombytes(fm_encode(encode(b'\x00\xfb')))

def csum(dat):
y = 0
for x in dat:
y ^= x
y = ((y << 1) | (y >> 7)) & 255
return y

class NorthStarMFM(codec.Codec):
class Mode(Enum):
FM, MFM = range(2)
def __str__(self):
NAMES = [ 'North Star FM', 'North Star MFM' ]
return f'{NAMES[self.value]}'

class NorthStar(codec.Codec):

time_per_rev = 0.2
clock = 2e-6
bps = 512

verify_revs: float = default_revs

def __init__(self, cyl: int, head: int, config):
if config.mode is Mode.FM:
self.clock = 4e-6
self.bps = 256
self.sync = fm_sync
self.presync_bytes = 17
self.sync_bytes = 1
else:
self.clock = 2e-6
self.bps = 512
self.sync = mfm_sync
self.presync_bytes = 34
self.sync_bytes = 2
self.cyl, self.head = cyl, head
self.config = config
self.sector: List[Optional[bytes]]
Expand All @@ -51,7 +71,7 @@ def nsec(self) -> int:

def summary_string(self) -> str:
nsec, nbad = self.nsec, self.nr_missing()
s = "North Star MFM (%d/%d sectors)" % (nsec - nbad, nsec)
s = "%s (%d/%d sectors)" % (self.config.mode, nsec - nbad, nsec)
return s

# private
Expand Down Expand Up @@ -111,13 +131,13 @@ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
continue

s, e = hardsector_bits[sec_id], hardsector_bits[sec_id+1]
offs = bits[s:e].search(mfm_sync)
offs = bits[s:e].search(self.sync)
if len(offs) == 0:
continue
off = offs[0]
data = decode(bits[s+off:s+off+(3+self.bps+1)*16].tobytes())
if csum(data[3:-1]) == data[-1]:
self.add(sec_id, data[3:-1])
off = offs[0] + (1 + self.sync_bytes) * 16
data = decode(bits[s+off:s+off+(self.bps+1)*16].tobytes())
if csum(data[:-1]) == data[-1]:
self.add(sec_id, data[:-1])


def master_track(self) -> MasterTrack:
Expand All @@ -126,15 +146,15 @@ def master_track(self) -> MasterTrack:
slen = int((self.time_per_rev / self.clock / self.nsec / 16))

for sec_id in range(self.nsec):
s = encode(bytes(34))
s += encode(b'\xfb\xfb')
s = encode(bytes(self.presync_bytes))
s += encode(b'\xfb' * self.sync_bytes)
sector = self.sector[sec_id]
data = bad_sector*(self.bps//16) if sector is None else sector
s += encode(data + bytes([csum(data)]))
s += encode(bytes(slen - len(s)//2))
t += s

t = mfm_encode(t)
t = mfm_encode(t) if self.config.mode is Mode.MFM else fm_encode(t)

hardsector_bits = [slen*16*i for i in range(self.nsec)]

Expand All @@ -150,18 +170,26 @@ def verify_track(self, flux):
and self.sector == readback_track.sector)


class NorthStarMFMDef(codec.TrackDef):
class NorthStarDef(codec.TrackDef):

default_revs = default_revs

def __init__(self, format_name: str):
self.mode: Optional[Mode] = None
self.secs: Optional[int] = None
self.finalised = False

def add_param(self, key: str, val) -> None:
if key == 'secs':
val = int(val)
self.secs = val
elif key == 'mode':
if val == 'fm':
self.mode = Mode.FM
elif val == 'mfm':
self.mode = Mode.MFM
else:
raise error.Fatal('unrecognised mode %s' % val)
else:
raise error.Fatal('unrecognised track option %s' % key)

Expand All @@ -170,10 +198,12 @@ def finalise(self) -> None:
return
error.check(self.secs is not None,
'number of sectors not specified')
error.check(self.mode is not None,
'mode not specified')
self.finalised = True

def mk_track(self, cyl: int, head: int) -> NorthStarMFM:
return NorthStarMFM(cyl, head, self)
def mk_track(self, cyl: int, head: int) -> NorthStar:
return NorthStar(cyl, head, self)


# Local variables:
Expand Down
24 changes: 22 additions & 2 deletions src/greaseweazle/data/diskdefs.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -944,18 +944,38 @@ disk msx.2dd
end
end

disk northstar.fm.ss
cyls = 35
heads = 1
tracks * northstar
mode = fm
secs = 10
end
end

disk northstar.fm.ds
cyls = 35
heads = 2
tracks * northstar
mode = fm
secs = 10
end
end

disk northstar.mfm.ss
cyls = 35
heads = 1
tracks * northstar.mfm
tracks * northstar
mode = mfm
secs = 10
end
end

disk northstar.mfm.ds
cyls = 35
heads = 2
tracks * northstar.mfm
tracks * northstar
mode = mfm
secs = 10
end
end
Expand Down

0 comments on commit cf6bd63

Please sign in to comment.