badline 0.15.0 → 0.16.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 +102 -0
- data/CLAUDE.md +15 -7
- data/README.md +142 -16
- data/doc/media.md +19 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +274 -31
- data/doc/snapshots.md +14 -0
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/machine_player.rb +2 -14
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/chips/crtc/keyed_state.rb +46 -0
- data/lib/badline/chips/crtc.rb +163 -0
- data/lib/badline/chips/pia/control_lines.rb +138 -0
- data/lib/badline/chips/pia/keyed_parts.rb +56 -0
- data/lib/badline/chips/pia.rb +138 -0
- data/lib/badline/chips/tia.rb +16 -0
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/datasette/keyed_state.rb +8 -3
- data/lib/badline/drives/engine/core.rb +6 -14
- data/lib/badline/drives/engine/idle.rb +42 -4
- data/lib/badline/drives/engine/idle_cpu.rb +18 -0
- data/lib/badline/drives/engine/serial_connection.rb +25 -0
- data/lib/badline/drives/engine/sleep.rb +1 -0
- data/lib/badline/drives/engine.rb +1 -0
- data/lib/badline/drives/ieee_bus.rb +28 -0
- data/lib/badline/drives/model1541/byte_ready.rb +19 -3
- data/lib/badline/drives/model1541/mechanism.rb +10 -1
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551/bus.rb +138 -0
- data/lib/badline/drives/model1551/cable.rb +107 -0
- data/lib/badline/drives/model1551/cpu.rb +14 -0
- data/lib/badline/drives/model1551/keyed_state.rb +65 -0
- data/lib/badline/drives/model1551/port.rb +74 -0
- data/lib/badline/drives/model1551/tia.rb +71 -0
- data/lib/badline/drives/model1551.rb +192 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/boot.rb +20 -1
- data/lib/badline/frontend/controls.rb +23 -3
- data/lib/badline/frontend/gamepads.rb +10 -3
- data/lib/badline/frontend/keys.rb +88 -0
- data/lib/badline/frontend/media_slots.rb +1 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +15 -6
- data/lib/badline/frontend/menu/menu_media.rb +96 -19
- data/lib/badline/frontend/menu/menu_pages.rb +25 -7
- data/lib/badline/frontend/menu/menu_ports.rb +22 -1
- data/lib/badline/frontend/menu/pause_menu.rb +1 -0
- data/lib/badline/frontend/pet_keyboard.rb +124 -0
- data/lib/badline/frontend/player/player_controller.rb +11 -4
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/timeline.rb +16 -9
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/dos/writes.rb +3 -1
- data/lib/badline/kernal_trap/dos.rb +6 -1
- data/lib/badline/kernal_trap/frames.rb +117 -0
- data/lib/badline/kernal_trap/ieee.rb +148 -0
- data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
- data/lib/badline/kernal_trap/keyed_state.rb +4 -4
- data/lib/badline/kernal_trap/routine.rb +3 -53
- data/lib/badline/kernal_trap/serial.rb +4 -102
- data/lib/badline/kernal_trap/steps.rb +64 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/machine/keyboard_buffer.rb +8 -0
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +37 -11
- data/lib/badline/media/extensions.rb +5 -2
- data/lib/badline/media/pet_media.rb +40 -0
- data/lib/badline/media/program.rb +14 -2
- data/lib/badline/media/true_drive.rb +7 -5
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +36 -16
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +23 -5
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +28 -4
- data/lib/badline/options/validation.rb +117 -14
- data/lib/badline/options.rb +65 -21
- data/lib/badline/pet/bus/expansion.rb +106 -0
- data/lib/badline/pet/bus.rb +204 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +26 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +142 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_ports.rb +66 -0
- data/lib/badline/pet/ieee_traps.rb +75 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +85 -0
- data/lib/badline/pet/keyed_parts.rb +123 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +113 -0
- data/lib/badline/pet/rom_sets.rb +65 -0
- data/lib/badline/pet/sound.rb +128 -0
- data/lib/badline/pet/via.rb +10 -0
- data/lib/badline/pet/via_ports.rb +19 -0
- data/lib/badline/pet.rb +226 -0
- data/lib/badline/plus4/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +181 -45
- data/lib/badline/plus4/keyed_modules.rb +22 -5
- data/lib/badline/plus4/keyed_parts.rb +33 -4
- data/lib/badline/plus4/keyed_state.rb +10 -5
- data/lib/badline/plus4/ram_expansion.rb +145 -0
- data/lib/badline/plus4/sid_card.rb +95 -0
- data/lib/badline/plus4/sound_mix.rb +51 -0
- data/lib/badline/plus4/tcbm_drive.rb +134 -0
- data/lib/badline/plus4/user_port.rb +11 -2
- data/lib/badline/plus4.rb +104 -22
- data/lib/badline/roms/README +146 -0
- data/lib/badline/roms/dos1551.rom +0 -0
- data/lib/badline/roms/pet/basic1.rom +0 -0
- data/lib/badline/roms/pet/basic2.rom +0 -0
- data/lib/badline/roms/pet/basic4.rom +0 -0
- data/lib/badline/roms/pet/characters1.rom +0 -0
- data/lib/badline/roms/pet/characters2.rom +0 -0
- data/lib/badline/roms/pet/editor1.rom +0 -0
- data/lib/badline/roms/pet/editor2-b.rom +0 -0
- data/lib/badline/roms/pet/editor2-n.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-b.rom +0 -0
- data/lib/badline/roms/pet/editor4-n.rom +0 -0
- data/lib/badline/roms/pet/kernal1.rom +0 -0
- data/lib/badline/roms/pet/kernal2.rom +0 -0
- data/lib/badline/roms/pet/kernal4.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +21 -6
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- data/lib/badline/snapshot/plus4_setup.rb +52 -10
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot.rb +9 -5
- data/lib/badline/storage/d80_image.rb +48 -0
- data/lib/badline/storage/d82_image.rb +21 -0
- data/lib/badline/storage/g64_image.rb +1 -1
- data/lib/badline/storage/tap.rb +16 -3
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/support/pet_checkpoint.rb +38 -0
- data/lib/badline/timing.rb +5 -4
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +7 -2
- data/lib/badline/vic20/bus/keyed_state.rb +5 -4
- data/lib/badline/vic20/bus.rb +5 -0
- data/lib/badline/vic20.rb +1 -1
- metadata +69 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# Hannes's RAM expansion, 256K or 1M in 64K banks, and Csory's 256K on
|
|
6
|
+
# the same board, from Solder's register notes and the board's GAL
|
|
7
|
+
# equations (plus4world, "256K RAM (Hannes)").
|
|
8
|
+
#
|
|
9
|
+
# The control register is a 6529 at $FD16, which reads back what was
|
|
10
|
+
# written. The expansion decodes $FD10-$FD1F further, so the user port
|
|
11
|
+
# answers only at $FD10, and the rest of the range is open.
|
|
12
|
+
#
|
|
13
|
+
# - Bits 0-1, and bits 2-3 on the 1M: the bank the CPU sees above the
|
|
14
|
+
# switch bound. All ones is the standard bank, the RAM below the
|
|
15
|
+
# bound, which bits 0-3 pick inverted: bank 3 of the 256K and bank
|
|
16
|
+
# 15 of the 1M.
|
|
17
|
+
# - Bit 6: the TED fetches from the CPU's bank when set, and from the
|
|
18
|
+
# standard bank when clear.
|
|
19
|
+
# - Bit 7: the switch bound, $4000 when set, $1000 when clear.
|
|
20
|
+
# - Bits 4-5: on the Csory board, two different bits switch Csory's
|
|
21
|
+
# mapping on (the notes' gate version; its table names 10 and its
|
|
22
|
+
# GAL 01).
|
|
23
|
+
#
|
|
24
|
+
# The register is all ones at power-on and after a reset, so the
|
|
25
|
+
# machine starts in the standard bank, with the TED following it.
|
|
26
|
+
#
|
|
27
|
+
# Csory's mapping puts one 16K block of the 256K, picked by bits 0-3
|
|
28
|
+
# of a write-only latch at $FD15, in the 16K window of the CPU's 64K
|
|
29
|
+
# that bits 6-7 pick. The standard bank holds blocks 0-3. The TED sees
|
|
30
|
+
# the block too while bit 6 of $FD16 is set. On the Csory board the
|
|
31
|
+
# TED reads the KERNAL wherever it fetches from the high ROM, whichever
|
|
32
|
+
# bank the ROM latch selects.
|
|
33
|
+
class RAMExpansion
|
|
34
|
+
# Each expansion by name: its 64K banks, and whether it has Csory's
|
|
35
|
+
# mapping.
|
|
36
|
+
KINDS = { hannes256: [4, false], hannes1m: [16, false], csory256: [4, true] }.freeze
|
|
37
|
+
|
|
38
|
+
# Whose design each is, as the pause menu names it.
|
|
39
|
+
NAMES = { hannes256: "HANNES", hannes1m: "HANNES", csory256: "CSORY" }.freeze
|
|
40
|
+
|
|
41
|
+
CONTROL = 0x06
|
|
42
|
+
CSORY_LATCH = 0x05
|
|
43
|
+
|
|
44
|
+
# The register at $FD16, and Csory's latch at $FD15.
|
|
45
|
+
attr_reader :kind, :control, :csory_latch
|
|
46
|
+
|
|
47
|
+
# +ram+ is the machine's own 64K, the standard bank, and +view+ the
|
|
48
|
+
# Bus::MirroredRAM the bus reads it through.
|
|
49
|
+
def initialize(kind, ram, view)
|
|
50
|
+
@kind = kind
|
|
51
|
+
count, csory = KINDS.fetch(kind)
|
|
52
|
+
@csory = csory
|
|
53
|
+
@mask = count - 1
|
|
54
|
+
size = (count - 1) * 0x10000
|
|
55
|
+
@extra = Memory.new(Array.new(size, Bus::RAM_POWER_ON), length: size, start: 0)
|
|
56
|
+
@banks = [view] + Array.new(count - 1) { |index| Bus::MirroredRAM.new(@extra, 0xffff, index * 0x10000) }
|
|
57
|
+
@blocks = csory ? Array.new(16) { |block| block_view(ram, block) } : []
|
|
58
|
+
@control = 0xff
|
|
59
|
+
@csory_latch = 0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The size in K.
|
|
63
|
+
def size_kb = (@mask + 1) * 64
|
|
64
|
+
|
|
65
|
+
# Whether the TED reads the KERNAL for the whole high ROM.
|
|
66
|
+
def kernal_for_ted? = @csory
|
|
67
|
+
|
|
68
|
+
# A write to $FD11-$FD1F: the control register at $FD16, and on the
|
|
69
|
+
# Csory board, Csory's latch at $FD15.
|
|
70
|
+
def poke(addr, value)
|
|
71
|
+
case addr & 0x0f
|
|
72
|
+
when CONTROL then @control = value
|
|
73
|
+
when CSORY_LATCH then @csory_latch = value if @csory
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The RESET line sets the register, as power-on does. Csory's latch
|
|
78
|
+
# keeps its value.
|
|
79
|
+
def reset!
|
|
80
|
+
@control = 0xff
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def power_on!
|
|
84
|
+
@extra.clear!(Array.new(@extra.length, Bus::RAM_POWER_ON))
|
|
85
|
+
@csory_latch = 0
|
|
86
|
+
reset!
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Lays the RAM the CPU sees out in +cpu+ and the RAM the TED sees in
|
|
90
|
+
# +ted+, a view for each of the 256 pages.
|
|
91
|
+
def map(cpu, ted)
|
|
92
|
+
control = @control
|
|
93
|
+
bound = control.anybits?(0x80) ? 0x40 : 0x10
|
|
94
|
+
standard = @banks[0]
|
|
95
|
+
cpu.fill(standard, 0x00, bound)
|
|
96
|
+
cpu.fill(@banks[~control & @mask], bound, 0x100 - bound)
|
|
97
|
+
follows = control.anybits?(0x40)
|
|
98
|
+
if follows
|
|
99
|
+
page = 0
|
|
100
|
+
while page < 0x100
|
|
101
|
+
ted[page] = cpu[page]
|
|
102
|
+
page += 1
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
ted.fill(standard, 0x00, 0x100)
|
|
106
|
+
end
|
|
107
|
+
return unless csory_on?
|
|
108
|
+
|
|
109
|
+
latch = @csory_latch
|
|
110
|
+
block = @blocks[latch & 0x0f]
|
|
111
|
+
window = (latch >> 6) * 0x40
|
|
112
|
+
cpu.fill(block, window, 0x40)
|
|
113
|
+
ted.fill(block, window, 0x40) if follows
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The banks beyond the standard one, and Csory's latch, in a keyed
|
|
117
|
+
# snapshot module (BL-RAMEXP).
|
|
118
|
+
def save_fields(out)
|
|
119
|
+
out.int("control", @control)
|
|
120
|
+
out.int("csory_latch", @csory_latch) if @csory
|
|
121
|
+
@extra.save_fields(out, "ram")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def load_fields(input)
|
|
125
|
+
@control = input.byte("control", @control)
|
|
126
|
+
@csory_latch = input.byte("csory_latch", @csory_latch)
|
|
127
|
+
@extra.load_fields(input, "ram")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def csory_on?
|
|
133
|
+
control = @control
|
|
134
|
+
@csory && control.anybits?(0x10) != control.anybits?(0x20)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Block +block+ of the 256K, from the standard bank's four on.
|
|
138
|
+
def block_view(ram, block)
|
|
139
|
+
return Bus::MirroredRAM.new(ram, 0x3fff, block * 0x4000) if block < 4
|
|
140
|
+
|
|
141
|
+
Bus::MirroredRAM.new(@extra, 0x3fff, (block - 4) * 0x4000)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# A SID card's SID at $FD40-$FD5F, or at $FE80-$FE9F as Csory's card
|
|
6
|
+
# puts it, clocked once a TED cycle, as the cards clock it from the
|
|
7
|
+
# TED's single clock. The SID counts the
|
|
8
|
+
# cycles that have passed whenever something touches it, rather than
|
|
9
|
+
# every cycle: a CPU access runs it through the cycle the access is in,
|
|
10
|
+
# and the host, between cycles, up to the cycle about to start.
|
|
11
|
+
# `clock` answers the machine's cycles.
|
|
12
|
+
class SIDCard
|
|
13
|
+
# Where a card can put its SID.
|
|
14
|
+
ADDRESSES = [0xfd40, 0xfe80].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :sid, :address
|
|
17
|
+
|
|
18
|
+
def initialize(clock, model, address = ADDRESSES.first)
|
|
19
|
+
raise ArgumentError, format("no SID card at $%04X", address) unless ADDRESSES.include?(address)
|
|
20
|
+
|
|
21
|
+
@clock = clock
|
|
22
|
+
@address = address
|
|
23
|
+
@sid = Chips::SID.new(model:, at: address)
|
|
24
|
+
@clocked = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def model = @sid.model
|
|
28
|
+
|
|
29
|
+
def model=(model)
|
|
30
|
+
catch_up!
|
|
31
|
+
@sid.model = model
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def peek(addr)
|
|
35
|
+
run_to(@clock.cycles + 1)
|
|
36
|
+
@sid.peek(addr)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def poke(addr, value)
|
|
40
|
+
run_to(@clock.cycles + 1)
|
|
41
|
+
@sid.poke(addr, value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The RES line, from the host between cycles.
|
|
45
|
+
def reset!
|
|
46
|
+
run_to(@clock.cycles)
|
|
47
|
+
@sid.reset!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Runs the SID, its voices and its filter, up to the current cycle.
|
|
51
|
+
def catch_up!
|
|
52
|
+
run_to(@clock.cycles)
|
|
53
|
+
@sid.catch_up!
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def record(rate:, clock_hz:)
|
|
57
|
+
run_to(@clock.cycles)
|
|
58
|
+
@sid.record(rate:, clock_hz:)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def drain_samples
|
|
62
|
+
run_to(@clock.cycles)
|
|
63
|
+
@sid.drain_samples
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The SID's fields in a keyed snapshot module (BL-SID), saved once the
|
|
67
|
+
# machine has settled it (#catch_up!).
|
|
68
|
+
def save_fields(out) = @sid.save_fields(out)
|
|
69
|
+
|
|
70
|
+
def load_fields(input)
|
|
71
|
+
@sid.load_fields(input)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The SID counts on from the machine's cycle the snapshot was taken at.
|
|
75
|
+
def restored!
|
|
76
|
+
@sid.restored!
|
|
77
|
+
@clocked = @clock.cycles
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def run_to(target)
|
|
83
|
+
clocked = @clocked
|
|
84
|
+
return if clocked >= target
|
|
85
|
+
|
|
86
|
+
sid = @sid
|
|
87
|
+
while clocked < target
|
|
88
|
+
sid.cycle!
|
|
89
|
+
clocked += 1
|
|
90
|
+
end
|
|
91
|
+
@clocked = clocked
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# What a front end plays from a 264 with a SID card fitted: the TED's
|
|
6
|
+
# sound and the card's SID, each recorded at the audio rate and added
|
|
7
|
+
# together, clipped to a signed 16-bit sample. A sample one source has
|
|
8
|
+
# closed waits for the other's. The SID's model is the mix's, as it
|
|
9
|
+
# is a C64's sound source's, for the pause menu's Sound page.
|
|
10
|
+
class SoundMix
|
|
11
|
+
def initialize(ted_sound, sid)
|
|
12
|
+
@ted_sound = ted_sound
|
|
13
|
+
@sid = sid
|
|
14
|
+
@ted_waiting = []
|
|
15
|
+
@sid_waiting = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def model = @sid.model
|
|
19
|
+
|
|
20
|
+
def model=(model)
|
|
21
|
+
@sid.model = model
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Starts both sources recording at `rate` samples a second, their
|
|
25
|
+
# cycles taken to run at `clock_hz`.
|
|
26
|
+
def record(rate:, clock_hz:)
|
|
27
|
+
@ted_sound.record(rate:, clock_hz:)
|
|
28
|
+
@sid.record(rate:, clock_hz:)
|
|
29
|
+
@ted_waiting = []
|
|
30
|
+
@sid_waiting = []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Runs both sources up to the current cycle.
|
|
34
|
+
def catch_up!
|
|
35
|
+
@ted_sound.catch_up!
|
|
36
|
+
@sid.catch_up!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The mixed samples both sources have closed since the last drain.
|
|
40
|
+
def drain_samples
|
|
41
|
+
ted = @ted_waiting.concat(@ted_sound.drain_samples)
|
|
42
|
+
sid = @sid_waiting.concat(@sid.drain_samples)
|
|
43
|
+
count = [ted.length, sid.length].min
|
|
44
|
+
mixed = Array.new(count) { |index| (ted[index] + sid[index]).clamp(-0x8000, 0x7fff) }
|
|
45
|
+
@ted_waiting = ted[count, ted.length - count] || []
|
|
46
|
+
@sid_waiting = sid[count, sid.length - count] || []
|
|
47
|
+
mixed
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The 264 series' true drives: a Drives::Model1541 and a
|
|
6
|
+
# Drives::Model1581 on the serial bus (C64::TrueDrives), and a
|
|
7
|
+
# Drives::Model1551 on TCBM, its interface's TIA in the expansion port
|
|
8
|
+
# at $FEE0 for device 8 or $FEC0 for device 9. Each sits in a field of
|
|
9
|
+
# its own, so none makes another's per-cycle call a union.
|
|
10
|
+
#
|
|
11
|
+
# A device number has one drive at a time: a 1551 takes the place of a
|
|
12
|
+
# serial drive with its number, and the other way round.
|
|
13
|
+
module TCBMDrive
|
|
14
|
+
# The true drive models plug_true_drive builds.
|
|
15
|
+
TRUE_DRIVE_MODELS = %w[1541 1551].freeze
|
|
16
|
+
|
|
17
|
+
# The 1551 on TCBM, or nil.
|
|
18
|
+
attr_reader :drive1551
|
|
19
|
+
|
|
20
|
+
# The drive plug_true_drive plugs in, "1541" or "1551".
|
|
21
|
+
attr_reader :true_drive_model
|
|
22
|
+
|
|
23
|
+
# Plugs in a Drives::Model1551, its interface's TIA where its device
|
|
24
|
+
# number puts it, in place of the TCBM interface's own there, and in
|
|
25
|
+
# place of a serial drive with its number. The traps stop answering
|
|
26
|
+
# device 8 when the drive is device 8.
|
|
27
|
+
def attach_drive1551(drive)
|
|
28
|
+
detach_drive1551
|
|
29
|
+
device = drive.device
|
|
30
|
+
detach_drive1541 if @drive1541&.device == device
|
|
31
|
+
detach_drive1581 if @drive1581&.device == device
|
|
32
|
+
drive.host_clock_hz = timing.clock_hz
|
|
33
|
+
@drive1551 = drive
|
|
34
|
+
@bus.plug_tia(drive.device, drive.host_tia)
|
|
35
|
+
drive.on_renumber { |old| drive1551_renumbered(old) }
|
|
36
|
+
@serial_trap&.device = serial_trap_device
|
|
37
|
+
@init_threshold = [@init_threshold, Badline::Drives::Model1551::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Unplugs the Drives::Model1551, leaving the TCBM interface's own TIA,
|
|
41
|
+
# if there is one, in its place.
|
|
42
|
+
def detach_drive1551
|
|
43
|
+
drive = @drive1551
|
|
44
|
+
return unless drive
|
|
45
|
+
|
|
46
|
+
@bus.plug_tia(drive.device, tcbm_tia(drive.device))
|
|
47
|
+
drive.on_renumber
|
|
48
|
+
@drive1551 = nil
|
|
49
|
+
@serial_trap&.device = serial_trap_device
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The true drive: a 1581 ahead of a 1551 ahead of a 1541.
|
|
53
|
+
def true_drive = @drive1581 || @drive1551 || @drive1541
|
|
54
|
+
|
|
55
|
+
# Plugs in the model true_drive_model names as device 8, in place of
|
|
56
|
+
# the drive there, and returns it.
|
|
57
|
+
def plug_true_drive
|
|
58
|
+
return plug_drive1551 if @true_drive_model == "1551"
|
|
59
|
+
|
|
60
|
+
unplug_device8
|
|
61
|
+
detach_drive1581 if drive1581_on_device8?
|
|
62
|
+
Badline::Drives::Model1541.new.tap { |drive| attach_drive1541(drive) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Plugs in a 1551 as device 8, in place of the drive there, and
|
|
66
|
+
# returns it.
|
|
67
|
+
def plug_drive1551
|
|
68
|
+
Badline::Drives::Model1551.new.tap { |drive| attach_drive1551(drive) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def init_drives(true_drive_model)
|
|
74
|
+
unless TRUE_DRIVE_MODELS.include?(true_drive_model)
|
|
75
|
+
raise ArgumentError, "no true drive model #{true_drive_model} for the Plus/4"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@true_drive_model = true_drive_model
|
|
79
|
+
@drive1541 = nil
|
|
80
|
+
@drive1551 = nil
|
|
81
|
+
@drive1581 = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def save_drive1551_modules(sections)
|
|
85
|
+
drive = @drive1551
|
|
86
|
+
drive&.save_modules(sections, "BL-D#{drive.device}")
|
|
87
|
+
sections
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_drive1551_modules(modules, input, device)
|
|
91
|
+
return input.adjust("a 1551 as device #{device}, left out") unless [8, 9].include?(device)
|
|
92
|
+
|
|
93
|
+
detach_drive1551 if @drive1551 && @drive1551.device != device
|
|
94
|
+
attach_drive1551(Badline::Drives::Model1551.new(device:)) unless @drive1551
|
|
95
|
+
@drive1551.load_modules(modules, input, "BL-D#{device}")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The 1551's DEV line moved it from device +old+ to the other: its
|
|
99
|
+
# interface's TIA moves with it, and the TCBM interface's own takes
|
|
100
|
+
# its old place.
|
|
101
|
+
def drive1551_renumbered(old)
|
|
102
|
+
drive = @drive1551
|
|
103
|
+
return unless drive
|
|
104
|
+
|
|
105
|
+
@bus.plug_tia(old, tcbm_tia(old))
|
|
106
|
+
@bus.plug_tia(drive.device, drive.host_tia)
|
|
107
|
+
@serial_trap&.device = serial_trap_device
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The TCBM interface's own TIA for +device+, or nil without one.
|
|
111
|
+
def tcbm_tia(device)
|
|
112
|
+
tias = @tias
|
|
113
|
+
return nil if tias.empty?
|
|
114
|
+
|
|
115
|
+
device == 9 ? tias[1] : tias[0]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Device 8, unless a true drive is device 8.
|
|
119
|
+
def serial_trap_device
|
|
120
|
+
device = KernalTrap::Routine::DEVICE
|
|
121
|
+
on_device8 = @drive1551&.device == device || @drive1541&.device == device || drive1581_on_device8?
|
|
122
|
+
on_device8 ? nil : device
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Unplugs the 1541 and the 1551 if either is device 8, for a 1581 to
|
|
126
|
+
# take its place.
|
|
127
|
+
def unplug_device8
|
|
128
|
+
device = KernalTrap::Routine::DEVICE
|
|
129
|
+
detach_drive1541 if @drive1541&.device == device
|
|
130
|
+
detach_drive1551 if @drive1551&.device == device
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -6,22 +6,31 @@ module Badline
|
|
|
6
6
|
# latch: a line reads low where the latch or the outside pulls it. With
|
|
7
7
|
# nothing plugged into the user port, the outside pulls only bit 2, the
|
|
8
8
|
# cassette sense switch, low while a key is down on the deck. The
|
|
9
|
-
# KERNAL's IRQ reads it there ($CFBF)
|
|
9
|
+
# KERNAL's IRQ reads it there ($CFBF), and takes P1 as RS-232's clear
|
|
10
|
+
# to send, sending through the ACIA only while it reads high.
|
|
11
|
+
#
|
|
12
|
+
# A device on the user port drives P0-P7 through #inputs, low where its
|
|
13
|
+
# bit is clear.
|
|
10
14
|
class UserPort
|
|
11
15
|
TAPE_SENSE = 0x04
|
|
12
16
|
|
|
13
17
|
attr_reader :output
|
|
18
|
+
attr_accessor :inputs
|
|
14
19
|
|
|
15
20
|
def initialize(datasette)
|
|
16
21
|
@datasette = datasette
|
|
17
22
|
@output = 0xff
|
|
23
|
+
@inputs = 0xff
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def reset!
|
|
21
27
|
@output = 0xff
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def peek(_addr)
|
|
30
|
+
def peek(_addr)
|
|
31
|
+
lines = @output & @inputs
|
|
32
|
+
@datasette.sense_low? ? lines & ~TAPE_SENSE : lines
|
|
33
|
+
end
|
|
25
34
|
|
|
26
35
|
def poke(_addr, value)
|
|
27
36
|
@output = value
|