* 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.
Before, the `drives` field was an `array[0..1]` of `Drive`, but all of
the methods took a `DriveNumber`, which was `[1..2]`. This meant that
code everywhere was always subtracting 1 from the drive number.
Now, `drives` is a `Record<DriveNumber, Drive>`, which means tha it
has indexes `1, 2` and there's no need to subtract 1 everywhere.
This change updates the `DiskII` class and its tests.
The motivation for this change is to slowly split the WOZ disk
implementation from the nibble disk implementation. I've tried twice,
but the change has always grown too big and hairy, so I'm starting
very small this time and working my way up.
Before, keyboard input used key codes to map events to Apple II keys.
This worked reasonably well, but `event.keyCode` was deprecated and
slated to be removed.
The refactored code now uses `event.key` which returns the localized,
keyboard-mapped key that the user pressed, which may be a letter or a
"symbolic" key. This is then transformed into an Apple II key.
One side effect of the refactoring is that the keys now light up as
you type and that combinations of mouse clicks on modifiers and plain
keys will take the modifiers into account.
Before, if there was an error in the audio worker or in the disk
worker, the emulator would not start. This could happen, for example,
if the page is loaded directly from disk in Chrome instead of through
a server.
Now, even if there is an error, the emulator will start.
Before, if the browser window wasn't tall enough to show the whole
keyboard, using the arrow keys in the window would cause the page
to move as well. Now all key events that are sent to the keyboard
have `preventDefault()` called on them.
* Add tests for Applesoft compiler in preparation for refactoring
While refactoring the compiler, I found several small bugs:
* Lower-case letters in strings and REM statements were converted
to upper-case.
* Lines are stored in the order received, not sorted by line number.
* Does not prefer `ATN` to `AT`.
* Does not prefer `TO` to `AT`.
* `DATA` statements don't preserve spaces.
* `DATA` statements don't preserve lowercase.
These will be fixed in the upcoming refactoring.
* Refactor the Applesoft Compiler
Before, the compiler had a few bugs that were not trivial to solve
because the implementation was in one heavily-nested function.
In this refactoring of the compiler, things like tokenization have
been split into separate methods which makes them a bit easier to
understand.
This refactoring also passes all of the tests.
* Set `PRGEND` when compiling to memory
Before, `PRGEND` was not adjusted which made round-tripping from
the Applesoft compiler to the decompiler not work. This change
now updates `PRGEND` with the end-of-program + 2 bytes which seems
to be the most frequent value that I have observed.
* Fix two compiler bugs
In debugging the decompiler, I noticed two bugs in the compiler:
* The first character after a line number was skipped.
* `?` was not accepted as a shortcut for `PRINT`.
This change fixes these two problems and adds tests.
* Ignore spaces more aggressively
It turns out that Applesoft happily accepts 'T H E N' for `THEN`
but the parser did not. This change fixes that and adds tests for
some odd cases.
Interestingly, this means that there are some valid statements
that Applesoft can never parse correctly because it is greedy
and ignores (most) spaces. For example, `NOT RACE` will always
parse as `NOTRACE` even though `NOT RACE` is a valid expression.
* Move tokens into a separate file
Because the token lists are just maps in opposite directions, put
them in the same file. In the future, maybe we can build one
automatically.
* Fix `apple2.ts`
I had neglected to actually update `apple2.ts` to use the new
compiler and decompiler. They now do.
Also, the decompiler can be created from `Memory`. It assumes,
though, that the zero page pointers to the start and end of the
program are correct.
* Address comments
* No more `as const` for tokens.
* Extracted zero page constants to their own file.
Co-authored-by: Will Scullin <scullin@scullin.com>
* Add a basic write test for WOZ images
The new test just tries to change some random nibbles at the beginning
of the image and then verifies that the change has been recorded.
This exposed a bug where `q7` was never set to `true` when write mode
was toggled on.
Also, the assumptions and limitations of `moveHead` are more clearly
documented.
* Address comments
* Improved `moveHead` documentation a bit more.
* Removed redundant variable in `readNibble`.
* Refactored `findSector` and commented out the chatty log line.
All tests pass. No lint warnings.
* Add a test for the dirty callback on writes
This new test just checks that a clean disk becomes dirty after a
write _and_ that the dirty callback is fired.
* Add tests for WOZ disks
The new tests verify the basic read behavior of the state sequencer on
well-behaved disks, including sync bytes and so on. Write tests are
still to come.
There's also a change to the Woz format to return the info chunk data
as well.
* Add tests for the DiskII card
This change adds basic read tests for nibble-based disks for the
DiskII card and fixes a few minor errors.
These tests are in preparation for refactoring.
* Add write tests
These are some basic tests of writing to nibble disks. In the process,
one minor bug was found, fixed and documented.
* Fix the write tests
I misinterpreted something from Sather and thought that the high bit
had to be set on the data for writing to happen at all. This is not
true. Instead, there is a flux transition every time the high bit is
set as the data is left-shifted out of the data register. The
erroneous test has been removed.
At the same time, I finally understand what `skip` does and documented
that.
* Add tests for saving and restoring Disk II state
These are not exhaustive tests, but they ensure that some basic state
is saved and restored.
* Add `spawn` as a way of calling promise-returning blocks
This change adds `spawn` which takes a no-argument, promise-returning
function, calls it, and returns `void`. This makes it easy to call
async blocks from `useEffect` and other places that don't take async
functions, but also makes such calls explicit.
* Adds interruptability to `spawn`
Now, the task function passed to `spawn` can take an `Interrupted`
argument, which is merely a method that returns `true` if the task
should stop doing work. Likewise, `spawn` returns an `Interrupt`
function that causes the `Interrupted` function to return `true`.
* Change to using `AbortController` and `AbortSignal`
Before, `spawn` used functions to interrupt and determine interruption
state. Now, based on feedback from @whscullin, it uses
`AbortController` and `AbortSignal`.
Tests now show how the controller can be used to abort long-running
tasks and API calls in the `spawn`. The also show how signals can be
chained using `addEventListener`.
* Fix `Apple2.tsx`
Forgot to change it to use `AbortController` and `AbortSignal`.
Co-authored-by: Will Scullin <scullin@scullin.com>
This change adds `spawn` which takes a no-argument, promise-returning
function, calls it, and returns `void`. This makes it easy to call
async blocks from `useEffect` and other places that don't take async
functions, but also makes such calls explicit.
Before, the offset for `FLAGS` in `2mg.ts` was `0x0A`, which is
incorrect according to the spec at:
https://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt
Now, all of the fields in the 2mg header are described, including
their lengths and any constraints. These constraints are enforced by
`read2MGHeader` and tested by new tests.