Floppy controller refactorings 1 (#155)

* Add `DiskMetada` to the `Disk` interface

Before, metadata about the image, such as name, side, etc. was mixed
in with actual disk image information.  This change breaks that
information into a separate structure called `DiskMetadata`.
Currently, the only two fields are `name` and `side`, but the idea is
that more fields could be added as necessary, like a description, a
scan of the disk or label, etc.  In a follow-on change, the default
write-protection status will come from the metadata as well.

The current implementation copies the metadata when saving/restoring
state, loading disk images, etc.  In the future, the metadata should
passed around until the format is required to change (like saving one
disk image format as another).  Likewise, in the future, in may be
desirable to be able to override the disk image metadata with
user-supplied metadata.  This could be use, for example, to
temporarily add or remove write-protection from a disk image.

All existing tests pass and the emulator builds with no errors.

* Rename `writeMode` to `q7`

Before, nibble disk emulation used the `writeMode` field to keep track
of whether the drive should be read from or written to, but the WOZ
emulation used `q7` to keep track of the same state.

This change renames `writeMode` to `q7` because it more accurately
reflects the state of the Disk II controller as specified in the
manuals, DOS source, and, especially, _Understanding the Apple //e_ by
Jim Sather.

* Remove the coil state

Before, `q` captured the state of the coils.  But it was never read.
This change just deletes it.

* Use the bootstrap and sequencer ROMs with indirection

Before, the contents of the bootstrap ROM and sequencer ROM were set
directly on fields of the controller.  These were not saved or
restored with the state in `getState` and `setState`.  (It would have
been very space inefficient if they had).

Now, these ROMs are used from constants indexed by the number of
sectors the card supports.  This, in turn, means that if the number of
sectors is saved with the state, it can be easily restored.

* Split out the Disk II controller state

This change factors the emulated hardware state into a separate
structure in the Disk II controller.  The idea is that this hardware
state will be able to be shared with the WOZ and nibble disk code
instead of sharing _all_ of the controller state (like callbacks and
so forth).

* Factor out disk insertion

Before, several places in the code essentially inserted a new disk
image into the drive, which similar—but not always exactly the
same—code.  Now there is an `insertDisk` method that is responsible
for inserting a new `FloppyDisk`.

All tests pass, everything compiles, manually tested nibble disks and
WOZ disks.
This commit is contained in:
Ian Flanigan 2022-09-01 03:55:01 +02:00 committed by GitHub
parent 5e224006e4
commit 41e0609f55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 243 additions and 207 deletions

View File

@ -390,7 +390,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
), ),
encoding: ENCODING_BLOCK, encoding: ENCODING_BLOCK,
readOnly: disk.readOnly, readOnly: disk.readOnly,
name: disk.name, metadata: { ...disk.metadata },
}; };
} }
return result; return result;
@ -441,7 +441,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
const prodos = new ProDOSVolume(disk); const prodos = new ProDOSVolume(disk);
this._name[drive] = disk.name; this._name[drive] = disk.metadata.name;
this._partitions[drive] = prodos; this._partitions[drive] = prodos;
if (drive) { if (drive) {
@ -483,7 +483,8 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
if (!blockDisk) { if (!blockDisk) {
return null; return null;
} }
const { name, blocks, readOnly } = blockDisk; const { blocks, readOnly } = blockDisk;
const { name } = blockDisk.metadata;
let ext; let ext;
let data: ArrayBuffer; let data: ArrayBuffer;
if (this._metadata[drive]) { if (this._metadata[drive]) {
@ -498,7 +499,7 @@ export default class CFFA implements Card, MassStorage<BlockFormat>, Restorable<
data = dataArray.buffer; data = dataArray.buffer;
} }
return { return {
name, metadata: { name },
ext, ext,
data, data,
readOnly, readOnly,

View File

@ -4,7 +4,7 @@ import type {
Card, Card,
memory, memory,
nibble, nibble,
rom, ReadonlyUint8Array,
} from '../types'; } from '../types';
import { import {
@ -22,6 +22,9 @@ import {
ENCODING_BITSTREAM, ENCODING_BITSTREAM,
MassStorage, MassStorage,
MassStorageData, MassStorageData,
DiskMetadata,
SupportedSectors,
FloppyDisk,
} from '../formats/types'; } from '../formats/types';
import { import {
@ -121,6 +124,18 @@ const SEQUENCER_ROM_16 = [
0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F 0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F
] as const; ] as const;
/** Contents of the P6 sequencer ROM. */
const SEQUENCER_ROM: Record<SupportedSectors, ReadonlyArray<byte>> = {
13: SEQUENCER_ROM_13,
16: SEQUENCER_ROM_16,
};
/** Contents of the P5 ROM at 0xCnXX. */
const BOOTSTRAP_ROM: Record<SupportedSectors, ReadonlyUint8Array> = {
13: BOOTSTRAP_ROM_13,
16: BOOTSTRAP_ROM_16,
};
type LssClockCycle = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; type LssClockCycle = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
type Phase = 0 | 1 | 2 | 3; type Phase = 0 | 1 | 2 | 3;
@ -138,11 +153,10 @@ type Phase = 0 | 1 | 2 | 3;
* head momentum. * head momentum.
* *
* Examining the https://computerhistory.org/blog/apple-ii-dos-source-code/, * Examining the https://computerhistory.org/blog/apple-ii-dos-source-code/,
* one finds that the SEEK routine on line 4831 of `appdos31.lst`. It uses * one finds the SEEK routine on line 4831 of `appdos31.lst`. It uses `ONTABLE`
* `ONTABLE` and `OFFTABLE` (each 12 bytes) to know exactly how many * and `OFFTABLE` (each 12 bytes) to know exactly how many microseconds to
* microseconds to power on/off each coil as the head accelerates. At the end, * power on/off each coil as the head accelerates. At the end, the final coil
* the final coil is left powered on 9.5 milliseconds to ensure the head has * is left powered on 9.5 milliseconds to ensure the head has settled.
* settled.
* *
* https://embeddedmicro.weebly.com/apple-2iie.html shows traces of the boot * https://embeddedmicro.weebly.com/apple-2iie.html shows traces of the boot
* seek (which is slightly different) and a regular seek. * seek (which is slightly different) and a regular seek.
@ -153,23 +167,56 @@ const PHASE_DELTA = [
[-2, -1, 0, 1], [-2, -1, 0, 1],
[1, -2, -1, 0] [1, -2, -1, 0]
] as const; ] as const;
/**
* State of the controller.
*/
interface ControllerState {
/** Sectors supported by the controller. */
sectors: SupportedSectors;
/** Is the active drive powered on? */
on: boolean;
/** The active drive. */
drive: DriveNumber;
/** The 8-cycle LSS clock. */
clock: LssClockCycle;
/** Current state of the Logic State Sequencer. */
state: nibble;
/** Q6 (Shift/Load) */
q6: boolean;
/** Q7 (Read/Write) */
q7: boolean;
/** Last data from the disk drive. */
latch: byte;
/** Last data written by the CPU to card softswitch 0x8D. */
bus: byte;
}
/** Callbacks triggered by events of the drive or controller. */
export interface Callbacks { export interface Callbacks {
/** Called when a drive turns on or off. */
driveLight: (drive: DriveNumber, on: boolean) => void; driveLight: (drive: DriveNumber, on: boolean) => void;
/**
* Called when a disk has been written to. For performance and integrity,
* this is only called when the drive stops spinning or is removed from
* the drive.
*/
dirty: (drive: DriveNumber, dirty: boolean) => void; dirty: (drive: DriveNumber, dirty: boolean) => void;
/** Called when a disk is inserted or removed from the drive. */
label: (drive: DriveNumber, name?: string, side?: string) => void; label: (drive: DriveNumber, name?: string, side?: string) => void;
} }
/** Common information for Nibble and WOZ disks. */ /** Common information for Nibble and WOZ disks. */
interface BaseDrive { interface BaseDrive {
/** Current disk format. */ /** Current disk format. */
format: NibbleFormat; format: NibbleFormat;
/** Current disk volume number. */ /** Current disk volume number. */
volume: byte; volume: byte;
/** Displayed disk name */
name: string;
/** (Optional) Disk side (Front/Back, A/B) */
side?: string | undefined;
/** Quarter track position of read/write head. */ /** Quarter track position of read/write head. */
track: byte; track: byte;
/** Position of the head on the track. */ /** Position of the head on the track. */
@ -180,6 +227,8 @@ interface BaseDrive {
readOnly: boolean; readOnly: boolean;
/** Whether the drive has been written to since it was loaded. */ /** Whether the drive has been written to since it was loaded. */
dirty: boolean; dirty: boolean;
/** Metadata about the disk image */
metadata: DiskMetadata;
} }
/** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */ /** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */
@ -214,8 +263,6 @@ interface DriveState {
format: NibbleFormat; format: NibbleFormat;
encoding: typeof ENCODING_BITSTREAM | typeof ENCODING_NIBBLE; encoding: typeof ENCODING_BITSTREAM | typeof ENCODING_NIBBLE;
volume: byte; volume: byte;
name: string;
side?: string | undefined;
tracks: memory[]; tracks: memory[];
track: byte; track: byte;
head: byte; head: byte;
@ -224,15 +271,15 @@ interface DriveState {
dirty: boolean; dirty: boolean;
trackMap: number[]; trackMap: number[];
rawTracks: Uint8Array[]; rawTracks: Uint8Array[];
metadata: DiskMetadata;
} }
/** State of the controller for saving/restoring. */
// TODO(flan): It's unclear whether reusing ControllerState here is a good idea.
interface State { interface State {
drives: DriveState[]; drives: DriveState[];
skip: number; skip: number;
latch: number; controllerState: ControllerState;
writeMode: boolean;
on: boolean;
drive: DriveNumber;
} }
function getDriveState(drive: Drive): DriveState { function getDriveState(drive: Drive): DriveState {
@ -240,8 +287,6 @@ function getDriveState(drive: Drive): DriveState {
format: drive.format, format: drive.format,
encoding: drive.encoding, encoding: drive.encoding,
volume: drive.volume, volume: drive.volume,
name: drive.name,
side: drive.side,
tracks: [], tracks: [],
track: drive.track, track: drive.track,
head: drive.head, head: drive.head,
@ -250,6 +295,7 @@ function getDriveState(drive: Drive): DriveState {
dirty: drive.dirty, dirty: drive.dirty,
trackMap: [], trackMap: [],
rawTracks: [], rawTracks: [],
metadata: { ...drive.metadata },
}; };
if (isNibbleDrive(drive)) { if (isNibbleDrive(drive)) {
@ -273,14 +319,13 @@ function setDriveState(state: DriveState) {
format: state.format, format: state.format,
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
volume: state.volume, volume: state.volume,
name: state.name,
side: state.side,
tracks: [], tracks: [],
track: state.track, track: state.track,
head: state.head, head: state.head,
phase: state.phase, phase: state.phase,
readOnly: state.readOnly, readOnly: state.readOnly,
dirty: state.dirty, dirty: state.dirty,
metadata: { ...state.metadata },
}; };
for (let idx = 0; idx < state.tracks.length; idx++) { for (let idx = 0; idx < state.tracks.length; idx++) {
result.tracks.push(new Uint8Array(state.tracks[idx])); result.tracks.push(new Uint8Array(state.tracks[idx]));
@ -290,8 +335,6 @@ function setDriveState(state: DriveState) {
format: state.format, format: state.format,
encoding: ENCODING_BITSTREAM, encoding: ENCODING_BITSTREAM,
volume: state.volume, volume: state.volume,
name: state.name,
side: state.side,
track: state.track, track: state.track,
head: state.head, head: state.head,
phase: state.phase, phase: state.phase,
@ -299,6 +342,7 @@ function setDriveState(state: DriveState) {
dirty: state.dirty, dirty: state.dirty,
trackMap: [...state.trackMap], trackMap: [...state.trackMap],
rawTracks: [], rawTracks: [],
metadata: { ...state.metadata },
}; };
for (let idx = 0; idx < state.rawTracks.length; idx++) { for (let idx = 0; idx < state.rawTracks.length; idx++) {
result.rawTracks.push(new Uint8Array(state.rawTracks[idx])); result.rawTracks.push(new Uint8Array(state.rawTracks[idx]));
@ -317,65 +361,46 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
format: 'dsk', format: 'dsk',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
volume: 254, volume: 254,
name: 'Disk 1',
tracks: [], tracks: [],
track: 0, track: 0,
head: 0, head: 0,
phase: 0, phase: 0,
readOnly: false, readOnly: false,
dirty: false, dirty: false,
metadata: { name: 'Disk 1' },
}, },
2: { // Drive 2 2: { // Drive 2
format: 'dsk', format: 'dsk',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
volume: 254, volume: 254,
name: 'Disk 2',
tracks: [], tracks: [],
track: 0, track: 0,
head: 0, head: 0,
phase: 0, phase: 0,
readOnly: false, readOnly: false,
dirty: false, dirty: false,
metadata: { name: 'Disk 2' },
} }
}; };
private state: ControllerState;
/** /**
* When `1`, the next nibble will be available for read; when `0`, * When `1`, the next nibble will be available for read; when `0`,
* the card is pretending to wait for data to be shifted in by the * the card is pretending to wait for data to be shifted in by the
* sequencer. * sequencer.
*/ */
private skip = 0; private skip = 0;
/** Last data written by the CPU to card softswitch 0x8D. */
private bus = 0;
/** Drive data register. */
private latch = 0;
/** Drive off timeout id or null. */ /** Drive off timeout id or null. */
private offTimeout: number | null = null; private offTimeout: number | null = null;
/** Q6 (Shift/Load): Used by WOZ disks. */
private q6 = 0;
/** Q7 (Read/Write): Used by WOZ disks. */
private q7: boolean = false;
/** Q7 (Read/Write): Used by Nibble disks. */
private writeMode = false;
/** Whether the selected drive is on. */
private on = false;
/** Current drive number (1, 2). */
private drive: DriveNumber = 1;
/** Current drive object. */ /** Current drive object. */
private cur = this.drives[this.drive]; private cur: Drive;
/** Nibbles read this on cycle */ /** Nibbles read this on cycle */
private nibbleCount = 0; private nibbleCount = 0;
/** Q0-Q3: Coil states. */
private q = [false, false, false, false];
/** The 8-cycle LSS clock. */
private clock: LssClockCycle = 0;
/** Current CPU cycle count. */ /** Current CPU cycle count. */
private lastCycles = 0; private lastCycles = 0;
/** Current state of the Logic State Sequencer. */
private state: nibble = 0;
/** /**
* Number of zeros read in a row. The Disk ][ can only read two zeros in a * Number of zeros read in a row. The Disk ][ can only read two zeros in a
* row reliably; above that and the drive starts reporting garbage. See * row reliably; above that and the drive starts reporting garbage. See
@ -383,26 +408,31 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
*/ */
private zeros = 0; private zeros = 0;
/** Contents of the P5 ROM at 0xCnXX. */
private bootstrapRom: rom;
/** Contents of the P6 ROM. */
private sequencerRom: typeof SEQUENCER_ROM_16 | typeof SEQUENCER_ROM_13;
private worker: Worker; private worker: Worker;
/** Builds a new Disk ][ card. */ /** Builds a new Disk ][ card. */
constructor(private io: Apple2IO, private callbacks: Callbacks, private sectors = 16) { constructor(private io: Apple2IO, private callbacks: Callbacks, private sectors: SupportedSectors = 16) {
this.debug('Disk ]['); this.debug('Disk ][');
this.lastCycles = this.io.cycles(); this.lastCycles = this.io.cycles();
this.bootstrapRom = this.sectors === 16 ? BOOTSTRAP_ROM_16 : BOOTSTRAP_ROM_13; this.state = {
this.sequencerRom = this.sectors === 16 ? SEQUENCER_ROM_16 : SEQUENCER_ROM_13; sectors,
// From the example in UtA2e, p. 9-29, col. 1, para. 1., this is bus: 0,
// essentially the start of the sequencer loop and produces latch: 0,
// correctly synced nibbles immediately. Starting at state 0 drive: 1,
// would introduce a spurrious 1 in the latch at the beginning, on: false,
// which requires reading several more sync bytes to sync up. q6: false,
this.state = 2; q7: false,
clock: 0,
// From the example in UtA2e, p. 9-29, col. 1, para. 1., this is
// essentially the start of the sequencer loop and produces
// correctly synced nibbles immediately. Starting at state 0
// would introduce a spurrious 1 in the latch at the beginning,
// which requires reading several more sync bytes to sync up.
state: 2,
};
this.cur = this.drives[this.state.drive];
this.initWorker(); this.initWorker();
} }
@ -462,10 +492,12 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
const track = const track =
this.cur.rawTracks[this.cur.trackMap[this.cur.track]] || [0]; this.cur.rawTracks[this.cur.trackMap[this.cur.track]] || [0];
const state = this.state;
while (workCycles-- > 0) { while (workCycles-- > 0) {
let pulse: number = 0; let pulse: number = 0;
if (this.clock === 4) { if (state.clock === 4) {
pulse = track[this.cur.head]; pulse = track[this.cur.head];
if (!pulse) { if (!pulse) {
// More than 2 zeros can not be read reliably. // More than 2 zeros can not be read reliably.
@ -479,47 +511,47 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
let idx = 0; let idx = 0;
idx |= pulse ? 0x00 : 0x01; idx |= pulse ? 0x00 : 0x01;
idx |= this.latch & 0x80 ? 0x02 : 0x00; idx |= state.latch & 0x80 ? 0x02 : 0x00;
idx |= this.q6 ? 0x04 : 0x00; idx |= state.q6 ? 0x04 : 0x00;
idx |= this.q7 ? 0x08 : 0x00; idx |= state.q7 ? 0x08 : 0x00;
idx |= this.state << 4; idx |= state.state << 4;
const command = this.sequencerRom[idx]; const command = SEQUENCER_ROM[this.sectors][idx];
this.debug(`clock: ${this.clock} state: ${toHex(this.state)} pulse: ${pulse} command: ${toHex(command)} q6: ${this.q6} latch: ${toHex(this.latch)}`); this.debug(`clock: ${state.clock} state: ${toHex(state.state)} pulse: ${pulse} command: ${toHex(command)} q6: ${state.q6} latch: ${toHex(state.latch)}`);
switch (command & 0xf) { switch (command & 0xf) {
case 0x0: // CLR case 0x0: // CLR
this.latch = 0; state.latch = 0;
break; break;
case 0x8: // NOP case 0x8: // NOP
break; break;
case 0x9: // SL0 case 0x9: // SL0
this.latch = (this.latch << 1) & 0xff; state.latch = (state.latch << 1) & 0xff;
break; break;
case 0xA: // SR case 0xA: // SR
this.latch >>= 1; state.latch >>= 1;
if (this.cur.readOnly) { if (this.cur.readOnly) {
this.latch |= 0x80; state.latch |= 0x80;
} }
break; break;
case 0xB: // LD case 0xB: // LD
this.latch = this.bus; state.latch = state.bus;
this.debug('Loading', toHex(this.latch), 'from bus'); this.debug('Loading', toHex(state.latch), 'from bus');
break; break;
case 0xD: // SL1 case 0xD: // SL1
this.latch = ((this.latch << 1) | 0x01) & 0xff; state.latch = ((state.latch << 1) | 0x01) & 0xff;
break; break;
default: default:
this.debug(`unknown command: ${toHex(command & 0xf)}`); this.debug(`unknown command: ${toHex(command & 0xf)}`);
} }
this.state = (command >> 4 & 0xF) as nibble; state.state = (command >> 4 & 0xF) as nibble;
if (this.clock === 4) { if (state.clock === 4) {
if (this.on) { if (state.on) {
if (this.q7) { if (state.q7) {
track[this.cur.head] = this.state & 0x8 ? 0x01 : 0x00; track[this.cur.head] = state.state & 0x8 ? 0x01 : 0x00;
this.debug('Wrote', this.state & 0x8 ? 0x01 : 0x00); this.debug('Wrote', state.state & 0x8 ? 0x01 : 0x00);
} }
if (++this.cur.head >= track.length) { if (++this.cur.head >= track.length) {
@ -528,8 +560,8 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
} }
if (++this.clock > 7) { if (++state.clock > 7) {
this.clock = 0; state.clock = 0;
} }
} }
} }
@ -539,28 +571,29 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
if (!isNibbleDrive(this.cur)) { if (!isNibbleDrive(this.cur)) {
return; return;
} }
if (this.on && (this.skip || this.writeMode)) { const state = this.state;
if (state.on && (this.skip || state.q7)) {
const track = this.cur.tracks[this.cur.track >> 2]; const track = this.cur.tracks[this.cur.track >> 2];
if (track && track.length) { if (track && track.length) {
if (this.cur.head >= track.length) { if (this.cur.head >= track.length) {
this.cur.head = 0; this.cur.head = 0;
} }
if (this.writeMode) { if (state.q7) {
if (!this.cur.readOnly) { if (!this.cur.readOnly) {
track[this.cur.head] = this.bus; track[this.cur.head] = state.bus;
if (!this.cur.dirty) { if (!this.cur.dirty) {
this.updateDirty(this.drive, true); this.updateDirty(state.drive, true);
} }
} }
} else { } else {
this.latch = track[this.cur.head]; state.latch = track[this.cur.head];
} }
++this.cur.head; ++this.cur.head;
} }
} else { } else {
this.latch = 0; state.latch = 0;
} }
this.skip = (++this.skip % 2); this.skip = (++this.skip % 2);
} }
@ -579,7 +612,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// 5. [...] enables head positioning [...] // 5. [...] enables head positioning [...]
// //
// Therefore do nothing if no drive is on. // Therefore do nothing if no drive is on.
if (!this.on) { if (!this.state.on) {
this.debug(`ignoring phase ${phase}${on ? ' on' : ' off'}`); this.debug(`ignoring phase ${phase}${on ? ' on' : ' off'}`);
return; return;
} }
@ -608,11 +641,10 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// 'Drive', _drive, 'track', toHex(_cur.track >> 2) + '.' + (_cur.track & 0x3), // 'Drive', _drive, 'track', toHex(_cur.track >> 2) + '.' + (_cur.track & 0x3),
// '(' + toHex(_cur.track) + ')', // '(' + toHex(_cur.track) + ')',
// '[' + phase + ':' + (on ? 'on' : 'off') + ']'); // '[' + phase + ':' + (on ? 'on' : 'off') + ']');
this.q[phase] = on;
} }
private access(off: byte, val?: byte) { private access(off: byte, val?: byte) {
const state = this.state;
let result = 0; let result = 0;
const readMode = val === undefined; const readMode = val === undefined;
@ -644,13 +676,13 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
case LOC.DRIVEOFF: // 0x08 case LOC.DRIVEOFF: // 0x08
if (!this.offTimeout) { if (!this.offTimeout) {
if (this.on) { if (state.on) {
// TODO(flan): This is fragile because it relies on // TODO(flan): This is fragile because it relies on
// wall-clock time instead of emulator time. // wall-clock time instead of emulator time.
this.offTimeout = window.setTimeout(() => { this.offTimeout = window.setTimeout(() => {
this.debug('Drive Off'); this.debug('Drive Off');
this.on = false; state.on = false;
this.callbacks.driveLight(this.drive, false); this.callbacks.driveLight(state.drive, false);
this.debug('nibbles read', this.nibbleCount); this.debug('nibbles read', this.nibbleCount);
}, 1000); }, 1000);
} }
@ -662,37 +694,37 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
window.clearTimeout(this.offTimeout); window.clearTimeout(this.offTimeout);
this.offTimeout = null; this.offTimeout = null;
} }
if (!this.on) { if (!state.on) {
this.debug('Drive On'); this.debug('Drive On');
this.nibbleCount = 0; this.nibbleCount = 0;
this.on = true; state.on = true;
this.lastCycles = this.io.cycles(); this.lastCycles = this.io.cycles();
this.callbacks.driveLight(this.drive, true); this.callbacks.driveLight(state.drive, true);
} }
break; break;
case LOC.DRIVE1: // 0x0a case LOC.DRIVE1: // 0x0a
this.debug('Disk 1'); this.debug('Disk 1');
this.drive = 1; state.drive = 1;
this.cur = this.drives[this.drive]; this.cur = this.drives[state.drive];
if (this.on) { if (state.on) {
this.callbacks.driveLight(2, false); this.callbacks.driveLight(2, false);
this.callbacks.driveLight(1, true); this.callbacks.driveLight(1, true);
} }
break; break;
case LOC.DRIVE2: // 0x0b case LOC.DRIVE2: // 0x0b
this.debug('Disk 2'); this.debug('Disk 2');
this.drive = 2; state.drive = 2;
this.cur = this.drives[this.drive]; this.cur = this.drives[state.drive];
if (this.on) { if (state.on) {
this.callbacks.driveLight(1, false); this.callbacks.driveLight(1, false);
this.callbacks.driveLight(2, true); this.callbacks.driveLight(2, true);
} }
break; break;
case LOC.DRIVEREAD: // 0x0c (Q6L) Shift case LOC.DRIVEREAD: // 0x0c (Q6L) Shift
this.q6 = 0; state.q6 = false;
if (this.writeMode) { if (state.q7) {
this.debug('clearing _q6/SHIFT'); this.debug('clearing _q6/SHIFT');
} }
if (isNibbleDrive(this.cur)) { if (isNibbleDrive(this.cur)) {
@ -701,17 +733,17 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
break; break;
case LOC.DRIVEWRITE: // 0x0d (Q6H) LOAD case LOC.DRIVEWRITE: // 0x0d (Q6H) LOAD
this.q6 = 1; state.q6 = true;
if (this.writeMode) { if (state.q7) {
this.debug('setting _q6/LOAD'); this.debug('setting _q6/LOAD');
} }
if (isNibbleDrive(this.cur)) { if (isNibbleDrive(this.cur)) {
if (readMode && !this.writeMode) { if (readMode && !state.q7) {
if (this.cur.readOnly) { if (this.cur.readOnly) {
this.latch = 0xff; state.latch = 0xff;
this.debug('Setting readOnly'); this.debug('Setting readOnly');
} else { } else {
this.latch = this.latch >> 1; state.latch = state.latch >> 1;
this.debug('Clearing readOnly'); this.debug('Clearing readOnly');
} }
} }
@ -720,13 +752,11 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
case LOC.DRIVEREADMODE: // 0x0e (Q7L) case LOC.DRIVEREADMODE: // 0x0e (Q7L)
this.debug('Read Mode'); this.debug('Read Mode');
this.q7 = false; state.q7 = false;
this.writeMode = false;
break; break;
case LOC.DRIVEWRITEMODE: // 0x0f (Q7H) case LOC.DRIVEWRITEMODE: // 0x0f (Q7H)
this.debug('Write Mode'); this.debug('Write Mode');
this.q7 = true; state.q7 = true;
this.writeMode = true;
break; break;
default: default:
@ -740,7 +770,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// used to read the data register onto the CPU bus, although some // used to read the data register onto the CPU bus, although some
// also cause conflicts with the disk controller commands. // also cause conflicts with the disk controller commands.
if ((off & 0x01) === 0) { if ((off & 0x01) === 0) {
result = this.latch; result = state.latch;
if (result & 0x80) { if (result & 0x80) {
this.nibbleCount++; this.nibbleCount++;
} }
@ -750,7 +780,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} else { } else {
// It's not explicitly stated, but writes to any address set the // It's not explicitly stated, but writes to any address set the
// data register. // data register.
this.bus = val; state.bus = val;
} }
return result; return result;
@ -768,7 +798,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
read(_page: byte, off: byte) { read(_page: byte, off: byte) {
return this.bootstrapRom[off]; return BOOTSTRAP_ROM[this.sectors][off];
} }
write() { write() {
@ -776,15 +806,13 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
reset() { reset() {
if (this.on) { const state = this.state;
this.callbacks.driveLight(this.drive, false); if (state.on) {
this.writeMode = false; this.callbacks.driveLight(state.drive, false);
this.on = false; state.q7 = false;
this.drive = 1; state.on = false;
this.cur = this.drives[this.drive]; state.drive = 1;
} this.cur = this.drives[state.drive];
for (let idx = 0; idx < 4; idx++) {
this.q[idx] = false;
} }
} }
@ -793,16 +821,10 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
getState(): State { getState(): State {
// TODO(flan): This does not accurately save state. It's missing
// all of the state for WOZ disks and the current status of the
// bus.
const result = { const result = {
drives: [] as DriveState[], drives: [] as DriveState[],
skip: this.skip, skip: this.skip,
latch: this.latch, controllerState: { ...this.state },
writeMode: this.writeMode,
on: this.on,
drive: this.drive
}; };
result.drives[1] = getDriveState(this.drives[1]); result.drives[1] = getDriveState(this.drives[1]);
result.drives[2] = getDriveState(this.drives[2]); result.drives[2] = getDriveState(this.drives[2]);
@ -812,18 +834,16 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
setState(state: State) { setState(state: State) {
this.skip = state.skip; this.skip = state.skip;
this.latch = state.latch; this.state = { ...state.controllerState };
this.writeMode = state.writeMode;
this.on = state.on;
this.drive = state.drive;
for (const d of DRIVE_NUMBERS) { for (const d of DRIVE_NUMBERS) {
this.drives[d] = setDriveState(state.drives[d]); this.drives[d] = setDriveState(state.drives[d]);
const { name, side, dirty } = state.drives[d]; const { name, side } = state.drives[d].metadata;
const { dirty } = state.drives[d];
this.callbacks.label(d, name, side); this.callbacks.label(d, name, side);
this.callbacks.driveLight(d, this.on); this.callbacks.driveLight(d, this.state.on);
this.callbacks.dirty(d, dirty); this.callbacks.dirty(d, dirty);
} }
this.cur = this.drives[this.drive]; this.cur = this.drives[this.state.drive];
} }
getMetadata(driveNo: DriveNumber) { getMetadata(driveNo: DriveNumber) {
@ -864,10 +884,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} else { } else {
const disk = createDiskFromJsonDisk(jsonDisk); const disk = createDiskFromJsonDisk(jsonDisk);
if (disk) { if (disk) {
const cur = this.drives[drive]; this.insertDisk(drive, disk);
Object.assign(cur, disk);
this.updateDirty(drive, false);
this.callbacks.label(drive, disk.name, disk.side);
return true; return true;
} }
} }
@ -893,8 +910,8 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
}; };
this.worker.postMessage(message); this.worker.postMessage(message);
} else { } else {
const cur = this.drives[drive]; const disk = jsonDecode(json);
Object.assign(cur, jsonDecode(json)); this.insertDisk(drive, disk);
} }
return true; return true;
} }
@ -924,12 +941,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} else { } else {
const disk = createDisk(fmt, options); const disk = createDisk(fmt, options);
if (disk) { if (disk) {
const cur = this.drives[drive]; this.insertDisk(drive, disk);
const { name, side } = cur;
Object.assign(cur, disk);
this.updateDirty(drive, true);
this.callbacks.label(drive, name, side);
return true; return true;
} }
} }
@ -951,11 +963,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
{ {
const { drive, disk } = data.payload; const { drive, disk } = data.payload;
if (disk) { if (disk) {
const cur = this.drives[drive]; this.insertDisk(drive, disk);
Object.assign(cur, disk);
const { name, side } = cur;
this.updateDirty(drive, true);
this.callbacks.label(drive, name, side);
} }
} }
break; break;
@ -966,13 +974,22 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
} }
} }
private insertDisk(drive: DriveNumber, disk: FloppyDisk) {
const cur = this.drives[drive];
Object.assign(cur, disk);
const { name, side } = cur.metadata;
this.updateDirty(drive, true);
this.callbacks.label(drive, name, side);
}
// TODO(flan): Does not work with WOZ or D13 disks // TODO(flan): Does not work with WOZ or D13 disks
getBinary(drive: DriveNumber, ext?: NibbleFormat): MassStorageData | null { getBinary(drive: DriveNumber, ext?: NibbleFormat): MassStorageData | null {
const cur = this.drives[drive]; const cur = this.drives[drive];
if (!isNibbleDrive(cur)) { if (!isNibbleDrive(cur)) {
return null; return null;
} }
const { format, name, readOnly, tracks, volume } = cur; const { format, readOnly, tracks, volume } = cur;
const { name } = cur.metadata;
const len = format === 'nib' ? const len = format === 'nib' ?
tracks.reduce((acc, track) => acc + track.length, 0) : tracks.reduce((acc, track) => acc + track.length, 0) :
this.sectors * tracks.length * 256; this.sectors * tracks.length * 256;
@ -995,7 +1012,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
return { return {
ext, ext,
name, metadata: { name },
data: data.buffer, data: data.buffer,
readOnly, readOnly,
volume, volume,

View File

@ -523,7 +523,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
), ),
encoding: ENCODING_BLOCK, encoding: ENCODING_BLOCK,
readOnly: disk.readOnly, readOnly: disk.readOnly,
name: disk.name, metadata: { ...disk.metadata },
}; };
return result; return result;
} }
@ -540,7 +540,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
), ),
encoding: ENCODING_BLOCK, encoding: ENCODING_BLOCK,
readOnly: disk.readOnly, readOnly: disk.readOnly,
name: disk.name, metadata: { ...disk.metadata },
}; };
return result; return result;
} }
@ -580,7 +580,8 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
} }
const disk = this.disks[drive]; const disk = this.disks[drive];
const ext = this.ext[drive]; const ext = this.ext[drive];
const { name, readOnly } = disk; const { readOnly } = disk;
const { name } = disk.metadata;
let data: ArrayBuffer; let data: ArrayBuffer;
if (ext === '2mg') { if (ext === '2mg') {
data = create2MGFromBlockDisk(this.metadata[drive], disk); data = create2MGFromBlockDisk(this.metadata[drive], disk);
@ -593,7 +594,7 @@ export default class SmartPort implements Card, MassStorage<BlockFormat>, Restor
data = byteArray.buffer; data = byteArray.buffer;
} }
return { return {
name, metadata: { name },
ext, ext,
data, data,
readOnly, readOnly,

View File

@ -17,6 +17,7 @@ import { Videoterm } from './Videoterm';
import { spawn, Ready } from './util/promises'; import { spawn, Ready } from './util/promises';
import styles from './css/Apple2.module.css'; import styles from './css/Apple2.module.css';
import { SupportedSectors } from 'js/formats/types';
declare global { declare global {
interface Window { interface Window {
@ -33,7 +34,7 @@ export interface Apple2Props {
e: boolean; e: boolean;
gl: boolean; gl: boolean;
rom: string; rom: string;
sectors: number; sectors: SupportedSectors;
} }
/** /**

View File

@ -21,7 +21,8 @@ export const DownloadModal = ({ massStorage, number, onClose, isOpen } : Downloa
if (isOpen) { if (isOpen) {
const storageData = massStorage.getBinary(number); const storageData = massStorage.getBinary(number);
if (storageData) { if (storageData) {
const { name, ext, data } = storageData; const { ext, data } = storageData;
const { name } = storageData.metadata;
if (data.byteLength) { if (data.byteLength) {
const blob = new Blob( const blob = new Blob(
[data], [data],

View File

@ -10,7 +10,7 @@ import { ErrorModal } from './ErrorModal';
import { ProgressModal } from './ProgressModal'; import { ProgressModal } from './ProgressModal';
import { loadHttpUnknownFile, getHashParts, loadJSON, SmartStorageBroker } from './util/files'; import { loadHttpUnknownFile, getHashParts, loadJSON, SmartStorageBroker } from './util/files';
import { useHash } from './hooks/useHash'; import { useHash } from './hooks/useHash';
import { DISK_FORMATS, DriveNumber } from 'js/formats/types'; import { DISK_FORMATS, DriveNumber, SupportedSectors } from 'js/formats/types';
import { spawn, Ready } from './util/promises'; import { spawn, Ready } from './util/promises';
import styles from './css/Drives.module.css'; import styles from './css/Drives.module.css';
@ -32,7 +32,7 @@ export interface DrivesProps {
cpu: CPU6502 | undefined; cpu: CPU6502 | undefined;
io: Apple2IO | undefined; io: Apple2IO | undefined;
enhanced: boolean; enhanced: boolean;
sectors: number; sectors: SupportedSectors;
ready: Ready; ready: Ready;
} }

View File

@ -254,7 +254,8 @@ const DiskInfo = ({ massStorage, drive, setFileData }: DiskInfoProps) => {
const disk = useMemo(() => { const disk = useMemo(() => {
const massStorageData = massStorage.getBinary(drive, 'po'); const massStorageData = massStorage.getBinary(drive, 'po');
if (massStorageData) { if (massStorageData) {
const { name, data, readOnly, ext } = massStorageData; const { data, readOnly, ext } = massStorageData;
const { name } = massStorageData.metadata;
let disk: BlockDisk | NibbleDisk | null = null; let disk: BlockDisk | NibbleDisk | null = null;
if (ext === '2mg') { if (ext === '2mg') {
disk = createDiskFrom2MG({ disk = createDiskFrom2MG({

View File

@ -14,7 +14,7 @@ export const defaultSystem = {
e: true, e: true,
enhanced: true, enhanced: true,
sectors: 16, sectors: 16,
}; } as const;
export const systemTypes: Record<string, Partial<SystemType>> = { export const systemTypes: Record<string, Partial<SystemType>> = {
// Apple //e // Apple //e
@ -68,4 +68,4 @@ export const systemTypes: Record<string, Partial<SystemType>> = {
characterRom: 'apple2_char', characterRom: 'apple2_char',
e: false, e: false,
} }
}; } as const;

View File

@ -21,7 +21,7 @@ export default function createBlockDisk(options: DiskOptions): BlockDisk {
const disk: BlockDisk = { const disk: BlockDisk = {
encoding: ENCODING_BLOCK, encoding: ENCODING_BLOCK,
blocks, blocks,
name, metadata: { name },
readOnly, readOnly,
}; };

View File

@ -11,8 +11,7 @@ export default function createDiskFromDOS13(options: DiskOptions) {
const disk: NibbleDisk = { const disk: NibbleDisk = {
format: 'd13', format: 'd13',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
name, metadata: { name, side },
side,
volume, volume,
readOnly, readOnly,
tracks: [] tracks: []

View File

@ -13,8 +13,7 @@ export default function createDiskFromDOS(options: DiskOptions): NibbleDisk {
const disk: NibbleDisk = { const disk: NibbleDisk = {
format: 'dsk', format: 'dsk',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
name, metadata: { name, side },
side,
volume, volume,
readOnly, readOnly,
tracks: [], tracks: [],

View File

@ -554,7 +554,7 @@ export function jsonDecode(data: string): NibbleDisk {
volume: v, volume: v,
format: json.type, format: json.type,
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
name: json.name, metadata: { name: json.name },
tracks, tracks,
readOnly, readOnly,
}; };

View File

@ -11,8 +11,7 @@ export default function createDiskFromNibble(options: DiskOptions): NibbleDisk {
const disk: NibbleDisk = { const disk: NibbleDisk = {
format: 'nib', format: 'nib',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
name, metadata: { name, side },
side,
volume: volume || 254, volume: volume || 254,
readOnly: readOnly || false, readOnly: readOnly || false,
tracks: [] tracks: []

View File

@ -13,8 +13,7 @@ export default function createDiskFromProDOS(options: DiskOptions) {
const disk: NibbleDisk = { const disk: NibbleDisk = {
format: 'po', format: 'po',
encoding: ENCODING_NIBBLE, encoding: ENCODING_NIBBLE,
name, metadata: { name, side },
side,
volume: volume || 254, volume: volume || 254,
tracks: [], tracks: [],
readOnly: readOnly || false, readOnly: readOnly || false,

View File

@ -2,13 +2,15 @@ import type { byte, memory, MemberOf, word } from '../types';
import type { GamepadConfiguration } from '../ui/types'; import type { GamepadConfiguration } from '../ui/types';
import { InfoChunk } from './woz'; import { InfoChunk } from './woz';
export const SUPPORTED_SECTORS = [13, 16] as const;
export type SupportedSectors = MemberOf<typeof SUPPORTED_SECTORS>;
export const DRIVE_NUMBERS = [1, 2] as const; export const DRIVE_NUMBERS = [1, 2] as const;
export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>; export type DriveNumber = MemberOf<typeof DRIVE_NUMBERS>;
/** /**
* Arguments for the disk format processors. * Arguments for the disk format processors.
*/ */
export interface DiskOptions { export interface DiskOptions {
name: string; name: string;
side?: string | undefined; side?: string | undefined;
@ -33,7 +35,6 @@ export interface DiskDescriptor {
/** /**
* JSON binary image (not used?) * JSON binary image (not used?)
*/ */
export interface JSONBinaryImage { export interface JSONBinaryImage {
type: 'binary'; type: 'binary';
start: word; start: word;
@ -42,14 +43,24 @@ export interface JSONBinaryImage {
gamepad?: GamepadConfiguration; gamepad?: GamepadConfiguration;
} }
/**
* Information about a disk image not directly related to the
* disk contents. For example, the name or even a scan of the
* disk label are "metadata", but the volume number is not.
*/
export interface DiskMetadata {
/** Displayed disk name */
name: string;
/** (Optional) Disk side (Front/Back, A/B) */
side?: string | undefined;
}
/** /**
* Return value from disk format processors. Describes raw disk * Return value from disk format processors. Describes raw disk
* data which the DiskII card can process. * data which the DiskII card can process.
*/ */
export interface Disk { export interface Disk {
name: string; metadata: DiskMetadata;
side?: string | undefined;
readOnly: boolean; readOnly: boolean;
} }
@ -207,7 +218,7 @@ export interface DiskProcessedResponse {
type: typeof DISK_PROCESSED; type: typeof DISK_PROCESSED;
payload: { payload: {
drive: DriveNumber; drive: DriveNumber;
disk: Disk | null; disk: FloppyDisk | null;
}; };
} }
@ -215,7 +226,7 @@ export type FormatWorkerResponse =
DiskProcessedResponse; DiskProcessedResponse;
export interface MassStorageData { export interface MassStorageData {
name: string; metadata: DiskMetadata;
ext: string; ext: string;
readOnly: boolean; readOnly: boolean;
volume?: byte; volume?: byte;

View File

@ -297,8 +297,10 @@ export default function createDiskFromWoz(options: DiskOptions): WozDisk {
tracks: trks?.tracks || [], tracks: trks?.tracks || [],
rawTracks: trks?.rawTracks || [], rawTracks: trks?.rawTracks || [],
readOnly: true, //chunks.info.writeProtected === 1; readOnly: true, //chunks.info.writeProtected === 1;
name: meta?.values['title'] || options.name, metadata: {
side: meta?.values['side_name'] || meta?.values['side'], name: meta?.values['title'] || options.name,
side: meta?.values['side_name'] || meta?.values['side']
},
info info
}; };

View File

@ -12,12 +12,13 @@ import Thunderclock from './cards/thunderclock';
import VideoTerm from './cards/videoterm'; import VideoTerm from './cards/videoterm';
import { Apple2 } from './apple2'; import { Apple2 } from './apple2';
import { SupportedSectors } from './formats/types';
const prefs = new Prefs(); const prefs = new Prefs();
const romVersion = prefs.readPref('computer_type2'); const romVersion = prefs.readPref('computer_type2');
let rom: string; let rom: string;
let characterRom: string; let characterRom: string;
let sectors = 16; let sectors: SupportedSectors = 16;
switch (romVersion) { switch (romVersion) {
case 'apple2': case 'apple2':

View File

@ -66,11 +66,11 @@ describe('DiskII', () => {
const state = diskII.getState(); const state = diskII.getState();
// These are just arbitrary changes, not an exhaustive list of fields. // These are just arbitrary changes, not an exhaustive list of fields.
state.drive = 2;
state.skip = 1; state.skip = 1;
state.latch = 0x42; state.controllerState.drive = 2;
state.on = true; state.controllerState.latch = 0x42;
state.writeMode = true; state.controllerState.on = true;
state.controllerState.q7 = true;
state.drives[2].tracks[14][12] = 0x80; state.drives[2].tracks[14][12] = 0x80;
state.drives[2].head = 1000; state.drives[2].head = 1000;
state.drives[2].phase = 3; state.drives[2].phase = 3;

View File

@ -190,7 +190,7 @@ describe('2mg format', () => {
} }
const disk: BlockDisk = { const disk: BlockDisk = {
blocks, blocks,
name: 'Good disk', metadata: { name: 'Good disk' },
readOnly: false, readOnly: false,
encoding: ENCODING_BLOCK, encoding: ENCODING_BLOCK,
}; };

View File

@ -7,9 +7,11 @@ describe('createDiskFromJsonDisk', () => {
expect(disk).toEqual({ expect(disk).toEqual({
encoding: 'nibble', encoding: 'nibble',
format: 'dsk', format: 'dsk',
name: 'Test Disk', metadata: {
name: 'Test Disk',
side: 'Front',
},
readOnly: undefined, readOnly: undefined,
side: 'Front',
volume: 254, volume: 254,
tracks: expect.any(Array) as number[][] tracks: expect.any(Array) as number[][]
}); });

View File

@ -21,7 +21,7 @@ describe('woz', () => {
const disk = createDiskFromWoz(options); const disk = createDiskFromWoz(options);
expect(disk).toEqual({ expect(disk).toEqual({
name: 'Mock Woz 1', metadata: { name: 'Mock Woz 1' },
readOnly: true, readOnly: true,
encoding: ENCODING_BITSTREAM, encoding: ENCODING_BITSTREAM,
trackMap: mockTMAP, trackMap: mockTMAP,
@ -58,8 +58,10 @@ describe('woz', () => {
const disk = createDiskFromWoz(options); const disk = createDiskFromWoz(options);
expect(disk).toEqual({ expect(disk).toEqual({
name: 'Mock Woz 2', metadata: {
side: 'B', name: 'Mock Woz 2',
side: 'B',
},
readOnly: true, readOnly: true,
encoding: ENCODING_BITSTREAM, encoding: ENCODING_BITSTREAM,
trackMap: mockTMAP, trackMap: mockTMAP,

View File

@ -6,12 +6,12 @@ import {
} from '../js/formats/create_disk'; } from '../js/formats/create_disk';
import { import {
FormatWorkerMessage, FormatWorkerMessage,
Disk,
DiskProcessedResponse, DiskProcessedResponse,
DISK_PROCESSED, DISK_PROCESSED,
PROCESS_BINARY, PROCESS_BINARY,
PROCESS_JSON_DISK, PROCESS_JSON_DISK,
PROCESS_JSON, PROCESS_JSON,
FloppyDisk,
} from '../js/formats/types'; } from '../js/formats/types';
debug('Worker loaded'); debug('Worker loaded');
@ -20,7 +20,7 @@ addEventListener('message', (message: MessageEvent<FormatWorkerMessage>) => {
debug('Worker started', message.type); debug('Worker started', message.type);
const data = message.data; const data = message.data;
const { drive } = data.payload; const { drive } = data.payload;
let disk: Disk | null = null; let disk: FloppyDisk | null = null;
switch (data.type) { switch (data.type) {
case PROCESS_BINARY: { case PROCESS_BINARY: {