badline 0.13.0 → 0.15.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 +72 -1
- data/CLAUDE.md +42 -12
- data/CONTRIBUTING.md +4 -3
- data/README.md +76 -13
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +174 -0
- data/doc/snapshot-format.md +1197 -0
- data/doc/snapshots.md +90 -40
- data/lib/badline/audio/bare_player.rb +46 -17
- data/lib/badline/audio/machine_player.rb +6 -10
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +7 -6
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +36 -4
- data/lib/badline/c128/drives.rb +6 -25
- data/lib/badline/c128/keyed_modules.rb +117 -0
- data/lib/badline/c128/keyed_state.rb +91 -0
- data/lib/badline/c128/keys.rb +9 -0
- data/lib/badline/c128/mmu.rb +9 -8
- data/lib/badline/c128/mmu_pages.rb +27 -0
- data/lib/badline/c128/modes.rb +19 -9
- data/lib/badline/c128/z80_turns.rb +1 -1
- data/lib/badline/c128.rb +8 -8
- data/lib/badline/c64/attachments.rb +0 -44
- data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
- data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
- data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
- data/lib/badline/c64/bus/debug_register.rb +32 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +3 -1
- data/lib/badline/c64/cartridge/action_replay.rb +6 -15
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
- data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
- data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
- data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +2 -11
- data/lib/badline/c64/cartridge/super_games.rb +3 -12
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
- data/lib/badline/c64/cartridge.rb +4 -30
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +115 -0
- data/lib/badline/c64/keyed_state.rb +68 -0
- data/lib/badline/c64/reu/dma.rb +0 -4
- data/lib/badline/c64/reu/keyed_state.rb +140 -0
- data/lib/badline/c64/reu/ram.rb +0 -15
- data/lib/badline/c64/reu.rb +1 -3
- data/lib/badline/c64/true_drives.rb +56 -5
- data/lib/badline/c64.rb +7 -3
- data/lib/badline/chips/cia/interrupt_register.rb +0 -17
- data/lib/badline/chips/cia/keyed_parts.rb +213 -0
- data/lib/badline/chips/cia/serial.rb +0 -4
- data/lib/badline/chips/cia/time_of_day.rb +0 -22
- data/lib/badline/chips/cia/timer.rb +0 -20
- data/lib/badline/chips/cia.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +18 -2
- data/lib/badline/chips/cpu/keyed_state.rb +177 -0
- data/lib/badline/chips/cpu/microcode.rb +16 -0
- data/lib/badline/chips/cpu/port.rb +20 -3
- data/lib/badline/chips/cpu.rb +5 -0
- data/lib/badline/chips/sid/decimator.rb +10 -8
- data/lib/badline/chips/sid/envelope.rb +1 -6
- data/lib/badline/chips/sid/filter.rb +0 -14
- data/lib/badline/chips/sid/keyed_parts.rb +286 -0
- data/lib/badline/chips/sid/voice.rb +0 -10
- data/lib/badline/chips/sid/waveform.rb +0 -2
- data/lib/badline/chips/sid.rb +1 -2
- data/lib/badline/chips/ted/beam.rb +118 -0
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +190 -0
- data/lib/badline/chips/tia.rb +61 -0
- data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
- data/lib/badline/chips/vdc/painter.rb +0 -12
- data/lib/badline/chips/vdc/raster.rb +0 -16
- data/lib/badline/chips/vdc/window.rb +0 -9
- data/lib/badline/chips/vdc.rb +1 -2
- data/lib/badline/chips/via/control_lines.rb +0 -15
- data/lib/badline/chips/via/interrupt_register.rb +0 -9
- data/lib/badline/chips/via/keyed_parts.rb +163 -0
- data/lib/badline/chips/via/shift_register.rb +0 -14
- data/lib/badline/chips/via/timer1.rb +0 -13
- data/lib/badline/chips/via/timer2.rb +0 -18
- data/lib/badline/chips/via.rb +1 -2
- data/lib/badline/chips/vic/border_mask.rb +0 -10
- data/lib/badline/chips/vic/collisions.rb +0 -11
- data/lib/badline/chips/vic/color_patches.rb +0 -11
- data/lib/badline/chips/vic/display_state.rb +0 -19
- data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
- data/lib/badline/chips/vic/keyed_parts.rb +228 -0
- data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
- data/lib/badline/chips/vic/keyed_state.rb +102 -0
- data/lib/badline/chips/vic/register_log.rb +0 -18
- data/lib/badline/chips/vic/registers.rb +0 -9
- data/lib/badline/chips/vic/sequencer.rb +0 -2
- data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
- data/lib/badline/chips/vic/sprite.rb +0 -2
- data/lib/badline/chips/vic/sprites.rb +0 -3
- data/lib/badline/chips/vic.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
- data/lib/badline/chips/vic_i/painter.rb +0 -4
- data/lib/badline/chips/vic_i/sound.rb +38 -46
- data/lib/badline/chips/vic_i.rb +2 -2
- data/lib/badline/chips/z80/core.rb +2 -2
- data/lib/badline/chips/z80/keyed_state.rb +73 -0
- data/lib/badline/chips.rb +10 -0
- data/lib/badline/core.rb +4 -21
- data/lib/badline/drives/datasette/keyed_state.rb +43 -0
- data/lib/badline/drives/datasette.rb +62 -12
- data/lib/badline/drives/engine/core.rb +3 -0
- data/lib/badline/drives/engine/serial_lines.rb +4 -7
- data/lib/badline/drives/model1541/bus.rb +0 -13
- data/lib/badline/drives/model1541/disk.rb +9 -8
- data/lib/badline/drives/model1541/keyed_state.rb +251 -0
- data/lib/badline/drives/model1541/mechanism.rb +0 -2
- data/lib/badline/drives/model1541.rb +1 -2
- data/lib/badline/drives/model1571/bus.rb +0 -13
- data/lib/badline/drives/model1571/keyed_state.rb +108 -0
- data/lib/badline/drives/model1571/wd1770.rb +0 -14
- data/lib/badline/drives/model1571.rb +1 -2
- data/lib/badline/drives/model1581/bus.rb +0 -13
- data/lib/badline/drives/model1581/disk.rb +0 -4
- data/lib/badline/drives/model1581/keyed_state.rb +280 -0
- data/lib/badline/drives/model1581/mechanism.rb +0 -21
- data/lib/badline/drives/model1581/slot.rb +0 -18
- data/lib/badline/drives/model1581/wd1772.rb +0 -2
- data/lib/badline/drives/model1581.rb +2 -2
- data/lib/badline/drives.rb +8 -0
- data/lib/badline/frontend/app.rb +3 -3
- data/lib/badline/frontend/boot.rb +4 -2
- data/lib/badline/frontend/controls.rb +25 -5
- data/lib/badline/frontend/drive_led.rb +44 -21
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
- data/lib/badline/frontend/menu/menu_media.rb +13 -3
- data/lib/badline/frontend/menu/menu_pages.rb +8 -8
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/snapshots.rb +12 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +43 -12
- data/lib/badline/input/matrix_typist.rb +98 -0
- data/lib/badline/input.rb +1 -0
- data/lib/badline/kernal_trap/chrout.rb +45 -0
- data/lib/badline/kernal_trap/dos/channel.rb +0 -27
- data/lib/badline/kernal_trap/dos/channels.rb +0 -25
- data/lib/badline/kernal_trap/dos/memory.rb +0 -9
- data/lib/badline/kernal_trap/dos/status.rb +0 -8
- data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
- data/lib/badline/kernal_trap/dos.rb +0 -19
- data/lib/badline/kernal_trap/file_routine.rb +4 -4
- data/lib/badline/kernal_trap/keyed_state.rb +150 -0
- data/lib/badline/kernal_trap/layout.rb +75 -11
- data/lib/badline/kernal_trap/load.rb +14 -16
- data/lib/badline/kernal_trap/routine.rb +12 -0
- data/lib/badline/kernal_trap/save.rb +8 -18
- data/lib/badline/kernal_trap/serial.rb +1 -17
- data/lib/badline/kernal_trap.rb +2 -0
- data/lib/badline/machine/keyboard_buffer.rb +49 -0
- data/lib/badline/machine/settling.rb +29 -0
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media.rb +33 -12
- data/lib/badline/memory.rb +3 -6
- data/lib/badline/options/family.rb +6 -2
- data/lib/badline/options/help.rb +66 -16
- data/lib/badline/options/table.rb +17 -2
- data/lib/badline/options/validation.rb +26 -2
- data/lib/badline/options.rb +42 -9
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +375 -0
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/cpu.rb +10 -0
- data/lib/badline/plus4/cpu_port.rb +93 -0
- data/lib/badline/plus4/keyboard_port.rb +76 -0
- data/lib/badline/plus4/keyed_modules.rb +99 -0
- data/lib/badline/plus4/keyed_parts.rb +143 -0
- data/lib/badline/plus4/keyed_state.rb +41 -0
- data/lib/badline/plus4/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +240 -0
- data/lib/badline/roms/README +38 -0
- data/lib/badline/roms/plus4/basic.rom +0 -0
- data/lib/badline/roms/plus4/function-high.rom +0 -0
- data/lib/badline/roms/plus4/function-low.rom +0 -0
- data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/c128_setup.rb +9 -20
- data/lib/badline/snapshot/c64_setup.rb +28 -24
- data/lib/badline/snapshot/container.rb +6 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
- data/lib/badline/snapshot/field_doc.rb +148 -0
- data/lib/badline/snapshot/image.rb +143 -28
- data/lib/badline/snapshot/keyed/cursor.rb +40 -0
- data/lib/badline/snapshot/keyed/modules.rb +66 -0
- data/lib/badline/snapshot/keyed/reader.rb +188 -0
- data/lib/badline/snapshot/keyed/recorder.rb +88 -0
- data/lib/badline/snapshot/keyed/report.rb +26 -0
- data/lib/badline/snapshot/keyed/writer.rb +55 -0
- data/lib/badline/snapshot/keyed.rb +64 -0
- data/lib/badline/snapshot/machine_state.rb +17 -64
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot/storage_setup.rb +29 -34
- data/lib/badline/snapshot/vic20_setup.rb +6 -15
- data/lib/badline/snapshot/vice/reu1764.rb +2 -2
- data/lib/badline/snapshot/vice.rb +7 -31
- data/lib/badline/snapshot.rb +48 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +9 -4
- data/lib/badline/storage/tap.rb +57 -20
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +3 -0
- data/lib/badline/vic20/bus/keyed_state.rb +52 -0
- data/lib/badline/vic20/bus.rb +1 -2
- data/lib/badline/vic20/keyed_modules.rb +73 -0
- data/lib/badline/vic20/keyed_state.rb +45 -0
- data/lib/badline/vic20.rb +5 -5
- data/lib/badline.rb +1 -0
- metadata +75 -42
- data/lib/badline/c128/bus/saved_state.rb +0 -53
- data/lib/badline/c128/saved_state.rb +0 -117
- data/lib/badline/c64/bus/saved_state.rb +0 -68
- data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
- data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
- data/lib/badline/c64/cartridge/saved_state.rb +0 -63
- data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
- data/lib/badline/c64/reu/saved_state.rb +0 -55
- data/lib/badline/c64/saved_state.rb +0 -112
- data/lib/badline/chips/cia/saved_state.rb +0 -49
- data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
- data/lib/badline/chips/cpu/saved_state.rb +0 -117
- data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
- data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
- data/lib/badline/chips/sid/saved_state.rb +0 -52
- data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
- data/lib/badline/chips/vdc/saved_state.rb +0 -36
- data/lib/badline/chips/via/saved_state.rb +0 -44
- data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
- data/lib/badline/chips/vic/saved_state.rb +0 -82
- data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
- data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
- data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
- data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
- data/lib/badline/chips/vic_i/saved_state.rb +0 -55
- data/lib/badline/chips/z80/saved_state.rb +0 -71
- data/lib/badline/chrout_trap.rb +0 -43
- data/lib/badline/debug_register.rb +0 -28
- data/lib/badline/drives/datasette/saved_state.rb +0 -37
- data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
- data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
- data/lib/badline/drives/model1541/saved_state.rb +0 -44
- data/lib/badline/drives/model1571/saved_state.rb +0 -47
- data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
- data/lib/badline/drives/model1581/saved_state.rb +0 -42
- data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
- data/lib/badline/keyboard_buffer.rb +0 -33
- data/lib/badline/snapshot/setup.rb +0 -31
- data/lib/badline/snapshot/state.rb +0 -179
- data/lib/badline/vic20/bus/saved_state.rb +0 -35
- data/lib/badline/vic20/saved_state.rb +0 -110
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/ted/sound/output"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Chips
|
|
7
|
+
class TED
|
|
8
|
+
# The TED's sound: two voices, the second a square wave or noise.
|
|
9
|
+
# Voice 1's 10-bit frequency is $FF0E with $FF12 bits 0-1 above it,
|
|
10
|
+
# voice 2's $FF0F with $FF10 bits 0-1. $FF11 holds the volume in bits
|
|
11
|
+
# 0-3 (0 silent, 8 and above the loudest), voice 1 on in bit 4, voice
|
|
12
|
+
# 2's square wave in bit 5 and its noise in bit 6 (the square wins if
|
|
13
|
+
# both are set), and the reload bit in bit 7.
|
|
14
|
+
#
|
|
15
|
+
# Each voice counts once every 4 cycles, and its output toggles every
|
|
16
|
+
# 1023 - x counts for a frequency x, so a square wave of the clock
|
|
17
|
+
# over 8 * (1023 - x). $3FF counts 1,024 between toggles, and $3FE
|
|
18
|
+
# stops the voice after its next toggle, with its output high. A new
|
|
19
|
+
# frequency takes hold at the next toggle. The voices count whether
|
|
20
|
+
# they are on or not.
|
|
21
|
+
#
|
|
22
|
+
# The noise is an 8-bit shift register that shifts at each of voice
|
|
23
|
+
# 2's toggles: bits 1, 4, 5 and 7 XORed go in at bit 0, and bit 1 is
|
|
24
|
+
# the noise's output.
|
|
25
|
+
#
|
|
26
|
+
# While the reload bit is set, the counters hold at their frequencies,
|
|
27
|
+
# the outputs stay high and the noise's register holds all ones, with
|
|
28
|
+
# the noise silent. Writing it clear starts each voice on a full high
|
|
29
|
+
# half-wave. Samples play this way, with the voices held high and the
|
|
30
|
+
# volume as a 9-level D/A converter.
|
|
31
|
+
#
|
|
32
|
+
# The output takes a level from the volume and how many voices are on
|
|
33
|
+
# and high. The levels are measured from xplus4 recordings, whose two
|
|
34
|
+
# voices together come out above twice one.
|
|
35
|
+
#
|
|
36
|
+
# The voices catch up to the current cycle whenever a write lands, the
|
|
37
|
+
# samples are drained or #catch_up! asks, whether or not #record
|
|
38
|
+
# collects the output, so their state doesn't depend on whether the
|
|
39
|
+
# host plays it.
|
|
40
|
+
class Sound
|
|
41
|
+
VOICES = 2
|
|
42
|
+
CYCLES_PER_COUNT = 4
|
|
43
|
+
|
|
44
|
+
# The frequency that stops a voice with its output high.
|
|
45
|
+
HELD = 0x3fe
|
|
46
|
+
|
|
47
|
+
NOISE_TAPS = 0xb2
|
|
48
|
+
NOISE_PERIOD = 255
|
|
49
|
+
|
|
50
|
+
# The output's level for each volume 0-8, with no voice, one voice
|
|
51
|
+
# and both voices high.
|
|
52
|
+
LEVELS = [
|
|
53
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
54
|
+
0, 586, 1610, 2634, 3658, 4682, 5706, 6730, 7754,
|
|
55
|
+
0, 1172, 3284, 5526, 7984, 10_658, 13_548, 16_654, 19_976
|
|
56
|
+
].freeze
|
|
57
|
+
|
|
58
|
+
# Far enough ahead that a voice stopped at $3FE, or held by the
|
|
59
|
+
# reload bit, never toggles.
|
|
60
|
+
NEVER = 1 << 60
|
|
61
|
+
|
|
62
|
+
attr_reader :volume, :control
|
|
63
|
+
|
|
64
|
+
# `clock` answers the machine's cycles, `clock_hz` the rate they run at.
|
|
65
|
+
def initialize(clock, clock_hz)
|
|
66
|
+
@clock = clock
|
|
67
|
+
@clock_hz = clock_hz
|
|
68
|
+
@output = nil
|
|
69
|
+
@time = 0
|
|
70
|
+
@frequencies = [0, 0]
|
|
71
|
+
@intervals = [0, 0]
|
|
72
|
+
@phases = [1, 1]
|
|
73
|
+
@next_toggles = [0, 0]
|
|
74
|
+
power_on!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def model = :mos7360
|
|
78
|
+
|
|
79
|
+
# The power-on state: every register clear, both voices starting on
|
|
80
|
+
# a high half-wave and the noise's register full of ones.
|
|
81
|
+
def power_on!
|
|
82
|
+
now = @clock.cycles
|
|
83
|
+
run_to(now)
|
|
84
|
+
@volume = 0
|
|
85
|
+
@control = 0
|
|
86
|
+
@reloading = false
|
|
87
|
+
@noise = 0xff
|
|
88
|
+
VOICES.times do |voice|
|
|
89
|
+
@frequencies[voice] = 0
|
|
90
|
+
@intervals[voice] = interval_for(0)
|
|
91
|
+
@phases[voice] = 1
|
|
92
|
+
@next_toggles[voice] = now + @intervals[voice]
|
|
93
|
+
end
|
|
94
|
+
@level = level_now
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Starts collecting the output, averaged down to `rate` samples a
|
|
98
|
+
# second, for #drain_samples. `clock_hz` is the rate the cycles are
|
|
99
|
+
# taken to run at.
|
|
100
|
+
def record(rate:, clock_hz: @clock_hz)
|
|
101
|
+
run_to(@clock.cycles)
|
|
102
|
+
@output = Output.new(clock_hz:, rate:, level: @level)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def recording? = !@output.nil?
|
|
106
|
+
|
|
107
|
+
# Runs the voices up to the current cycle.
|
|
108
|
+
def catch_up! = run_to(@clock.cycles)
|
|
109
|
+
|
|
110
|
+
# The samples recorded since the last drain.
|
|
111
|
+
def drain_samples
|
|
112
|
+
output = @output
|
|
113
|
+
return [] if output.nil?
|
|
114
|
+
|
|
115
|
+
run_to(@clock.cycles)
|
|
116
|
+
output.drain
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# A write to $FF0E-$FF12, which takes hold from the next cycle.
|
|
120
|
+
def write(register, value)
|
|
121
|
+
run_to(@clock.cycles + 1)
|
|
122
|
+
case register
|
|
123
|
+
when 0x0e then tune(0, (@frequencies[0] & 0x300) | value)
|
|
124
|
+
when 0x0f then tune(1, (@frequencies[1] & 0x300) | value)
|
|
125
|
+
when 0x10 then tune(1, (@frequencies[1] & 0xff) | ((value & 0x03) << 8))
|
|
126
|
+
when 0x11 then write_control(value)
|
|
127
|
+
when 0x12 then tune(0, (@frequencies[0] & 0xff) | ((value & 0x03) << 8))
|
|
128
|
+
end
|
|
129
|
+
@level = level_now
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def frequency(voice) = @frequencies[voice]
|
|
133
|
+
|
|
134
|
+
def reloading? = @reloading
|
|
135
|
+
|
|
136
|
+
# A voice's square-wave output as of the current cycle, 1 high.
|
|
137
|
+
def phase(voice)
|
|
138
|
+
run_to(@clock.cycles)
|
|
139
|
+
@phases[voice]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The noise's shift register as of the current cycle.
|
|
143
|
+
def noise
|
|
144
|
+
run_to(@clock.cycles)
|
|
145
|
+
@noise
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The output's level as of the current cycle.
|
|
149
|
+
def level
|
|
150
|
+
run_to(@clock.cycles)
|
|
151
|
+
@level
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
# Runs the voices up to the start of cycle `target`, the output holding
|
|
157
|
+
# each level for the cycles it lasts.
|
|
158
|
+
def run_to(target)
|
|
159
|
+
time = @time
|
|
160
|
+
return if target <= time
|
|
161
|
+
|
|
162
|
+
output = @output
|
|
163
|
+
return skip_to(target) if output.nil?
|
|
164
|
+
|
|
165
|
+
following = next_toggle
|
|
166
|
+
while following < target
|
|
167
|
+
output.hold_level(@level, following - time)
|
|
168
|
+
time = following
|
|
169
|
+
toggle_due(time)
|
|
170
|
+
@level = level_now
|
|
171
|
+
following = next_toggle
|
|
172
|
+
end
|
|
173
|
+
output.hold_level(@level, target - time)
|
|
174
|
+
@time = target
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Moves each voice through its toggles before `target` at once,
|
|
178
|
+
# the noise by as many shifts as voice 2 toggles, less whole turns
|
|
179
|
+
# of its sequence.
|
|
180
|
+
def skip_to(target)
|
|
181
|
+
VOICES.times do |voice|
|
|
182
|
+
due = @next_toggles[voice]
|
|
183
|
+
next if due >= target
|
|
184
|
+
|
|
185
|
+
interval = @intervals[voice]
|
|
186
|
+
toggles = ((target - 1 - due) / interval) + 1
|
|
187
|
+
@next_toggles[voice] = due + (toggles * interval)
|
|
188
|
+
@phases[voice] ^= toggles & 1
|
|
189
|
+
next unless voice == 1
|
|
190
|
+
|
|
191
|
+
(toggles % NOISE_PERIOD).times { shift_noise }
|
|
192
|
+
end
|
|
193
|
+
@level = level_now
|
|
194
|
+
@time = target
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def next_toggle
|
|
198
|
+
toggles = @next_toggles
|
|
199
|
+
following = toggles[0]
|
|
200
|
+
following = toggles[1] if toggles[1] < following
|
|
201
|
+
following
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def toggle_due(time)
|
|
205
|
+
toggles = @next_toggles
|
|
206
|
+
VOICES.times do |voice|
|
|
207
|
+
next unless toggles[voice] == time
|
|
208
|
+
|
|
209
|
+
@phases[voice] ^= 1
|
|
210
|
+
toggles[voice] = time + @intervals[voice]
|
|
211
|
+
shift_noise if voice == 1
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def shift_noise
|
|
216
|
+
noise = @noise
|
|
217
|
+
taps = noise & NOISE_TAPS
|
|
218
|
+
taps ^= taps >> 4
|
|
219
|
+
taps ^= taps >> 2
|
|
220
|
+
taps ^= taps >> 1
|
|
221
|
+
@noise = ((noise << 1) | (taps & 1)) & 0xff
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# The cycles between a voice's toggles at frequency `value`, and
|
|
225
|
+
# never again after the next one at $3FE.
|
|
226
|
+
def interval_for(value)
|
|
227
|
+
return NEVER if value == HELD
|
|
228
|
+
|
|
229
|
+
(((HELD - value) & 0x3ff) + 1) * CYCLES_PER_COUNT
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# A new frequency for `voice`, which takes hold at its next toggle. A
|
|
233
|
+
# voice stopped at $3FE starts counting now.
|
|
234
|
+
def tune(voice, value)
|
|
235
|
+
@frequencies[voice] = value
|
|
236
|
+
@intervals[voice] = interval_for(value)
|
|
237
|
+
return if @reloading || @next_toggles[voice] < NEVER
|
|
238
|
+
|
|
239
|
+
@next_toggles[voice] = @time + @intervals[voice]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def write_control(value)
|
|
243
|
+
reload = value >= 0x80
|
|
244
|
+
if reload && !@reloading
|
|
245
|
+
hold_voices
|
|
246
|
+
elsif !reload && @reloading
|
|
247
|
+
release_voices
|
|
248
|
+
end
|
|
249
|
+
@reloading = reload
|
|
250
|
+
@control = value
|
|
251
|
+
volume = value & 0x0f
|
|
252
|
+
volume = 8 if volume > 8
|
|
253
|
+
@volume = volume
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def hold_voices
|
|
257
|
+
@phases[0] = 1
|
|
258
|
+
@phases[1] = 1
|
|
259
|
+
@noise = 0xff
|
|
260
|
+
@next_toggles[0] = NEVER
|
|
261
|
+
@next_toggles[1] = NEVER
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def release_voices
|
|
265
|
+
VOICES.times { |voice| @next_toggles[voice] = @time + @intervals[voice] }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def level_now
|
|
269
|
+
control = @control
|
|
270
|
+
high = 0
|
|
271
|
+
high += 1 if control.anybits?(0x10) && high?(0)
|
|
272
|
+
if control.anybits?(0x20)
|
|
273
|
+
high += 1 if high?(1)
|
|
274
|
+
elsif control.anybits?(0x40) && !@reloading
|
|
275
|
+
high += 1 if @noise.anybits?(0x02) || @frequencies[1] == HELD
|
|
276
|
+
end
|
|
277
|
+
LEVELS[(high * 9) + @volume]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def high?(voice) = @phases[voice] == 1 || @frequencies[voice] == HELD
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
# The three 16-bit timers at $FF00-$FF05, which count down once a
|
|
7
|
+
# single clock cycle whatever clock the CPU runs at. Each raises its
|
|
8
|
+
# interrupt flag as it counts down to zero. Timer 1's next count then
|
|
9
|
+
# loads the value last written to it, and timers 2 and 3 roll over to
|
|
10
|
+
# $FFFF.
|
|
11
|
+
#
|
|
12
|
+
# A write to a timer's low byte stops it until its high byte is
|
|
13
|
+
# written. Timer 1's writes set its reload value and its count
|
|
14
|
+
# together, and timers 2 and 3's set the count.
|
|
15
|
+
module Timers
|
|
16
|
+
# The interrupt flag each timer raises in $FF09.
|
|
17
|
+
TIMER_FLAGS = [0x08, 0x10, 0x40].freeze
|
|
18
|
+
|
|
19
|
+
# The counts, the reload value of timer 1, and which timers run.
|
|
20
|
+
attr_reader :timer_counts, :timer_running
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def reset_timers
|
|
25
|
+
@timer_counts = [0, 0, 0]
|
|
26
|
+
@timer_running = [false, false, false]
|
|
27
|
+
@timer1_reload = 0
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def count_timers
|
|
31
|
+
i = 0
|
|
32
|
+
while i < 3
|
|
33
|
+
count_timer(i) if @timer_running[i]
|
|
34
|
+
i += 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def count_timer(index)
|
|
39
|
+
count = @timer_counts[index]
|
|
40
|
+
if count.zero? && index.zero?
|
|
41
|
+
@timer_counts[0] = @timer1_reload
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
count = (count - 1) & 0xffff
|
|
46
|
+
@timer_counts[index] = count
|
|
47
|
+
@interrupt_flags |= TIMER_FLAGS[index] if count.zero?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_timer(register)
|
|
51
|
+
count = @timer_counts[register >> 1]
|
|
52
|
+
register.even? ? count & 0xff : count >> 8
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def write_timer(register, value)
|
|
56
|
+
index = register >> 1
|
|
57
|
+
count = @timer_counts[index]
|
|
58
|
+
count = register.even? ? (count & 0xff00) | value : (count & 0x00ff) | (value << 8)
|
|
59
|
+
@timer_counts[index] = count
|
|
60
|
+
@timer1_reload = count if index.zero?
|
|
61
|
+
@timer_running[index] = register.odd?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/ted/timers"
|
|
4
|
+
require "badline/chips/ted/beam"
|
|
5
|
+
require "badline/chips/ted/fetches"
|
|
6
|
+
require "badline/chips/ted/palette"
|
|
7
|
+
require "badline/chips/ted/modes"
|
|
8
|
+
require "badline/chips/ted/picture"
|
|
9
|
+
require "badline/chips/ted/sound"
|
|
10
|
+
require "badline/chips/ted/keyed_state"
|
|
11
|
+
|
|
12
|
+
module Badline
|
|
13
|
+
module Chips
|
|
14
|
+
# The TED of the Commodore 264 series, the 7360 and 8360: its registers
|
|
15
|
+
# at $FF00-$FF1F, the raster, the three timers, the interrupt, the
|
|
16
|
+
# keyboard latch, the clock it gives the CPU (Beam), what it fetches
|
|
17
|
+
# for the picture (Fetches) and the picture (Picture). Writes to the
|
|
18
|
+
# sound's registers, $FF0E-$FF12, go on to the Sound attached as
|
|
19
|
+
# #sound.
|
|
20
|
+
#
|
|
21
|
+
# The machine clocks it once a single clock cycle, 57 to a line, and
|
|
22
|
+
# asks it then how many cycles the CPU runs in that one (#cpu_slots)
|
|
23
|
+
# and whether BA is low (#ba_low?).
|
|
24
|
+
#
|
|
25
|
+
# $FF08 is the keyboard latch: a write latches the K0-K7 inputs, which
|
|
26
|
+
# the keyboard port answers for the byte written (KeyboardPort#latch),
|
|
27
|
+
# and a read returns them. $FF09 holds the interrupt flags, each
|
|
28
|
+
# cleared by writing a 1 to it, with bit 7 set while the interrupt
|
|
29
|
+
# line is pulled. $FF0A holds the mask and bit 8 of the raster compare,
|
|
30
|
+
# and $FF0B the compare's low byte. $FF13 bit 0 reads whether the CPU
|
|
31
|
+
# sees ROM at $8000-$FFFF (#rom_selected=). $FF1C and $FF1D read and
|
|
32
|
+
# set the raster line, and $FF1E reads the horizontal position's top 8
|
|
33
|
+
# bits. $FF1A and $FF1B read and set the character position reload,
|
|
34
|
+
# and $FF1F the row's line in bits 0-2 and in bits 3-6 the blink
|
|
35
|
+
# counter, which counts frames.
|
|
36
|
+
class TED
|
|
37
|
+
include Timers
|
|
38
|
+
include Beam
|
|
39
|
+
include Fetches
|
|
40
|
+
include Palette
|
|
41
|
+
include Modes
|
|
42
|
+
include Picture
|
|
43
|
+
|
|
44
|
+
# The clock a TED is fed, its single clock: the crystal divided by 20
|
|
45
|
+
# on PAL machines and by 16 on NTSC ones (the Plus/4 hardware manual's
|
|
46
|
+
# TED datasheet).
|
|
47
|
+
Profile = Data.define(:name, :clock_hz)
|
|
48
|
+
|
|
49
|
+
PAL = Profile.new(name: :pal, clock_hz: 886_724)
|
|
50
|
+
NTSC = Profile.new(name: :ntsc, clock_hz: 894_886)
|
|
51
|
+
|
|
52
|
+
RASTER_FLAG = 0x02
|
|
53
|
+
|
|
54
|
+
# The interrupt sources a mask bit can enable: the raster, the light
|
|
55
|
+
# pen and the three timers.
|
|
56
|
+
SOURCES = 0x5e
|
|
57
|
+
|
|
58
|
+
# The registers the picture reads as it paints: $FF06, $FF07, the
|
|
59
|
+
# cursor and the five colours.
|
|
60
|
+
PICTURE_REGISTERS = 0x03e030c0
|
|
61
|
+
|
|
62
|
+
attr_reader :profile, :interrupt_flags, :interrupt_mask
|
|
63
|
+
|
|
64
|
+
# What answers the keyboard latch: an object with #latch(value).
|
|
65
|
+
attr_writer :keyboard_port
|
|
66
|
+
|
|
67
|
+
# Whether the CPU sees ROM at $8000-$FFFF, which $FF13 bit 0 reads.
|
|
68
|
+
attr_writer :rom_selected
|
|
69
|
+
|
|
70
|
+
# The Sound the sound's registers drive.
|
|
71
|
+
attr_accessor :sound
|
|
72
|
+
|
|
73
|
+
def initialize(profile = PAL)
|
|
74
|
+
@profile = profile
|
|
75
|
+
@keyboard_port = nil
|
|
76
|
+
@rom_selected = true
|
|
77
|
+
@bus = nil
|
|
78
|
+
@sound = nil
|
|
79
|
+
power_on!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def power_on!
|
|
83
|
+
@registers = Array.new(0x20, 0)
|
|
84
|
+
@interrupt_flags = 0
|
|
85
|
+
@interrupt_mask = 0
|
|
86
|
+
@raster_compare = 0
|
|
87
|
+
@key_latch = 0xff
|
|
88
|
+
@blink = 0
|
|
89
|
+
reset_timers
|
|
90
|
+
reset_fetches
|
|
91
|
+
reset_picture
|
|
92
|
+
reset_beam
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Counts the timers, moves the beam on a cycle and makes the cycle's
|
|
96
|
+
# fetches, unless $FF07's freeze bit holds them, then decides the
|
|
97
|
+
# CPU's clock for the cycle.
|
|
98
|
+
def cycle!
|
|
99
|
+
unless frozen?
|
|
100
|
+
count_timers
|
|
101
|
+
move_beam
|
|
102
|
+
fetch(@column)
|
|
103
|
+
picture_cycle(@column)
|
|
104
|
+
end
|
|
105
|
+
decide_clock
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Whether the interrupt line is pulled: a flag raised whose mask bit
|
|
109
|
+
# is set.
|
|
110
|
+
def irq? = @interrupt_flags.anybits?(@interrupt_mask & SOURCES)
|
|
111
|
+
|
|
112
|
+
def peek(addr)
|
|
113
|
+
register = addr & 0x3f
|
|
114
|
+
return 0xff if register >= 0x20
|
|
115
|
+
|
|
116
|
+
case register
|
|
117
|
+
when 0x00..0x05 then read_timer(register)
|
|
118
|
+
when 0x08 then @key_latch
|
|
119
|
+
when 0x09 then read_interrupt_flags
|
|
120
|
+
when 0x0a then @interrupt_mask | 0xa0
|
|
121
|
+
when 0x0b then @raster_compare & 0xff
|
|
122
|
+
when 0x13 then (@registers[0x13] & 0xfe) | (@rom_selected ? 1 : 0)
|
|
123
|
+
when 0x1a then 0xfc | (@char_position >> 8)
|
|
124
|
+
when 0x1b then @char_position & 0xff
|
|
125
|
+
when 0x1c then 0xfe | (@line >> 8)
|
|
126
|
+
when 0x1d then @line & 0xff
|
|
127
|
+
when 0x1e then @column << 2
|
|
128
|
+
when 0x1f then 0x80 | (@blink << 3) | @row_line
|
|
129
|
+
else @registers[register]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def poke(addr, value)
|
|
134
|
+
register = addr & 0x3f
|
|
135
|
+
return if register >= 0x20
|
|
136
|
+
|
|
137
|
+
paint_to_write if (PICTURE_REGISTERS >> register).anybits?(1)
|
|
138
|
+
@registers[register] = value
|
|
139
|
+
case register
|
|
140
|
+
when 0x00..0x05 then write_timer(register, value)
|
|
141
|
+
when 0x08 then @key_latch = @keyboard_port ? @keyboard_port.latch(value) : 0xff
|
|
142
|
+
when 0x09 then @interrupt_flags &= ~value
|
|
143
|
+
when 0x0a then write_interrupt_mask(value)
|
|
144
|
+
when 0x0b then @raster_compare = (@raster_compare & 0x100) | value
|
|
145
|
+
when 0x0e..0x12 then @sound&.write(register, value)
|
|
146
|
+
when 0x1a then @char_position = (@char_position & 0xff) | ((value & 0x03) << 8)
|
|
147
|
+
when 0x1b then @char_position = (@char_position & 0x300) | value
|
|
148
|
+
when 0x1c then self.line = (@line & 0xff) | ((value & 0x01) << 8)
|
|
149
|
+
when 0x1d then self.line = (@line & 0x100) | value
|
|
150
|
+
when 0x1f then write_row_line(value)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
# $FF07 bit 5 stops the beam and the timers.
|
|
157
|
+
def frozen? = @registers[0x07].anybits?(0x20)
|
|
158
|
+
|
|
159
|
+
def read_interrupt_flags
|
|
160
|
+
value = @interrupt_flags | 0x21
|
|
161
|
+
irq? ? value | 0x80 : value
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def write_row_line(value)
|
|
165
|
+
@row_line = value & 0x07
|
|
166
|
+
@blink = (value >> 3) & 0x0f
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# The end of a frame: the fetches' counters start over.
|
|
170
|
+
def end_frame
|
|
171
|
+
end_fetch_frame
|
|
172
|
+
end_picture_frame
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def write_interrupt_mask(value)
|
|
176
|
+
@interrupt_mask = value & 0x5f
|
|
177
|
+
@raster_compare = (@raster_compare & 0xff) | ((value & 0x01) << 8)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def line=(value)
|
|
181
|
+
@line = value
|
|
182
|
+
raster_reached
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def raster_reached
|
|
186
|
+
@interrupt_flags |= RASTER_FLAG if @line == @raster_compare
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
# The 6523 TIA, a tri-port interface adapter: three 8-bit ports, A, B
|
|
6
|
+
# and C at registers 0-2, each with its direction register at 3-5, a 1
|
|
7
|
+
# making the pin an output. Its three address lines repeat the
|
|
8
|
+
# registers every 8 bytes, and registers 6 and 7 read $FF.
|
|
9
|
+
#
|
|
10
|
+
# A pin read as an output returns what the port drives, and one read as
|
|
11
|
+
# an input returns what the other side drives, #inputs, per port.
|
|
12
|
+
class TIA
|
|
13
|
+
attr_reader :ports, :directions
|
|
14
|
+
|
|
15
|
+
# What the other side drives onto each port's pins.
|
|
16
|
+
attr_reader :inputs
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@inputs = [0, 0, 0]
|
|
20
|
+
reset!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset!
|
|
24
|
+
@ports = [0, 0, 0]
|
|
25
|
+
@directions = [0, 0, 0]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def peek(addr)
|
|
29
|
+
register = addr & 0x07
|
|
30
|
+
return 0xff if register > 5
|
|
31
|
+
return @directions[register - 3] if register >= 3
|
|
32
|
+
|
|
33
|
+
direction = @directions[register]
|
|
34
|
+
(@ports[register] & direction) | (@inputs[register] & ~direction & 0xff)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def poke(addr, value)
|
|
38
|
+
register = addr & 0x07
|
|
39
|
+
return if register > 5
|
|
40
|
+
|
|
41
|
+
if register >= 3
|
|
42
|
+
@directions[register - 3] = value
|
|
43
|
+
else
|
|
44
|
+
@ports[register] = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The ports, their directions and what the other side drives, in a
|
|
49
|
+
# keyed snapshot module (Snapshot::Keyed, BL-TIA1 and BL-TIA2).
|
|
50
|
+
def save_fields(out)
|
|
51
|
+
out.ints("ports", @ports).ints("directions", @directions).ints("inputs", @inputs)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_fields(input)
|
|
55
|
+
input.ints_into("ports", @ports)
|
|
56
|
+
input.ints_into("directions", @directions)
|
|
57
|
+
input.ints_into("inputs", @inputs)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class VDC
|
|
6
|
+
class Raster
|
|
7
|
+
# The raster's fields in a keyed snapshot module, under `prefix`:
|
|
8
|
+
# the frame count, where the beam is in the character rows and the
|
|
9
|
+
# frame, and the frame's size and sync as it latched them.
|
|
10
|
+
module KeyedState
|
|
11
|
+
def save_fields(out, prefix)
|
|
12
|
+
out.int("#{prefix}frame", @frame).int("#{prefix}row", @row).int("#{prefix}row_line", @row_line)
|
|
13
|
+
out.bool("#{prefix}adjusting", @adjusting).int("#{prefix}frame_line", @frame_line)
|
|
14
|
+
out.int("#{prefix}frame_lines", @frame_lines).int("#{prefix}sync_start", @sync_start)
|
|
15
|
+
out.int("#{prefix}sync_lines", @sync_lines)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_fields(input, prefix)
|
|
19
|
+
@frame = input.int("#{prefix}frame", @frame)
|
|
20
|
+
@row = input.int("#{prefix}row", @row)
|
|
21
|
+
@row_line = input.int("#{prefix}row_line", @row_line)
|
|
22
|
+
@adjusting = input.bool("#{prefix}adjusting", @adjusting)
|
|
23
|
+
@frame_line = input.int("#{prefix}frame_line", @frame_line)
|
|
24
|
+
@frame_lines = input.int("#{prefix}frame_lines", @frame_lines)
|
|
25
|
+
@sync_start = input.int("#{prefix}sync_start", @sync_start)
|
|
26
|
+
@sync_lines = input.int("#{prefix}sync_lines", @sync_lines)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
include KeyedState
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Window
|
|
34
|
+
# The start addresses the frame latched, under `prefix`.
|
|
35
|
+
module KeyedState
|
|
36
|
+
def save_fields(out, prefix)
|
|
37
|
+
out.int("#{prefix}display_start", @display_start).int("#{prefix}attribute_start", @attribute_start)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def load_fields(input, prefix)
|
|
41
|
+
@display_start = input.word("#{prefix}display_start", @display_start)
|
|
42
|
+
@attribute_start = input.word("#{prefix}attribute_start", @attribute_start)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
include KeyedState
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Painter
|
|
50
|
+
# The display's size, as the frame latched it, and the window's
|
|
51
|
+
# start addresses. The picture is painted again.
|
|
52
|
+
module KeyedState
|
|
53
|
+
def save_fields(out)
|
|
54
|
+
out.int("display.width", @width).int("display.height", @height)
|
|
55
|
+
@window.save_fields(out, "window.")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def load_fields(input)
|
|
59
|
+
resize(input.int("display.width", @width), input.int("display.height", @height))
|
|
60
|
+
@window.load_fields(input, "window.")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
include KeyedState
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The VDC's fields in a keyed snapshot module (Snapshot::Keyed): the
|
|
68
|
+
# registers, the RAM, the register selected, the data latches, the
|
|
69
|
+
# dot clock's phase and the line's length in the same units
|
|
70
|
+
# (dots·Hz, the line latched at its start), how long an access or
|
|
71
|
+
# block operation stays busy from now, the raster under raster.* and
|
|
72
|
+
# the display's size and window under display.* and window.*. The
|
|
73
|
+
# RAM's layout comes from R28 on load.
|
|
74
|
+
module KeyedState
|
|
75
|
+
def save_fields(out)
|
|
76
|
+
out.bytes("registers", @registers).bytes("ram", @memory.ram).int("selected", @selected)
|
|
77
|
+
out.int("read_latch", @read_latch).int("write_data", @write_data).int("phase", @phase)
|
|
78
|
+
out.int("line_units", @line_units).int("busy_for", @busy_until - @phase)
|
|
79
|
+
@raster.save_fields(out, "raster.")
|
|
80
|
+
@painter.save_fields(out)
|
|
81
|
+
out
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def load_fields(input)
|
|
85
|
+
input.bytes_into("registers", @registers)
|
|
86
|
+
input.bytes_into("ram", @memory.ram)
|
|
87
|
+
@selected = input.masked("selected", @selected, 0x3f)
|
|
88
|
+
@read_latch = input.byte("read_latch", @read_latch)
|
|
89
|
+
@write_data = input.byte("write_data", @write_data)
|
|
90
|
+
@phase = input.int("phase", @phase)
|
|
91
|
+
@line_units = input.int("line_units", @line_units)
|
|
92
|
+
@busy_until = @phase + input.int("busy_for", @busy_until - @phase)
|
|
93
|
+
@raster.load_fields(input, "raster.")
|
|
94
|
+
@painter.load_fields(input)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def restored!
|
|
98
|
+
@memory.mode64 = @registers[28].anybits?(0x10)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
include KeyedState
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|