badline 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/CLAUDE.md +6 -4
- data/README.md +60 -19
- data/doc/library.md +10 -3
- data/doc/media.md +11 -2
- data/doc/pinned-behaviour.md +199 -9
- data/doc/sid-player.md +8 -4
- data/doc/snapshots.md +79 -33
- data/lib/badline/address_bus/pla.rb +130 -0
- data/lib/badline/address_bus/roms.rb +32 -0
- data/lib/badline/address_bus.rb +13 -92
- data/lib/badline/audio/bare_player.rb +24 -0
- data/lib/badline/audio/checkpoints.rb +42 -0
- data/lib/badline/audio/jukebox.rb +11 -4
- data/lib/badline/audio/machine_player.rb +16 -0
- data/lib/badline/audio/renderer.rb +80 -9
- data/lib/badline/audio/stereo.rb +3 -5
- data/lib/badline/audio.rb +1 -0
- data/lib/badline/chrout_trap.rb +4 -3
- data/lib/badline/computer/attachments.rb +52 -12
- data/lib/badline/computer/saved_state.rb +1 -0
- data/lib/badline/computer.rb +50 -33
- data/lib/badline/control_ports.rb +10 -3
- data/lib/badline/core.rb +51 -0
- data/lib/badline/data_path.rb +37 -0
- data/lib/badline/datasette.rb +63 -10
- data/lib/badline/drive1541/disk.rb +3 -3
- data/lib/badline/drive1541/disk_state.rb +14 -5
- data/lib/badline/drive1541/serial_port.rb +5 -8
- data/lib/badline/drive1541/sleep.rb +5 -6
- data/lib/badline/drive1541.rb +3 -3
- data/lib/badline/frontend/app.rb +63 -64
- data/lib/badline/frontend/boot.rb +34 -4
- data/lib/badline/frontend/confirmation.rb +17 -11
- data/lib/badline/frontend/controls.rb +12 -6
- data/lib/badline/frontend/drive_led.rb +9 -10
- data/lib/badline/frontend/frame_rate.rb +4 -4
- data/lib/badline/frontend/machine_window.rb +87 -0
- data/lib/badline/frontend/menu_dialogs.rb +52 -14
- data/lib/badline/frontend/menu_media.rb +70 -16
- data/lib/badline/frontend/menu_pages.rb +47 -42
- data/lib/badline/frontend/menu_ports.rb +69 -0
- data/lib/badline/frontend/name_field.rb +88 -0
- data/lib/badline/frontend/pacer.rb +6 -5
- data/lib/badline/frontend/pause_menu.rb +52 -37
- data/lib/badline/frontend/player_screen.rb +1 -1
- data/lib/badline/frontend/player_window.rb +4 -4
- data/lib/badline/frontend/screen.rb +39 -37
- data/lib/badline/frontend/sid_view.rb +1 -1
- data/lib/badline/frontend/snapshots.rb +145 -17
- data/lib/badline/frontend/sound.rb +17 -9
- data/lib/badline/frontend/timeline.rb +36 -5
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/iec_bus.rb +16 -28
- data/lib/badline/kernal_trap/drive.rb +3 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +112 -0
- data/lib/badline/kernal_trap/load.rb +24 -25
- data/lib/badline/kernal_trap/routine.rb +17 -5
- data/lib/badline/kernal_trap/save.rb +13 -23
- data/lib/badline/kernal_trap/serial.rb +38 -24
- data/lib/badline/kernal_trap.rb +1 -0
- data/lib/badline/keyboard.rb +17 -14
- data/lib/badline/machine.rb +60 -0
- data/lib/badline/media/disk_list.rb +75 -0
- data/lib/badline/media/disk_set.rb +13 -3
- data/lib/badline/media/vic20_basic.rb +54 -0
- data/lib/badline/media/vic20_cartridge.rb +74 -0
- data/lib/badline/media/vic20_media.rb +132 -0
- data/lib/badline/media.rb +39 -20
- data/lib/badline/model.rb +62 -0
- data/lib/badline/options/event.rb +10 -6
- data/lib/badline/options/help.rb +25 -13
- data/lib/badline/options/table.rb +15 -1
- data/lib/badline/options/validation.rb +63 -3
- data/lib/badline/options.rb +37 -34
- data/lib/badline/region.rb +16 -4
- data/lib/badline/reu/ram.rb +27 -0
- data/lib/badline/reu/saved_state.rb +27 -0
- data/lib/badline/roms/README +37 -0
- data/lib/badline/roms/kernal-sx64.rom +0 -0
- data/lib/badline/roms/vic20/basic.rom +0 -0
- data/lib/badline/roms/vic20/character.rom +0 -0
- data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
- data/lib/badline/sid/decimator.rb +13 -0
- data/lib/badline/sid/filter.rb +6 -3
- data/lib/badline/sid.rb +19 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/fields.rb +1 -0
- data/lib/badline/snapshot/image.rb +15 -0
- data/lib/badline/snapshot/setup.rb +20 -8
- data/lib/badline/snapshot/state.rb +15 -7
- data/lib/badline/snapshot/vice/main_cpu.rb +1 -1
- data/lib/badline/snapshot/vice/peripherals.rb +12 -2
- data/lib/badline/snapshot/vice/restore.rb +3 -1
- data/lib/badline/snapshot/vice/reu1764.rb +55 -0
- data/lib/badline/snapshot/vice/vicii.rb +59 -13
- data/lib/badline/snapshot/vice/vicii_import.rb +14 -3
- data/lib/badline/snapshot/vice.rb +25 -23
- data/lib/badline/snapshot.rb +10 -0
- data/lib/badline/storage/crt_file.rb +16 -5
- data/lib/badline/storage/disk_image.rb +3 -0
- data/lib/badline/storage/host_directory.rb +3 -0
- data/lib/badline/storage/t64.rb +10 -4
- data/lib/badline/storage/tap.rb +44 -8
- data/lib/badline/storage/unavailable.rb +3 -1
- data/lib/badline/storage.rb +5 -5
- data/lib/badline/timing.rb +12 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/shift_register.rb +43 -16
- data/lib/badline/vic/bank.rb +53 -16
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic/sprite/timing.rb +5 -9
- data/lib/badline/vic/sprite.rb +4 -3
- data/lib/badline/vic.rb +34 -15
- data/lib/badline/vic20/attachments.rb +103 -0
- data/lib/badline/vic20/bus.rb +308 -0
- data/lib/badline/vic20/cpu.rb +10 -0
- data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
- data/lib/badline/vic20/port_wiring.rb +50 -0
- data/lib/badline/vic20/saved_state.rb +152 -0
- data/lib/badline/vic20/sound/output.rb +83 -0
- data/lib/badline/vic20/sound.rb +233 -0
- data/lib/badline/vic20/user_via_ports.rb +47 -0
- data/lib/badline/vic20/vic/painter.rb +239 -0
- data/lib/badline/vic20/vic/saved_state.rb +55 -0
- data/lib/badline/vic20/vic/text_window.rb +71 -0
- data/lib/badline/vic20/vic.rb +267 -0
- data/lib/badline/vic20.rb +220 -0
- data/lib/badline.rb +2 -38
- metadata +37 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
data/doc/sid-player.md
CHANGED
|
@@ -46,7 +46,7 @@ channels.
|
|
|
46
46
|
Playback asks the device for 44.1 kHz and takes whatever rate it
|
|
47
47
|
offers, unless `--rate` says otherwise. Ctrl-C stops it. The emulator
|
|
48
48
|
window's own options, `--no-autostart`, `--writable`, `--sound`,
|
|
49
|
-
`--true-drive`, `--reu`, `--ntsc` and `--verbose`, don't apply.
|
|
49
|
+
`--true-drive`, `--reu`, `--model`, `--ntsc` and `--verbose`, don't apply.
|
|
50
50
|
|
|
51
51
|
## The window
|
|
52
52
|
|
|
@@ -86,9 +86,13 @@ first and last. `n` and `p` step to the next and previous tune. `s`
|
|
|
86
86
|
turns shuffle on and off, which plays the queue's tunes in a random
|
|
87
87
|
order, and `l` turns looping on and off, so that the end of the queue
|
|
88
88
|
goes on to its start. `,` and `.` seek 10 seconds back and forward
|
|
89
|
-
within the subtune
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
within the subtune. As a subtune plays, the player saves its state every
|
|
90
|
+
10 seconds. A seek back runs silently from the latest of those saved
|
|
91
|
+
states at or before the point asked for, or from the subtune's start
|
|
92
|
+
before the first. A seek forward runs on silently from where the
|
|
93
|
+
subtune is, or from a saved state further on, when an earlier seek back
|
|
94
|
+
left one there. The saved states last until the tune or subtune
|
|
95
|
+
changes. Space pauses and `q` quits. The status line shows which
|
|
92
96
|
modes are on, and they last until the player quits.
|
|
93
97
|
|
|
94
98
|
`--no-tui`, or output that isn't a terminal, gives plain progress output
|
data/doc/snapshots.md
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
# Snapshots
|
|
2
2
|
|
|
3
|
-
In the window, `F11`
|
|
4
|
-
`
|
|
5
|
-
|
|
6
|
-
saved
|
|
7
|
-
the
|
|
8
|
-
|
|
3
|
+
In the window, `F11` quicksaves the whole machine to one of five slots,
|
|
4
|
+
`quicksave-1.vsf` to `quicksave-5.vsf` in the `quicksaves` folder of
|
|
5
|
+
badline's data folder. It fills the empty slots first, then replaces the
|
|
6
|
+
one saved longest ago. `F12` restores the newest quicksave, or a named
|
|
7
|
+
save in the data folder's `saves` folder if one is newer, in a new
|
|
8
|
+
machine built as the saved one was. Quicksaves stay after badline quits,
|
|
9
|
+
so `F12` finds them the next time it starts. What's printed on a save or
|
|
10
|
+
a restore names the slot and the file.
|
|
11
|
+
|
|
12
|
+
A run without `--frames` also autosaves every 6,000 frames the machine
|
|
13
|
+
has run, two minutes on PAL, to `autosave-1.vsf` to `autosave-3.vsf` in
|
|
14
|
+
the `autosaves` folder, replacing the oldest. Time in the pause menu
|
|
15
|
+
doesn't count, nothing is printed, and `F12` never picks an autosave. An
|
|
16
|
+
autosave that fails warns once and stops autosaving.
|
|
17
|
+
|
|
18
|
+
The pause menu's Snapshots page quicksaves, saves under a name in the
|
|
19
|
+
`saves` folder and loads a named save. The name starts as the game's,
|
|
20
|
+
from its cartridge, disk or tape, or BASIC, with the next free number,
|
|
21
|
+
and saving over a name already taken asks first. Below those, the
|
|
22
|
+
newest quicksaves and autosaves, with the time each was saved, load
|
|
23
|
+
with a press.
|
|
24
|
+
|
|
25
|
+
The data folder is `~/Library/Application Support/badline` on macOS,
|
|
26
|
+
and `$XDG_DATA_HOME/badline` or `~/.local/share/badline` elsewhere.
|
|
27
|
+
`BADLINE_DATA_PATH` puts it somewhere else.
|
|
28
|
+
|
|
29
|
+
Both executables do this, and both open a `.vsf` given as the media,
|
|
30
|
+
in a machine built as the saved one was, whatever `--model` says.
|
|
31
|
+
`--save-snapshot FILE` saves one to `FILE` after the last frame. A
|
|
32
|
+
snapshot that fails to open leaves the machine running as it was.
|
|
9
33
|
|
|
10
34
|
## From Ruby
|
|
11
35
|
|
|
@@ -25,45 +49,67 @@ a disk or directory mounted through the traps with its open channels,
|
|
|
25
49
|
and the tape with its place on it. A restored machine runs on exactly
|
|
26
50
|
as the saved one would have.
|
|
27
51
|
|
|
28
|
-
A
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
A tape, a `.t64` archive and a disk image mounted through the traps come
|
|
53
|
+
back with their contents from the snapshot, so they need no file to
|
|
54
|
+
restore. A true drive's disk opens again from its image's path, with its
|
|
55
|
+
tracks as the drive last saw them. When that file is gone, the disk
|
|
56
|
+
comes back from the tracks alone, write-protected. A directory mounted
|
|
57
|
+
as device 8 opens again from its path. What the host holds stays the
|
|
32
58
|
host's: the keyboard, the joysticks, the mouse and paddles, sound, and
|
|
33
59
|
blocks given to `on_init` that hadn't run yet, which a restore reports.
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
machine with
|
|
37
|
-
|
|
38
|
-
a machine with
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
Every model saves this way, PAL, NTSC, old NTSC and Drean alike, and so
|
|
62
|
+
does a machine with an REU, with its RAM, its registers and a transfer
|
|
63
|
+
part way through. A snapshot only restores in the badline version that
|
|
64
|
+
wrote it, into a machine with the same chip models, region, RAM
|
|
65
|
+
expansion, REU, KERNAL and datasette. One saved before badline recorded
|
|
66
|
+
the KERNAL and the datasette restores as a machine with the C64's KERNAL
|
|
67
|
+
and a datasette.
|
|
68
|
+
|
|
69
|
+
## The VIC-20
|
|
70
|
+
|
|
71
|
+
`badline vic20` saves and restores the same way, with the same keys,
|
|
72
|
+
slots and pause menu page. A VIC-20 snapshot holds its RAM and colour
|
|
73
|
+
RAM, a cartridge's ROM, the VIC with its picture and sound, both VIAs,
|
|
74
|
+
the CPU, the tape with its place on it, the disk or directory device 8
|
|
75
|
+
serves through the traps and a true 1541, and restores into a VIC-20
|
|
76
|
+
with the same RAM expansion. Its file names its machine `VIC20`, as
|
|
77
|
+
xvic's do, and holds only the `BADLINE` module, so xvic doesn't open
|
|
78
|
+
it, and badline doesn't open xvic's. Restoring a
|
|
79
|
+
VIC-20 snapshot while a C64 runs, or a C64 one while a VIC-20 runs,
|
|
80
|
+
swaps the machine in the window.
|
|
41
81
|
|
|
42
82
|
## VICE
|
|
43
83
|
|
|
44
84
|
Snapshots use VICE's `.vsf` format. badline writes VICE's modules for
|
|
45
|
-
the CPU, RAM and CPU port, both CIAs, the SID and the VIC-II,
|
|
46
|
-
ones x64sc needs to open the file, with
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
the CPU, RAM and CPU port, both CIAs, the SID and the VIC-II, and an
|
|
86
|
+
REU's `REU1764`, plus the ones x64sc needs to open the file, with
|
|
87
|
+
nothing else attached to the cartridge port, nothing on the tape or user
|
|
88
|
+
ports and no true drive. The VIC-II module names the chip as VICE does:
|
|
89
|
+
the 6569 or 8565 on PAL, the 6567R8 or 8562 on NTSC, the 6567R56A on old
|
|
90
|
+
NTSC and the 6572 on PAL-N. A `BADLINE` module, which VICE skips, holds
|
|
91
|
+
the whole machine, and badline restores its own snapshots from it.
|
|
50
92
|
|
|
51
93
|
x64sc 3.10 opens badline's snapshots, taken at the end of the
|
|
52
|
-
instruction the CPU was in,
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
instruction the CPU was in, and of an REU transfer under way, as long
|
|
95
|
+
as its VIC-II model matches: `-model c64`, `c64c`, `ntsc`, `newntsc`,
|
|
96
|
+
`oldntsc` or `drean`, as `--model` names the machine. It takes the REU
|
|
97
|
+
from the snapshot, keeps its own drives and leaves out the cartridge,
|
|
98
|
+
the other expansions and the tape. A program x64sc runs on from badline's
|
|
99
|
+
snapshot reads the raster as badline does, cycle for cycle.
|
|
55
100
|
|
|
56
101
|
Snapshots x64sc saves open in badline through the same modules: the CPU
|
|
57
102
|
at its instruction boundary, RAM, the CPU port, the CIAs' registers,
|
|
58
|
-
timers and clocks, the SID's registers and reSID voice state,
|
|
59
|
-
VIC-II's registers, beam position, counters and colour RAM
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
timers and clocks, the SID's registers and reSID voice state, the
|
|
104
|
+
VIC-II's registers, beam position, counters and colour RAM, and an
|
|
105
|
+
REU's size, registers and RAM. The VIC-II's model builds the machine's
|
|
106
|
+
region, and the 6569R1 runs as the 6569. The VIC-II's pixel pipeline
|
|
107
|
+
starts empty. badline reads the modules x64sc 3.7 to 3.10 write, and
|
|
108
|
+
VICE's development versions' `MAINC64CPU` and `VIC-IISC`. A module
|
|
109
|
+
version it doesn't know is left out, or fails the restore for the CPU
|
|
110
|
+
and RAM. It reports the modules it leaves out, such as the 1541 drives,
|
|
111
|
+
the cartridge port, the datasette and the keyboard, and carries on
|
|
112
|
+
without them. Restored into a running machine, a VICE snapshot
|
|
67
113
|
fails for a machine built another way, such as a C64C's snapshot in a
|
|
68
114
|
C64, and otherwise takes the cartridge out and switches the machine off
|
|
69
115
|
and on.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class AddressBus
|
|
5
|
+
# The C64's PLA: what each page of the CPU's view maps to, from the
|
|
6
|
+
# CPU port's LORAM, HIRAM and CHAREN lines and the cartridge's EXROM
|
|
7
|
+
# and GAME. The bus that includes it supplies the page tables, the
|
|
8
|
+
# ROMs, the chips, and #map_ram_pages, which lays out the RAM that the
|
|
9
|
+
# ROMs, the cartridge and I/O map over.
|
|
10
|
+
module PLA
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def map_pla_pages
|
|
14
|
+
map_ram_pages
|
|
15
|
+
@ultimax ? map_ultimax_pages : map_banked_pages
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def map_banked_pages
|
|
19
|
+
map_rom_overlays
|
|
20
|
+
map_cartridge_ram
|
|
21
|
+
@write_pages.fill(@cartridge.romh_writes, 0xe0, 0x20) if @cartridge&.romh_writes
|
|
22
|
+
|
|
23
|
+
if io?
|
|
24
|
+
map_io_pages
|
|
25
|
+
elsif character?
|
|
26
|
+
@read_pages.fill(character_rom, 0xd0, 0x10)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
|
|
31
|
+
# I/O always visible and open address space everywhere else. The ROML
|
|
32
|
+
# and ROMH selects fire on writes as well, so cartridge RAM or flash in
|
|
33
|
+
# either window takes the writes there.
|
|
34
|
+
def map_ultimax_pages
|
|
35
|
+
@read_pages.fill(@open_bus, 0x10, 0xf0)
|
|
36
|
+
@write_pages.fill(@open_bus, 0x10, 0xf0)
|
|
37
|
+
@read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
|
|
38
|
+
map_ultimax_writes(@cartridge.roml, 0x80)
|
|
39
|
+
@read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
|
|
40
|
+
map_ultimax_writes(@cartridge.romh, 0xe0)
|
|
41
|
+
map_ultimax_a000
|
|
42
|
+
map_io_pages
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def map_ultimax_writes(bank, first_page)
|
|
46
|
+
@write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def map_ultimax_a000
|
|
50
|
+
return unless (window = @cartridge.ultimax_a000)
|
|
51
|
+
|
|
52
|
+
@read_pages.fill(window, 0xa0, 0x20)
|
|
53
|
+
@write_pages.fill(window, 0xa0, 0x20)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# EXROM and GAME select the cartridge whether or not it has a chip
|
|
57
|
+
# there, and an empty socket leaves the bus floating.
|
|
58
|
+
def map_rom_overlays
|
|
59
|
+
@read_pages.fill(@cartridge.roml || @open_bus, 0x80, 0x20) if roml?
|
|
60
|
+
if romh?
|
|
61
|
+
@read_pages.fill(@cartridge.romh || @open_bus, 0xa0, 0x20)
|
|
62
|
+
elsif basic?
|
|
63
|
+
@read_pages.fill(basic_rom, 0xa0, 0x20)
|
|
64
|
+
end
|
|
65
|
+
@read_pages.fill(kernal_rom, 0xe0, 0x20) if kernal?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Cartridge RAM in the ROML or ROMH window decodes writes itself,
|
|
69
|
+
# whatever the $01 lines say.
|
|
70
|
+
def map_cartridge_ram
|
|
71
|
+
return unless @cartridge&.exrom&.zero?
|
|
72
|
+
|
|
73
|
+
map_cartridge_ram_bank(@cartridge.roml, 0x80)
|
|
74
|
+
map_cartridge_ram_bank(@cartridge.romh, 0xa0) if @cartridge.game.zero?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def map_cartridge_ram_bank(bank, first_page)
|
|
78
|
+
@write_pages.fill(bank, first_page, 0x20) if bank.is_a?(Cartridge::RAMBank)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def map_io_pages
|
|
82
|
+
{
|
|
83
|
+
vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
|
|
84
|
+
cia1 => 0xdc..0xdc, cia2 => 0xdd..0xdd, @open_bus => 0xde..0xdf
|
|
85
|
+
}.each do |chip, pages|
|
|
86
|
+
pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
|
|
87
|
+
end
|
|
88
|
+
@ram_expansion.map_io(@read_pages, @write_pages)
|
|
89
|
+
@read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
|
|
90
|
+
@read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
|
|
91
|
+
map_cartridge_io if @cartridge
|
|
92
|
+
map_extra_sids unless @sid_slots.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def map_cartridge_io
|
|
96
|
+
@write_pages.fill(@cartridge, 0xde, 2)
|
|
97
|
+
@cartridge.readable_io_pages.each { |p| @read_pages[p] = @cartridge }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def basic?
|
|
101
|
+
io_port.kernal? && io_port.basic? && game_high?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def game_high?
|
|
105
|
+
@cartridge.nil? || @cartridge.game == 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def roml?
|
|
109
|
+
@cartridge&.exrom&.zero? && io_port.kernal? && io_port.basic?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def romh?
|
|
113
|
+
@cartridge&.exrom&.zero? && @cartridge.game.zero? && io_port.kernal?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# In 16K mode LORAM alone leaves $d000 as RAM, where it still maps I/O.
|
|
117
|
+
def character?
|
|
118
|
+
(io_port.kernal? || (io_port.basic? && game_high?)) && !io_port.io?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def io?
|
|
122
|
+
(io_port.basic? || io_port.kernal?) && io_port.io?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def kernal?
|
|
126
|
+
io_port.kernal?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class AddressBus
|
|
5
|
+
# The BASIC, character and KERNAL ROMs, and which KERNAL is fitted.
|
|
6
|
+
module ROMs
|
|
7
|
+
# The KERNAL ROMs a machine can be fitted with, by name: the C64's
|
|
8
|
+
# and the SX-64's.
|
|
9
|
+
KERNALS = { c64: "kernal.rom", sx64: "kernal-sx64.rom" }.freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :basic_rom, :character_rom, :kernal_rom, :kernal
|
|
12
|
+
|
|
13
|
+
# Fits the KERNAL ROM KERNALS names `name`, in place of the one before.
|
|
14
|
+
def kernal=(name)
|
|
15
|
+
raise ArgumentError, "no KERNAL named #{name}" unless KERNALS.key?(name)
|
|
16
|
+
|
|
17
|
+
@kernal = name
|
|
18
|
+
@kernal_rom = ROM.load(KERNALS[name], 0xe000)
|
|
19
|
+
update_overlays!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def load_roms
|
|
25
|
+
@basic_rom = ROM.load("basic.rom", 0xa000)
|
|
26
|
+
@character_rom = ROM.load("character.rom", 0xd000)
|
|
27
|
+
@kernal = :c64
|
|
28
|
+
@kernal_rom = ROM.load(KERNALS[:c64], 0xe000)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/badline/address_bus.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
require "badline/address_bus/saved_state"
|
|
4
4
|
require "badline/address_bus/sid_slots"
|
|
5
5
|
require "badline/address_bus/extra_sids"
|
|
6
|
-
require "badline/address_bus/
|
|
6
|
+
require "badline/address_bus/pla"
|
|
7
|
+
require "badline/address_bus/roms"
|
|
7
8
|
|
|
8
9
|
module Badline
|
|
9
10
|
# Memory layout:
|
|
@@ -33,8 +34,9 @@ module Badline
|
|
|
33
34
|
# 0xE000-0xFFFF - KERNAL ROM / Cartridge ROM (high) - 8kb
|
|
34
35
|
class AddressBus
|
|
35
36
|
include Addressable
|
|
36
|
-
include
|
|
37
|
+
include PLA
|
|
37
38
|
include ExtraSIDs
|
|
39
|
+
include ROMs
|
|
38
40
|
|
|
39
41
|
# I/O 1 and 2, and the Ultimax holes, with nothing on the bus. A read
|
|
40
42
|
# picks up the byte the VIC fetched in the preceding phi1 half-cycle,
|
|
@@ -58,7 +60,7 @@ module Badline
|
|
|
58
60
|
# See doc/pinned-behaviour.md.
|
|
59
61
|
RAM_POWER_ON = Array.new(2**16) { |addr| (((addr + 2) / 4) ^ (addr / 0x4000)).odd? ? 0xff : 0x00 }.freeze
|
|
60
62
|
|
|
61
|
-
attr_reader :io_port, :ram,
|
|
63
|
+
attr_reader :io_port, :ram,
|
|
62
64
|
:vic, :sid, :color_ram, :cia1, :cia2, :keyboard, :joystick1, :joystick2,
|
|
63
65
|
:control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram, :reu
|
|
64
66
|
|
|
@@ -71,15 +73,13 @@ module Badline
|
|
|
71
73
|
@cartridge = @reu = nil
|
|
72
74
|
@debug_register = nil
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
@character_rom = ROM.load("character.rom", 0xd000)
|
|
76
|
-
@kernal_rom = ROM.load("kernal.rom", 0xe000)
|
|
76
|
+
load_roms
|
|
77
77
|
|
|
78
78
|
@keyboard = Keyboard.new
|
|
79
79
|
@joystick1 = Joystick.new
|
|
80
80
|
@joystick2 = Joystick.new
|
|
81
81
|
@control_ports = ControlPorts.new(keyboard: @keyboard, joystick1: @joystick1, joystick2: @joystick2)
|
|
82
|
-
@vic = VIC.new(
|
|
82
|
+
@vic = VIC.new(model: vic_model, region:)
|
|
83
83
|
@cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports, model: cia_model, region:)
|
|
84
84
|
@cia2 = CIA.new(start: 0xdd00, model: cia_model, region:)
|
|
85
85
|
@control_ports.port_a_source = @cia1
|
|
@@ -92,6 +92,8 @@ module Badline
|
|
|
92
92
|
@datasette.on_sense_change { @io_port.value = port_value }
|
|
93
93
|
|
|
94
94
|
@color_ram = ColorMemory.new(@vic)
|
|
95
|
+
@vic.vic_bank.connect(cia2: @cia2, color_ram: @color_ram)
|
|
96
|
+
@vic.vic_bank.map_character_rom(character_rom)
|
|
95
97
|
@open_bus = OpenBus.new(@vic)
|
|
96
98
|
@cpu_off_bus = false
|
|
97
99
|
|
|
@@ -201,96 +203,15 @@ module Badline
|
|
|
201
203
|
def update_overlays!
|
|
202
204
|
@ultimax = @cartridge ? @cartridge.ultimax? : false
|
|
203
205
|
@phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
|
|
204
|
-
@ram_expansion.map(@read_pages, @write_pages)
|
|
205
206
|
@video_ram = @ram_expansion.video_ram
|
|
207
|
+
@vic.vic_bank.map(@video_ram, phi1_ultimax: @phi1_ultimax, ultimax: @ultimax, romh: @cartridge&.romh)
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
map_pla_pages
|
|
208
210
|
@write_pages[0xff] = @reu.trigger.wrap(@write_pages[0xff]) if @reu
|
|
209
211
|
end
|
|
210
212
|
|
|
211
|
-
def
|
|
212
|
-
|
|
213
|
-
map_cartridge_ram
|
|
214
|
-
@write_pages.fill(@cartridge.romh_writes, 0xe0, 0x20) if @cartridge&.romh_writes
|
|
215
|
-
|
|
216
|
-
if io?
|
|
217
|
-
map_io_pages
|
|
218
|
-
elsif character?
|
|
219
|
-
@read_pages.fill(character_rom, 0xd0, 0x10)
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# EXROM and GAME select the cartridge whether or not it has a chip
|
|
224
|
-
# there, and an empty socket leaves the bus floating.
|
|
225
|
-
def map_rom_overlays
|
|
226
|
-
@read_pages.fill(@cartridge.roml || @open_bus, 0x80, 0x20) if roml?
|
|
227
|
-
if romh?
|
|
228
|
-
@read_pages.fill(@cartridge.romh || @open_bus, 0xa0, 0x20)
|
|
229
|
-
elsif basic?
|
|
230
|
-
@read_pages.fill(basic_rom, 0xa0, 0x20)
|
|
231
|
-
end
|
|
232
|
-
@read_pages.fill(kernal_rom, 0xe0, 0x20) if kernal?
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Cartridge RAM in the ROML or ROMH window decodes writes itself,
|
|
236
|
-
# whatever the $01 lines say.
|
|
237
|
-
def map_cartridge_ram
|
|
238
|
-
return unless @cartridge&.exrom&.zero?
|
|
239
|
-
|
|
240
|
-
map_cartridge_ram_bank(@cartridge.roml, 0x80)
|
|
241
|
-
map_cartridge_ram_bank(@cartridge.romh, 0xa0) if @cartridge.game.zero?
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def map_cartridge_ram_bank(bank, first_page)
|
|
245
|
-
@write_pages.fill(bank, first_page, 0x20) if bank.is_a?(Cartridge::RAMBank)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def map_io_pages
|
|
249
|
-
{
|
|
250
|
-
vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
|
|
251
|
-
cia1 => 0xdc..0xdc, cia2 => 0xdd..0xdd, @open_bus => 0xde..0xdf
|
|
252
|
-
}.each do |chip, pages|
|
|
253
|
-
pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
|
|
254
|
-
end
|
|
255
|
-
@ram_expansion.map_io(@read_pages, @write_pages)
|
|
256
|
-
@read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
|
|
257
|
-
@read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
|
|
258
|
-
map_cartridge_io if @cartridge
|
|
259
|
-
map_extra_sids unless @sid_slots.empty?
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def map_cartridge_io
|
|
263
|
-
@write_pages.fill(@cartridge, 0xde, 2)
|
|
264
|
-
@cartridge.readable_io_pages.each { |p| @read_pages[p] = @cartridge }
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def basic?
|
|
268
|
-
io_port.kernal? && io_port.basic? && game_high?
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def game_high?
|
|
272
|
-
@cartridge.nil? || @cartridge.game == 1
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def roml?
|
|
276
|
-
@cartridge&.exrom&.zero? && io_port.kernal? && io_port.basic?
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def romh?
|
|
280
|
-
@cartridge&.exrom&.zero? && @cartridge.game.zero? && io_port.kernal?
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# In 16K mode LORAM alone leaves $d000 as RAM, where it still maps I/O.
|
|
284
|
-
def character?
|
|
285
|
-
(io_port.kernal? || (io_port.basic? && game_high?)) && !io_port.io?
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def io?
|
|
289
|
-
(io_port.basic? || io_port.kernal?) && io_port.io?
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
def kernal?
|
|
293
|
-
io_port.kernal?
|
|
213
|
+
def map_ram_pages
|
|
214
|
+
@ram_expansion.map(@read_pages, @write_pages)
|
|
294
215
|
end
|
|
295
216
|
end
|
|
296
217
|
end
|
|
@@ -74,6 +74,30 @@ module Badline
|
|
|
74
74
|
settle
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# The rig between frames: the bus and its chips, the extra SIDs, the
|
|
78
|
+
# CPU, and the call waiting for the idle loop.
|
|
79
|
+
def save_state(out)
|
|
80
|
+
out.marker("BARE PLAYER")
|
|
81
|
+
@bus.save_state(out)
|
|
82
|
+
@extras.each { |sid| sid.save_state(out) }
|
|
83
|
+
@cpu.save_state(out)
|
|
84
|
+
out.optional_int(@pending).int(@argument).boolean(@idle)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Takes a rig that hasn't started to where #save_state found one, in
|
|
88
|
+
# place of #start.
|
|
89
|
+
def load_state(input)
|
|
90
|
+
input.marker("BARE PLAYER")
|
|
91
|
+
@bus.load_state(input)
|
|
92
|
+
@extras.each { |sid| sid.load_state(input) }
|
|
93
|
+
@cpu.load_state(input)
|
|
94
|
+
@pending = input.optional_int
|
|
95
|
+
@argument = input.int
|
|
96
|
+
@idle = input.boolean?
|
|
97
|
+
@stereo.synthesize!
|
|
98
|
+
install_dispatch
|
|
99
|
+
end
|
|
100
|
+
|
|
77
101
|
# Advances one call of play, or `budget` cycles if that is shorter,
|
|
78
102
|
# then yields whatever the SIDs recorded over it, in stereo. Returns
|
|
79
103
|
# the cycles advanced.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Audio
|
|
5
|
+
# The states a subtune's renderer saved as it played, at the first
|
|
6
|
+
# frame end past every `every` seconds, so that a seek starts from the
|
|
7
|
+
# latest one at or before where it goes rather than from the subtune's
|
|
8
|
+
# start. Whoever plays the subtune keeps them across its renderers, and
|
|
9
|
+
# drops them for another subtune.
|
|
10
|
+
class Checkpoints
|
|
11
|
+
EVERY = 10.0
|
|
12
|
+
|
|
13
|
+
def initialize(every: EVERY)
|
|
14
|
+
@every = every
|
|
15
|
+
@cycles = []
|
|
16
|
+
@states = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Whether the frame ending `cycles` into the subtune is the first past
|
|
20
|
+
# the next mark.
|
|
21
|
+
def due?(cycles, clock_hz) = cycles >= ((@cycles.length + 1) * @every * clock_hz).round
|
|
22
|
+
|
|
23
|
+
def keep(cycles, state)
|
|
24
|
+
@cycles << cycles
|
|
25
|
+
@states << state
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The state saved latest at or before `cycles`, or nil.
|
|
29
|
+
def latest(cycles)
|
|
30
|
+
index = @cycles.rindex { |at| at <= cycles }
|
|
31
|
+
index.nil? ? nil : @states[index]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The cycles into the subtune of the state saved latest at or before
|
|
35
|
+
# `cycles`, or 0 for the start.
|
|
36
|
+
def reach(cycles)
|
|
37
|
+
index = @cycles.rindex { |at| at <= cycles }
|
|
38
|
+
index.nil? ? 0 : @cycles[index]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -7,9 +7,11 @@ module Badline
|
|
|
7
7
|
# turn the queue's shuffle, loop and all-subtunes modes on and off, and
|
|
8
8
|
# quit, and the status line follows along. Seeking runs the subtune
|
|
9
9
|
# silently up to where it was asked to go: on from where it is to go
|
|
10
|
-
# forward, and from the
|
|
11
|
-
#
|
|
12
|
-
# the
|
|
10
|
+
# forward, and from the latest of its Checkpoints at or before there to
|
|
11
|
+
# go back, or to go forward when that checkpoint is further on than
|
|
12
|
+
# where the subtune is. A subtune that plays to its end moves on as
|
|
13
|
+
# Media::Queue#advance says. It stops at the end of the queue, on q, or
|
|
14
|
+
# on Ctrl-C.
|
|
13
15
|
#
|
|
14
16
|
# `renderer` builds the renderer for an entry's subtune at the sink's
|
|
15
17
|
# rate, or returns nil for an entry that can't play, which is skipped
|
|
@@ -34,6 +36,9 @@ module Badline
|
|
|
34
36
|
@backward = false
|
|
35
37
|
@below_at = -BELOW_NOTE
|
|
36
38
|
@from = 0.0
|
|
39
|
+
@entry = nil
|
|
40
|
+
@subtune = 0
|
|
41
|
+
@checkpoints = Checkpoints.new
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
# Returns the result of the last subtune's Playback#play, or :unplayable
|
|
@@ -77,6 +82,7 @@ module Badline
|
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
def play(entry, subtune)
|
|
85
|
+
@checkpoints = Checkpoints.new unless entry.equal?(@entry) && subtune == @subtune
|
|
80
86
|
@entry = entry
|
|
81
87
|
@subtune = subtune
|
|
82
88
|
@elapsed = 0.0
|
|
@@ -84,6 +90,7 @@ module Badline
|
|
|
84
90
|
return :unplayable if renderer.nil?
|
|
85
91
|
|
|
86
92
|
renderer.from = @from
|
|
93
|
+
renderer.checkpoints = @checkpoints
|
|
87
94
|
@streaming = renderer
|
|
88
95
|
@from = 0.0
|
|
89
96
|
@backward = false
|
|
@@ -127,7 +134,7 @@ module Badline
|
|
|
127
134
|
|
|
128
135
|
def seek(seconds)
|
|
129
136
|
target = seconds.clamp(0.0, [@length.call(@entry, @subtune) - 1.0, 0.0].max)
|
|
130
|
-
return @streaming.from = target if target > @elapsed
|
|
137
|
+
return @streaming.from = target if target > @elapsed && !@streaming.checkpoint_ahead?(target)
|
|
131
138
|
|
|
132
139
|
@from = target
|
|
133
140
|
skip
|
|
@@ -39,6 +39,22 @@ module Badline
|
|
|
39
39
|
|
|
40
40
|
def clock_hz = @computer.region.clock_hz
|
|
41
41
|
|
|
42
|
+
# The whole machine, as Computer#snapshot takes it, and the extra SIDs,
|
|
43
|
+
# once it plays.
|
|
44
|
+
def save_state(out)
|
|
45
|
+
@computer.save_state(out)
|
|
46
|
+
@extras.each { |sid| sid.save_state(out) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Takes a machine that hasn't started to where #save_state found one,
|
|
50
|
+
# in place of #start.
|
|
51
|
+
def load_state(input)
|
|
52
|
+
@computer.load_state(input)
|
|
53
|
+
@extras.each { |sid| sid.load_state(input) }
|
|
54
|
+
@stereo.synthesize!
|
|
55
|
+
@started = true
|
|
56
|
+
end
|
|
57
|
+
|
|
42
58
|
# Advances one frame of the machine, or `budget` cycles if that is
|
|
43
59
|
# shorter, then yields whatever the SIDs recorded over it, in stereo.
|
|
44
60
|
# Returns the cycles advanced.
|