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
|
@@ -10,17 +10,29 @@ module Badline
|
|
|
10
10
|
# wrong for the page to show.
|
|
11
11
|
class MenuMedia
|
|
12
12
|
attr_reader :problem
|
|
13
|
-
attr_writer :computer
|
|
14
13
|
|
|
15
14
|
# Takes the disk set from the command line's `media_path` when it's a
|
|
16
|
-
# list
|
|
17
|
-
|
|
15
|
+
# list, and the names of the cartridges it and `cartridges`, the
|
|
16
|
+
# Plus/4's ROM images by slot (Options#cartridges), put in the first
|
|
17
|
+
# machine.
|
|
18
|
+
def initialize(media_path, writable, cartridges)
|
|
18
19
|
@list = Media::DiskList.list?(media_path) ? media_path : ""
|
|
19
20
|
@writable = writable
|
|
20
21
|
@set_path = ""
|
|
21
22
|
@set = []
|
|
22
23
|
@problem = ""
|
|
23
24
|
@computer = nil
|
|
25
|
+
@command_line = cartridges.map { |slot, path| [slot, path] } + [["", media_path]]
|
|
26
|
+
@names = {}
|
|
27
|
+
@named = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def computer=(computer)
|
|
31
|
+
@computer = computer
|
|
32
|
+
return unless @named.nil?
|
|
33
|
+
|
|
34
|
+
@command_line.each { |slot, path| note_cartridge(computer, path.dup, slot) }
|
|
35
|
+
@named = computer
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
def writable? = @writable
|
|
@@ -87,26 +99,37 @@ module Badline
|
|
|
87
99
|
insert(:disk, set[index + step])
|
|
88
100
|
end
|
|
89
101
|
|
|
90
|
-
# The extensions of the cartridges the machine takes: .crt,
|
|
91
|
-
#
|
|
102
|
+
# The extensions of the cartridges the machine takes: .crt, on the
|
|
103
|
+
# Plus/4 raw .bin ROM images too, and none on the PET.
|
|
92
104
|
def cartridge_extensions
|
|
93
|
-
extensions = Media::Extensions.of(%i[cartridge])
|
|
105
|
+
extensions = Media::Extensions.of(@computer.family == :pet ? %i[none] : %i[cartridge])
|
|
94
106
|
@computer.family == :plus4 ? extensions + [Media::Plus4Media::ROM_IMAGE] : extensions
|
|
95
107
|
end
|
|
96
108
|
|
|
109
|
+
# The extensions of what goes in device 8: on the PET the 8050's and
|
|
110
|
+
# 8250's disk images besides the others, and no raw GCR, which only
|
|
111
|
+
# a serial true drive reads.
|
|
112
|
+
def disk_extensions
|
|
113
|
+
return Media::Extensions.of(%i[disk ieee_disk archive disk_list]) if @computer.family == :pet
|
|
114
|
+
|
|
115
|
+
Media::Extensions::DEVICE8
|
|
116
|
+
end
|
|
117
|
+
|
|
97
118
|
# Swaps the cartridge for the one at `path`, which power cycles the
|
|
98
119
|
# machine, and says whether it went in.
|
|
99
120
|
def attach_cartridge(path)
|
|
100
|
-
|
|
121
|
+
attached = cartridge_succeeds? { Media.attach(@computer, path) }
|
|
122
|
+
note_cartridge(@computer, path, "") if attached
|
|
123
|
+
attached
|
|
101
124
|
end
|
|
102
125
|
|
|
103
126
|
# What a save of the machine is named after: the cartridge, the disk,
|
|
104
127
|
# the tape, or BASIC, without the bracketed parts of a file's name. A
|
|
105
128
|
# VIC-20's are named after its disk, or else the machine, and a
|
|
106
|
-
# Plus/4's after its disk or tape.
|
|
129
|
+
# Plus/4's or a PET's after its disk or tape.
|
|
107
130
|
def game_name
|
|
108
131
|
vic20 = @computer.family == :vic20
|
|
109
|
-
name = vic20 || @computer.family
|
|
132
|
+
name = vic20 || %i[plus4 pet].include?(@computer.family) ? "" : cartridge_name
|
|
110
133
|
disk = disk_path
|
|
111
134
|
name = File.basename(disk, File.extname(disk)) if name.empty? && !disk.empty?
|
|
112
135
|
name = tape_name if name.empty? && !vic20
|
|
@@ -122,26 +145,38 @@ module Badline
|
|
|
122
145
|
end
|
|
123
146
|
|
|
124
147
|
# The name the cartridge in the expansion port gives itself, or an
|
|
125
|
-
# empty one. A
|
|
148
|
+
# empty one. A VIC-20 keeps its own (Vic20#cartridge_name). A Plus/4's
|
|
149
|
+
# is the name of the file it came from: a .crt's own, or else the
|
|
150
|
+
# file's. It names each of its
|
|
151
|
+
# cartridges with a ROM in, and a slot filled from elsewhere, such as
|
|
152
|
+
# a snapshot, by the slot.
|
|
126
153
|
def cartridge_name
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
154
|
+
case @computer.family
|
|
155
|
+
when :plus4 then @computer.cartridge_slots.map { |slot| slot_name(slot) }.uniq.join(", ")
|
|
156
|
+
when :vic20 then @computer.cartridge_name
|
|
157
|
+
when :pet then ""
|
|
158
|
+
else
|
|
159
|
+
cartridge = @computer.address_bus.cartridge
|
|
160
|
+
cartridge.nil? ? "" : cartridge.name
|
|
161
|
+
end
|
|
131
162
|
end
|
|
132
163
|
|
|
133
164
|
# Takes the cartridge out with the power off.
|
|
134
|
-
def remove_cartridge
|
|
165
|
+
def remove_cartridge
|
|
166
|
+
@names = {}
|
|
167
|
+
MediaSlots.remove_cartridge(@computer)
|
|
168
|
+
end
|
|
135
169
|
|
|
136
170
|
# A new machine started on `path`, as the command line would, or nil
|
|
137
171
|
# when it won't start.
|
|
138
172
|
def start(options, path)
|
|
139
173
|
computer = nil
|
|
140
|
-
|
|
141
|
-
computer =
|
|
142
|
-
|
|
174
|
+
if Media.kind(@computer, path) == :cartridge
|
|
175
|
+
cartridge_succeeds? { computer = started(options, path) }
|
|
176
|
+
else
|
|
177
|
+
succeeds? { computer = started(options, path) }
|
|
143
178
|
end
|
|
144
|
-
|
|
179
|
+
@problem.empty? ? computer : nil
|
|
145
180
|
end
|
|
146
181
|
|
|
147
182
|
private
|
|
@@ -153,6 +188,48 @@ module Badline
|
|
|
153
188
|
@problem.empty?
|
|
154
189
|
end
|
|
155
190
|
|
|
191
|
+
# Runs the block as #succeeds? does, keeping a cartridge's chip in a
|
|
192
|
+
# bank or slot the machine doesn't map as the problem too.
|
|
193
|
+
def cartridge_succeeds?(&)
|
|
194
|
+
@problem = Frontend.cartridge_problem(&)
|
|
195
|
+
@problem.empty?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# The machine Frontend.start started on `path`, with the names of
|
|
199
|
+
# the cartridges in it noted.
|
|
200
|
+
def started(options, path)
|
|
201
|
+
computer = Frontend.start(options, path, @writable)
|
|
202
|
+
list(path)
|
|
203
|
+
@names = {}
|
|
204
|
+
options.cartridges.each { |slot, image| note_cartridge(computer, image.dup, slot) }
|
|
205
|
+
note_cartridge(computer, path, "")
|
|
206
|
+
@named = computer
|
|
207
|
+
computer
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def named? = @computer.equal?(@named)
|
|
211
|
+
|
|
212
|
+
def slot_name(slot) = named? ? @names.fetch(slot, slot.upcase) : slot.upcase
|
|
213
|
+
|
|
214
|
+
# Keeps the name of the cartridge at `path` in a Plus/4 by the slots
|
|
215
|
+
# it fills, by xplus4's names, a raw image's in `slot` if it names
|
|
216
|
+
# one. Other media, and other machines' cartridges, leave the names
|
|
217
|
+
# alone.
|
|
218
|
+
def note_cartridge(computer, path, slot)
|
|
219
|
+
return unless computer.family == :plus4 && Media.kind(computer, path) == :cartridge
|
|
220
|
+
|
|
221
|
+
name = file_name(path)
|
|
222
|
+
Media::Plus4Media.cartridge_chips(path, slot: slot.empty? ? nil : slot).each do |chip|
|
|
223
|
+
@names["c#{chip.bank + 1}#{chip.address < 0xc000 ? 'lo' : 'hi'}"] = name
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# A .crt's own name, or else the file's, without its extension.
|
|
228
|
+
def file_name(path)
|
|
229
|
+
name = Media::Extensions.kind(path) == :cartridge ? Storage::CRTFile.new(path, machine: nil).name : ""
|
|
230
|
+
name.empty? ? File.basename(path, File.extname(path)) : name
|
|
231
|
+
end
|
|
232
|
+
|
|
156
233
|
# Keeps `path` as the list the set comes from, if it's a list.
|
|
157
234
|
def list(path)
|
|
158
235
|
return unless Media::DiskList.list?(path)
|
|
@@ -29,7 +29,7 @@ module Badline
|
|
|
29
29
|
@recent = []
|
|
30
30
|
@painter = painter
|
|
31
31
|
@buttons = buttons
|
|
32
|
-
@media = MenuMedia.new(media_path, options.writable
|
|
32
|
+
@media = MenuMedia.new(media_path, options.writable?, options.cartridges)
|
|
33
33
|
@reu_kb = options.reu
|
|
34
34
|
@computer = nil
|
|
35
35
|
@controls = nil
|
|
@@ -85,6 +85,9 @@ module Badline
|
|
|
85
85
|
# Whether the machine is a C128, whose F8 switches its screens.
|
|
86
86
|
def c128? = @computer.family == :c128
|
|
87
87
|
|
|
88
|
+
# Whether the machine is a PET, whose ports page shows its keyboard.
|
|
89
|
+
def pet? = @computer.family == :pet
|
|
90
|
+
|
|
88
91
|
private
|
|
89
92
|
|
|
90
93
|
def vic20? = @computer.family == :vic20
|
|
@@ -122,9 +125,11 @@ module Badline
|
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
def draw_expansion
|
|
125
|
-
return
|
|
128
|
+
return draw_pet_memory if pet?
|
|
126
129
|
|
|
127
130
|
info("CARTRIDGE", @media.cartridge_name)
|
|
131
|
+
return info("RAM EXPANSION", RAM_NAMES.fetch(@computer.ram_configuration)) if vic20?
|
|
132
|
+
|
|
128
133
|
row("INSERT...", :insert_cartridge)
|
|
129
134
|
row("REMOVE", :remove_cartridge)
|
|
130
135
|
return if plus4?
|
|
@@ -137,7 +142,8 @@ module Badline
|
|
|
137
142
|
info("RAM EXPANSION", "REU, #{@reu_kb} KB")
|
|
138
143
|
end
|
|
139
144
|
|
|
140
|
-
# The SID's model is a setting,
|
|
145
|
+
# The SID's model is a setting, the C64's SID's or a Plus/4's SID
|
|
146
|
+
# card's, and other sound chips have none.
|
|
141
147
|
def draw_sound
|
|
142
148
|
if @sound.on?
|
|
143
149
|
toggle("OUTPUT", :output, [%w[ON MUTED], %i[sound_on mute], @sound.muted? ? 1 : 0])
|
|
@@ -147,7 +153,13 @@ module Badline
|
|
|
147
153
|
model = @computer.sound_source.model
|
|
148
154
|
return unless SID_MODELS.include?(model)
|
|
149
155
|
|
|
150
|
-
toggle("SID", :sid, [%w[6581 8580], %i[sid6581 sid8580], model == :mos8580 ? 1 : 0])
|
|
156
|
+
toggle(plus4? ? "SID CARD" : "SID", :sid, [%w[6581 8580], %i[sid6581 sid8580], model == :mos8580 ? 1 : 0])
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The PET's memory: its RAM, and the 8096's 64K board.
|
|
160
|
+
def draw_pet_memory
|
|
161
|
+
info("RAM", "#{@computer.ram_kb}K")
|
|
162
|
+
info("EXPANSION RAM", @computer.model.expansion ? "64K BOARD" : "")
|
|
151
163
|
end
|
|
152
164
|
|
|
153
165
|
# Saving now and by name, loading a named save, and the quicksaves
|
|
@@ -165,11 +177,17 @@ module Badline
|
|
|
165
177
|
end
|
|
166
178
|
end
|
|
167
179
|
|
|
168
|
-
# The C128 names its model and the mode it runs in first,
|
|
169
|
-
# Plus/4 its
|
|
180
|
+
# The C128 names its model and the mode it runs in first, the Plus/4
|
|
181
|
+
# and the PET their model and RAM, and the Plus/4 its RAM expansion if
|
|
182
|
+
# it has one.
|
|
170
183
|
def draw_power
|
|
171
184
|
info("MACHINE", "#{@computer.model.name.upcase}, #{@computer.mode.to_s.upcase} MODE") if c128?
|
|
172
|
-
info("MACHINE", @computer.model_label) if
|
|
185
|
+
info("MACHINE", @computer.model_label) if pet?
|
|
186
|
+
if plus4?
|
|
187
|
+
info("MACHINE", @computer.model_label)
|
|
188
|
+
expansion = @computer.ram_expansion_label
|
|
189
|
+
info("RAM EXPANSION", expansion) if expansion
|
|
190
|
+
end
|
|
173
191
|
row("RESET", :reset)
|
|
174
192
|
row("POWER CYCLE", :power_cycle)
|
|
175
193
|
skip
|
|
@@ -5,18 +5,28 @@ module Badline
|
|
|
5
5
|
# The pause menu's PORTS page, which MenuPages draws: what each C64
|
|
6
6
|
# control port has plugged in, a joystick or a pot device, and whether
|
|
7
7
|
# the keys type or drive the joysticks. The VIC-20 has one port, a
|
|
8
|
-
# joystick's, and the Plus/4 two joysticks and no pot devices.
|
|
8
|
+
# joystick's, and the Plus/4 two joysticks and no pot devices. The PET
|
|
9
|
+
# has no ports, and the page, KEYBOARD there, says where its keys are
|
|
10
|
+
# instead.
|
|
9
11
|
module MenuPorts
|
|
10
12
|
PORT_DEVICES = %i[joystick mouse paddles].freeze
|
|
11
13
|
PORT_NAMES = %w[JOY MOUSE PADDLES].freeze
|
|
12
14
|
PORT_ACTIONS = [%i[port1_joystick port1_mouse port1_paddles], %i[port2_joystick port2_mouse port2_paddles]].freeze
|
|
13
15
|
POTS = [%i[none mouse1 paddles1], %i[none mouse2 paddles2]].freeze
|
|
14
16
|
|
|
17
|
+
# Where the PET's keys without a character of their own are, on both
|
|
18
|
+
# keyboards, and then on the graphics one and on the business one.
|
|
19
|
+
PET_KEYS = ["PAGE DOWN: RUN/STOP", "HOME: CLR/HOME", "BACKSPACE: INST/DEL", "CTRL: RVS",
|
|
20
|
+
"ARROWS: CURSOR", "^: UP ARROW", "_: LEFT ARROW"].freeze
|
|
21
|
+
PET_GRAPHICS_KEYS = ["ESC: RUN/STOP", "SHIFT+LETTER: GRAPHIC", "ALT+SYMBOL: GRAPHIC"].freeze
|
|
22
|
+
PET_BUSINESS_KEYS = ["ESC: ESC", "TAB: TAB", "PAGE UP: REPEAT", "KEYPAD: KEYPAD", "ALT+KEY: SHIFT+KEY"].freeze
|
|
23
|
+
|
|
15
24
|
private
|
|
16
25
|
|
|
17
26
|
def draw_ports
|
|
18
27
|
return draw_vic20_port if @computer.family == :vic20
|
|
19
28
|
return draw_plus4_ports if @computer.family == :plus4
|
|
29
|
+
return draw_pet_keys if @computer.family == :pet
|
|
20
30
|
|
|
21
31
|
toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
|
|
22
32
|
toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
|
|
@@ -49,6 +59,17 @@ module Badline
|
|
|
49
59
|
info("WASD, LEFT SHIFT", "JOYSTICK #{3 - arrows}")
|
|
50
60
|
end
|
|
51
61
|
|
|
62
|
+
# The PET's keyboard, whose keys type the characters on the host's
|
|
63
|
+
# (PETKeyboard), and the keys that stand for the rest of its keys.
|
|
64
|
+
def draw_pet_keys
|
|
65
|
+
business = @computer.model.keyboard == :business
|
|
66
|
+
info("KEYBOARD", business ? "BUSINESS" : "GRAPHICS")
|
|
67
|
+
(PET_KEYS + (business ? PET_BUSINESS_KEYS : PET_GRAPHICS_KEYS)).each do |line|
|
|
68
|
+
@painter.text(@left, @top, line, MenuTheme::TEXT)
|
|
69
|
+
@top += MenuPages::LINE
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
52
73
|
# The toggle between the keyboard named and the joysticks, and
|
|
53
74
|
# whether the keys drive the joysticks.
|
|
54
75
|
def draw_keys(keyboard)
|
|
@@ -238,6 +238,7 @@ module Badline
|
|
|
238
238
|
def draw_sections
|
|
239
239
|
top = @top + 32
|
|
240
240
|
SECTIONS.each_with_index do |name, index|
|
|
241
|
+
name = "KEYBOARD" if PAGES[index] == :ports && @pages.pet?
|
|
241
242
|
@buttons.plain(@left + 6, top, name.ljust(14), PAGES[index], on: index == @section)
|
|
242
243
|
top += Buttons::HEIGHT + 2
|
|
243
244
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Frontend
|
|
5
|
+
# The host's keys on a PET's key matrix, by the character each types
|
|
6
|
+
# (Keys.pet_key), so the host's " types the PET's " on either keyboard.
|
|
7
|
+
#
|
|
8
|
+
# The PET's SHIFT follows the host's, except while a key down needs it
|
|
9
|
+
# one way to type its character: then it is held that way until the
|
|
10
|
+
# key goes up, the last such key deciding.
|
|
11
|
+
#
|
|
12
|
+
# With either Alt down, a key that types a character presses the PET
|
|
13
|
+
# key its unshifted character is on with the PET's SHIFT, so the
|
|
14
|
+
# graphics on the graphics keyboard's symbol keys can be typed.
|
|
15
|
+
class PETKeyboard
|
|
16
|
+
LSHIFT = 225
|
|
17
|
+
RSHIFT = 229
|
|
18
|
+
LALT = 226
|
|
19
|
+
RALT = 230
|
|
20
|
+
|
|
21
|
+
# +layout+ is the PET's keyboard, :graphics or :business.
|
|
22
|
+
def initialize(keyboard, layout)
|
|
23
|
+
@keyboard = keyboard
|
|
24
|
+
@layout = layout
|
|
25
|
+
@held = {}
|
|
26
|
+
@left_shift = false
|
|
27
|
+
@right_shift = false
|
|
28
|
+
@left_alt = false
|
|
29
|
+
@right_alt = false
|
|
30
|
+
@forced = -1
|
|
31
|
+
@forced_shift = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key(scancode, down)
|
|
35
|
+
if [LSHIFT, RSHIFT].include?(scancode)
|
|
36
|
+
shift_key(scancode, down)
|
|
37
|
+
elsif [LALT, RALT].include?(scancode)
|
|
38
|
+
alt_key(scancode, down)
|
|
39
|
+
elsif down
|
|
40
|
+
key_down(scancode)
|
|
41
|
+
else
|
|
42
|
+
key_up(scancode)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Lets go of every key, SHIFT among them.
|
|
47
|
+
def release_all
|
|
48
|
+
@held.each_value { |key| @keyboard.release(key) }
|
|
49
|
+
@held = {}
|
|
50
|
+
@left_shift = false
|
|
51
|
+
@right_shift = false
|
|
52
|
+
@left_alt = false
|
|
53
|
+
@right_alt = false
|
|
54
|
+
@forced = -1
|
|
55
|
+
sync_shift
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def key_down(scancode)
|
|
61
|
+
key_up(scancode) if @held.key?(scancode)
|
|
62
|
+
key, shift = alt? ? shifted_key(scancode) : Keys.pet_key(scancode, shifted?, @layout)
|
|
63
|
+
return if key == :none
|
|
64
|
+
|
|
65
|
+
unless shift == :host
|
|
66
|
+
@forced = scancode
|
|
67
|
+
@forced_shift = shift == :on
|
|
68
|
+
end
|
|
69
|
+
sync_shift
|
|
70
|
+
@held[scancode] = key
|
|
71
|
+
@keyboard.press(key)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def key_up(scancode)
|
|
75
|
+
key = @held.delete(scancode)
|
|
76
|
+
return if key.nil?
|
|
77
|
+
|
|
78
|
+
@keyboard.release(key)
|
|
79
|
+
return unless @forced == scancode
|
|
80
|
+
|
|
81
|
+
@forced = -1
|
|
82
|
+
sync_shift
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def shift_key(scancode, down)
|
|
86
|
+
if scancode == LSHIFT
|
|
87
|
+
@left_shift = down
|
|
88
|
+
else
|
|
89
|
+
@right_shift = down
|
|
90
|
+
end
|
|
91
|
+
sync_shift
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def alt_key(scancode, down)
|
|
95
|
+
if scancode == LALT
|
|
96
|
+
@left_alt = down
|
|
97
|
+
else
|
|
98
|
+
@right_alt = down
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The PET key for the scancode with Alt down: for a letter or a key
|
|
103
|
+
# that types a character, the key its unshifted character is on, with
|
|
104
|
+
# SHIFT, and for any other key the one it presses without Alt.
|
|
105
|
+
def shifted_key(scancode)
|
|
106
|
+
key, shift = Keys.pet_key(scancode, false, @layout)
|
|
107
|
+
return [key, shift] if key == :none
|
|
108
|
+
return [key, shift] unless scancode.between?(4, 29) || Keys::US_CHARACTERS.key?(scancode)
|
|
109
|
+
|
|
110
|
+
[key, :on]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def shifted? = @left_shift || @right_shift
|
|
114
|
+
|
|
115
|
+
def alt? = @left_alt || @right_alt
|
|
116
|
+
|
|
117
|
+
def sync_shift
|
|
118
|
+
shift = @forced == -1 ? shifted? : @forced_shift
|
|
119
|
+
@keyboard.release(:lshift)
|
|
120
|
+
@keyboard.press(:lshift) if shift
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -15,7 +15,8 @@ module Badline
|
|
|
15
15
|
# way: #wait handles the window's clicks, keys and drops between frames
|
|
16
16
|
# and redraws it at the display's pace. The SID model buttons switch
|
|
17
17
|
# the chips playing on the spot, and each tune after it starts on the
|
|
18
|
-
# same choice. AUTO gives each of a tune's SIDs its own model.
|
|
18
|
+
# same choice. AUTO gives each of a tune's SIDs its own model. It
|
|
19
|
+
# quits once it has drawn `frames` frames (--frames), unless that's 0.
|
|
19
20
|
class PlayerController < Audio::Terminal
|
|
20
21
|
FRAME = 1.0 / 50
|
|
21
22
|
|
|
@@ -30,8 +31,10 @@ module Badline
|
|
|
30
31
|
Keys::TAB => :view, 6 => :chip
|
|
31
32
|
}.freeze
|
|
32
33
|
|
|
33
|
-
def initialize(input:, output:)
|
|
34
|
-
super
|
|
34
|
+
def initialize(input:, output:, frames: 0)
|
|
35
|
+
super(input:, output:)
|
|
36
|
+
@frame_limit = frames
|
|
37
|
+
@frames = 0
|
|
35
38
|
@screen = PlayerScreen.new
|
|
36
39
|
@player = nil
|
|
37
40
|
@tune_file = nil
|
|
@@ -73,7 +76,10 @@ module Badline
|
|
|
73
76
|
actions = poll
|
|
74
77
|
deadline = now + seconds
|
|
75
78
|
loop do
|
|
76
|
-
|
|
79
|
+
if now >= @next_draw
|
|
80
|
+
draw
|
|
81
|
+
actions << :quit if @frames == @frame_limit
|
|
82
|
+
end
|
|
77
83
|
left = deadline - now
|
|
78
84
|
break if left <= 0 || !actions.empty?
|
|
79
85
|
|
|
@@ -211,6 +217,7 @@ module Badline
|
|
|
211
217
|
|
|
212
218
|
def draw
|
|
213
219
|
@next_draw = now + FRAME
|
|
220
|
+
@frames += 1
|
|
214
221
|
@screen.draw(@state, @player, @history, @tune_file, SIDTune::STIL::Credit.at(@credits, @state.played))
|
|
215
222
|
end
|
|
216
223
|
|
|
@@ -71,7 +71,8 @@ module Badline
|
|
|
71
71
|
else
|
|
72
72
|
timing = @computer.timing
|
|
73
73
|
@screen = Screen.new(@computer.video, timing.crop,
|
|
74
|
-
pixel_width: timing.pixel_width,
|
|
74
|
+
pixel_width: timing.pixel_width, dots: timing.dots,
|
|
75
|
+
frame_lines: timing.lines_per_frame)
|
|
75
76
|
end
|
|
76
77
|
end
|
|
77
78
|
|
|
@@ -39,19 +39,22 @@ module Badline
|
|
|
39
39
|
def self.page?(name) = name.empty? || PauseMenu::PAGES.any? { |page| page.to_s == name }
|
|
40
40
|
|
|
41
41
|
# Whether the family's machine has a key of the name. The Plus/4 has
|
|
42
|
-
# no RESTORE.
|
|
42
|
+
# no RESTORE, and the PET neither that nor joysticks.
|
|
43
43
|
def self.key?(name, family)
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
pet = family == :pet
|
|
45
|
+
(name == "restore" && !pet && family != :plus4) || (family == :c128 && LOCKS.include?(name)) ||
|
|
46
|
+
!keyboard_key(name, family).nil? || (!pet && !joystick_direction(name).nil?)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# The family's Keyboard key for a name such as space, return or a,
|
|
49
|
-
# on the C128 esc or keypad1,
|
|
50
|
-
#
|
|
51
|
-
# C128's.
|
|
50
|
+
# on the C128 esc or keypad1, on the Plus/4 help or crsr_up, or on
|
|
51
|
+
# the PET rvs or kp1, of either of its keyboards. The shifted cursor
|
|
52
|
+
# keys, such as cursor_up, are the C64's and the C128's.
|
|
52
53
|
def self.keyboard_key(name, family)
|
|
54
|
+
return nil if name == "none"
|
|
55
|
+
|
|
53
56
|
key = keyboard_matrix(family).flatten.find { |candidate| candidate.to_s == name }
|
|
54
|
-
return key unless key.nil? && family
|
|
57
|
+
return key unless key.nil? && !%i[plus4 pet].include?(family)
|
|
55
58
|
|
|
56
59
|
Input::Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name }
|
|
57
60
|
end
|
|
@@ -60,6 +63,7 @@ module Badline
|
|
|
60
63
|
case family
|
|
61
64
|
when :c128 then C128::KEYBOARD_MATRIX
|
|
62
65
|
when :plus4 then Plus4::KeyboardPort::MATRIX
|
|
66
|
+
when :pet then PET::KeyboardPorts::GRAPHICS_MATRIX + PET::KeyboardPorts::BUSINESS_MATRIX
|
|
63
67
|
else Input::Keyboard::C64_MATRIX
|
|
64
68
|
end
|
|
65
69
|
end
|
|
@@ -223,8 +227,11 @@ module Badline
|
|
|
223
227
|
puts "Ejected the #{what}"
|
|
224
228
|
end
|
|
225
229
|
|
|
226
|
-
# Whether there's one in to eject. A VIC-20 has no cartridge to take
|
|
227
|
-
|
|
230
|
+
# Whether there's one in to eject. A VIC-20 has no cartridge to take
|
|
231
|
+
# out yet, and a PET no expansion port.
|
|
232
|
+
def ejectable?(computer, what)
|
|
233
|
+
(what != "cartridge" || !%i[vic20 pet].include?(computer.family)) && inserted?(computer, what)
|
|
234
|
+
end
|
|
228
235
|
|
|
229
236
|
def inserted?(computer, what)
|
|
230
237
|
return !computer.datasette.tape.nil? if what == "tape"
|
data/lib/badline/frontend.rb
CHANGED
|
@@ -15,6 +15,7 @@ require "badline/frontend/audio_device"
|
|
|
15
15
|
require "badline/frontend/sound"
|
|
16
16
|
require "badline/frontend/pacer"
|
|
17
17
|
require "badline/frontend/frame_report"
|
|
18
|
+
require "badline/frontend/pet_keyboard"
|
|
18
19
|
require "badline/frontend/controls"
|
|
19
20
|
require "badline/frontend/pad_port"
|
|
20
21
|
require "badline/frontend/gamepads"
|
|
@@ -19,7 +19,7 @@ module Badline
|
|
|
19
19
|
@channels.close(secondary)
|
|
20
20
|
return report(FILE_EXISTS)
|
|
21
21
|
end
|
|
22
|
-
return refuse_write(secondary) unless writable_disk?
|
|
22
|
+
return refuse_write(secondary) unless writable_disk? || channel_save?(secondary)
|
|
23
23
|
|
|
24
24
|
open_writer(secondary, WriteFile.new(file, type || :seq, mode: replace ? :replace : :write))
|
|
25
25
|
end
|
|
@@ -41,6 +41,8 @@ module Badline
|
|
|
41
41
|
|
|
42
42
|
def writable_disk? = @storage.respond_to?(:writable?) && @storage.writable?
|
|
43
43
|
|
|
44
|
+
def channel_save?(secondary) = @channel_saves && secondary == 1 && saves?
|
|
45
|
+
|
|
44
46
|
# Runs a write to the disk and reports how it went. Returns whether
|
|
45
47
|
# the disk took it.
|
|
46
48
|
def writing
|
|
@@ -68,7 +68,12 @@ module Badline
|
|
|
68
68
|
|
|
69
69
|
# A drive powers on reporting its DOS version, as a reset does. It
|
|
70
70
|
# can take the RAM of a drive it replaces, and initializes its disk.
|
|
71
|
-
|
|
71
|
+
# With +channel_saves+, SAVE's channel, secondary address 1, takes a
|
|
72
|
+
# program for storage that saves only whole files, such as a host
|
|
73
|
+
# directory, and saves it when the channel closes: a PET's SAVE sends
|
|
74
|
+
# it through the bus with no trap of its own.
|
|
75
|
+
def initialize(storage, memory = Memory.new, channel_saves: false)
|
|
76
|
+
@channel_saves = channel_saves
|
|
72
77
|
@channels = Channels.new
|
|
73
78
|
@status = Status.new
|
|
74
79
|
@memory = memory
|