badline 0.17.0 → 0.18.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 +153 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +169 -20
- data/doc/pinned-behaviour.md +128 -0
- data/doc/snapshot-format.md +253 -18
- data/doc/snapshots.md +57 -23
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +1 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +1 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +5 -3
- data/lib/badline/cbm2.rb +6 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- data/lib/badline/chips/riot/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +4 -2
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
- data/lib/badline/chips/vic_i.rb +84 -21
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model2031.rb +6 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +192 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +179 -0
- data/lib/badline/drives/model8050/board.rb +77 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/idle.rb +11 -7
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +18 -130
- data/lib/badline/drives/model9060/idle.rb +149 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +195 -0
- data/lib/badline/frontend/app.rb +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +31 -2
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +116 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +190 -0
- data/lib/badline/machine/ieee_drives.rb +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +46 -15
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/true_drive.rb +42 -45
- data/lib/badline/media.rb +12 -6
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +43 -11
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +81 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +216 -0
- data/lib/badline/pet.rb +19 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
- data/lib/badline/roms/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +166 -0
- data/lib/badline/roms/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.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 +27 -6
- data/lib/badline/snapshot/image.rb +4 -1
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- data/lib/badline/snapshot/vice/pet.rb +13 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot.rb +4 -0
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20.rb +26 -12
- data/lib/badline.rb +1 -0
- metadata +77 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/drives/ieee_models"
|
|
3
4
|
require "badline/drives/model2031"
|
|
4
5
|
require "badline/drives/model8050"
|
|
6
|
+
require "badline/drives/model9060"
|
|
7
|
+
require "badline/drives/model1001"
|
|
8
|
+
require "badline/drives/model4040"
|
|
5
9
|
|
|
6
10
|
module Badline
|
|
7
11
|
module Machine
|
|
8
|
-
# A machine's true drive on its IEEE-488 bus
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# A machine's true drive on its IEEE-488 bus, one at a time, of a model
|
|
13
|
+
# Drives::IEEEModels names: a Drives::Model2031, Model8050, Model8250,
|
|
14
|
+
# Model1001, Model4040, Model3040, Model2040, Model9060 or Model9090. It
|
|
15
|
+
# is clocked after the CPU each cycle at the machine's clock
|
|
16
|
+
# (ieee_host_clock_hz) and reset with it, as the machine's RES line
|
|
17
|
+
# reaches IFC. The traps on the KERNAL's primitives leave device 8 to
|
|
18
|
+
# the drive while it is device 8 (trap_device). The PET (PET::IEEEDrive) and the CBM-II
|
|
14
19
|
# (CBM2::IEEEDrive) include it.
|
|
15
20
|
#
|
|
16
21
|
# The machine's @ieee is its side of the bus: #bus, the
|
|
@@ -18,142 +23,126 @@ module Badline
|
|
|
18
23
|
# machine pulls now, and #cycle_lines, which does so once a cycle if
|
|
19
24
|
# the machine doesn't push them as its chips are written.
|
|
20
25
|
module IEEEDrives
|
|
21
|
-
# The
|
|
22
|
-
attr_reader :
|
|
23
|
-
|
|
24
|
-
# The 8050 or 8250 on the bus, or nil.
|
|
25
|
-
attr_reader :drive8050
|
|
26
|
-
|
|
27
|
-
# Plugs in a Drives::Model2031, in place of the drive there.
|
|
28
|
-
def attach_drive2031(drive)
|
|
29
|
-
detach_true_drives
|
|
30
|
-
connect_drive(drive)
|
|
31
|
-
@drive2031 = drive
|
|
32
|
-
@ieee_trap&.device = trap_device
|
|
33
|
-
@init_threshold = [@init_threshold, Badline::Drives::Model2031::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Unplugs the 2031, leaving it on a bus of its own.
|
|
37
|
-
def detach_drive2031
|
|
38
|
-
drive = @drive2031
|
|
39
|
-
return unless drive
|
|
26
|
+
# The true drive on the bus, or nil.
|
|
27
|
+
attr_reader :true_drive
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@ieee_trap&.device = trap_device
|
|
44
|
-
end
|
|
29
|
+
# The model plug_true_drive plugs in, one Drives::IEEEModels names.
|
|
30
|
+
attr_reader :true_drive_model
|
|
45
31
|
|
|
46
|
-
# Plugs in
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@
|
|
32
|
+
# Plugs in the drive, in place of the drive there.
|
|
33
|
+
def attach_ieee_drive(drive)
|
|
34
|
+
detach_ieee_drive
|
|
35
|
+
drive.host_clock_hz = ieee_host_clock_hz
|
|
36
|
+
drive.connect(@ieee.bus)
|
|
37
|
+
@ieee.push_lines
|
|
38
|
+
@true_drive = drive
|
|
52
39
|
@ieee_trap&.device = trap_device
|
|
53
|
-
@init_threshold = [@init_threshold,
|
|
40
|
+
@init_threshold = [@init_threshold, drive.boot_cycles].max if @cycles < @init_threshold
|
|
54
41
|
end
|
|
55
42
|
|
|
56
|
-
# Unplugs the
|
|
57
|
-
def
|
|
58
|
-
drive = @
|
|
43
|
+
# Unplugs the true drive, leaving it on a bus of its own.
|
|
44
|
+
def detach_ieee_drive
|
|
45
|
+
drive = @true_drive
|
|
59
46
|
return unless drive
|
|
60
47
|
|
|
61
48
|
drive.connect(Badline::Drives::IEEEBus.new)
|
|
62
|
-
@
|
|
49
|
+
@true_drive = nil
|
|
63
50
|
@ieee_trap&.device = trap_device
|
|
64
51
|
end
|
|
65
52
|
|
|
66
|
-
# Plugs in
|
|
67
|
-
#
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
drive.tap { |unit| attach_drive8050(unit) }
|
|
53
|
+
# Plugs in the model as +device+, in place of the drive there, and
|
|
54
|
+
# returns it.
|
|
55
|
+
def plug_ieee_drive(model, device: 8)
|
|
56
|
+
build_ieee_drive(model, device).tap { |drive| attach_ieee_drive(drive) }
|
|
71
57
|
end
|
|
72
58
|
|
|
73
|
-
# The
|
|
74
|
-
|
|
59
|
+
# The drive of a dual unit a disk image given on its own goes in
|
|
60
|
+
# (Media::TrueDrive): drive 0.
|
|
61
|
+
def disk_drive_number = 0
|
|
75
62
|
|
|
76
|
-
# Plugs in
|
|
77
|
-
# returns it.
|
|
78
|
-
def plug_true_drive
|
|
79
|
-
Badline::Drives::Model2031.new.tap { |drive| attach_drive2031(drive) }
|
|
80
|
-
end
|
|
63
|
+
# Plugs in the model true_drive_model names as device 8, in place of
|
|
64
|
+
# the drive there, and returns it.
|
|
65
|
+
def plug_true_drive = plug_ieee_drive(@true_drive_model)
|
|
81
66
|
|
|
82
|
-
# Settles for a snapshot, then runs on until
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# instruction. It stands in for Machine::Settling's, which the
|
|
67
|
+
# Settles for a snapshot, then runs on until the true drive is where
|
|
68
|
+
# VICE saves it (the drive's settled_for_vice?) between instructions
|
|
69
|
+
# with the machine's, or for so many cycles, then on to the machine's
|
|
70
|
+
# next instruction. It stands in for Machine::Settling's, which the
|
|
87
71
|
# machines include first.
|
|
88
72
|
def settle_for_vice!
|
|
89
73
|
ran = settle_for_snapshot!
|
|
90
|
-
|
|
74
|
+
drive = @true_drive
|
|
75
|
+
return ran if drive.nil?
|
|
91
76
|
|
|
92
|
-
until ran >= Machine::Settling::LIMIT || (
|
|
77
|
+
until ran >= Machine::Settling::LIMIT || (drive.settled_for_vice? && settled_for_snapshot?)
|
|
93
78
|
cycle!
|
|
94
79
|
ran += 1
|
|
95
80
|
end
|
|
96
81
|
ran + settle_for_snapshot!
|
|
97
82
|
end
|
|
98
83
|
|
|
99
|
-
# Whether the true drive is where VICE saves it (#settle_for_vice!).
|
|
100
|
-
def drive_settled_for_vice?
|
|
101
|
-
return @drive2031.cpu.boundary? if @drive2031
|
|
102
|
-
|
|
103
|
-
fdc = @drive8050.fdc_cpu
|
|
104
|
-
@drive8050.ip_cpu.boundary? && fdc.boundary? && fdc.program_counter == Badline::Drives::Model8050::Idle::FDC_LOOP
|
|
105
|
-
end
|
|
106
|
-
|
|
107
84
|
private
|
|
108
85
|
|
|
109
|
-
def init_ieee_drives
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def detach_true_drives
|
|
115
|
-
detach_drive2031
|
|
116
|
-
detach_drive8050
|
|
117
|
-
end
|
|
86
|
+
def init_ieee_drives(true_drive_model)
|
|
87
|
+
unless Badline::Drives::IEEEModels::NAMES.include?(true_drive_model)
|
|
88
|
+
raise ArgumentError, "no true drive model #{true_drive_model} for the IEEE-488 bus"
|
|
89
|
+
end
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
91
|
+
@true_drive_model = true_drive_model
|
|
92
|
+
@true_drive = nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# A new drive of the model as +device+.
|
|
96
|
+
def build_ieee_drive(model, device)
|
|
97
|
+
case model
|
|
98
|
+
when "8050" then Badline::Drives::Model8050.new(device:)
|
|
99
|
+
when "8250" then Badline::Drives::Model8250.new(device:)
|
|
100
|
+
when "1001" then Badline::Drives::Model1001.new(device:)
|
|
101
|
+
when "4040" then Badline::Drives::Model4040.new(device:)
|
|
102
|
+
when "3040" then Badline::Drives::Model3040.new(device:)
|
|
103
|
+
when "2040" then Badline::Drives::Model2040.new(device:)
|
|
104
|
+
when "9060" then Badline::Drives::Model9060.new(device:)
|
|
105
|
+
when "9090" then Badline::Drives::Model9090.new(device:)
|
|
106
|
+
else Badline::Drives::Model2031.new(device:)
|
|
107
|
+
end
|
|
123
108
|
end
|
|
124
109
|
|
|
125
110
|
# The true drive's cycles for one of the machine's, after the lines
|
|
126
111
|
# it pulls.
|
|
127
112
|
def clock_ieee_drives
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return unless drive2031 || drive8050
|
|
113
|
+
drive = @true_drive
|
|
114
|
+
return unless drive
|
|
131
115
|
|
|
132
116
|
@ieee.cycle_lines
|
|
133
|
-
|
|
134
|
-
drive8050&.host_cycle!
|
|
117
|
+
drive.host_cycle!
|
|
135
118
|
end
|
|
136
119
|
|
|
137
120
|
# The RES line reaches the true drive as IFC.
|
|
138
121
|
def reset_ieee_drives
|
|
139
|
-
@
|
|
140
|
-
|
|
122
|
+
@true_drive&.reset!
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def save_ieee_drive_fields(out)
|
|
126
|
+
out.text("host.ieee_drive_model", @true_drive_model)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def load_ieee_drive_fields(input)
|
|
130
|
+
@true_drive_model = input.choice("host.ieee_drive_model", Badline::Drives::IEEEModels::NAMES,
|
|
131
|
+
@true_drive_model)
|
|
141
132
|
end
|
|
142
133
|
|
|
143
134
|
# The true drive as keyed snapshot modules named from its device
|
|
144
135
|
# number: BL-D8, BL-D8-CPU and so on for device 8.
|
|
145
136
|
def save_drive_modules(sections)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
drive8050 = @drive8050
|
|
149
|
-
drive8050&.save_modules(sections, "BL-D#{drive8050.device}")
|
|
137
|
+
drive = @true_drive
|
|
138
|
+
drive&.save_modules(sections, "BL-D#{drive.device}")
|
|
150
139
|
sections
|
|
151
140
|
end
|
|
152
141
|
|
|
153
|
-
# The true drive a snapshot's modules hold,
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
142
|
+
# The true drive a snapshot's modules hold, of the model they name,
|
|
143
|
+
# built afresh unless the same model is on the bus as the same
|
|
144
|
+
# device, or none when they hold no drive. The bus then carries the
|
|
145
|
+
# machine's lines as its chips left them.
|
|
157
146
|
def load_drive_modules(modules)
|
|
158
147
|
device = 8
|
|
159
148
|
input = modules.optional("BL-D8")
|
|
@@ -161,33 +150,21 @@ module Badline
|
|
|
161
150
|
device += 1
|
|
162
151
|
input = modules.optional("BL-D#{device}")
|
|
163
152
|
end
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
load_drive8050_modules(modules, input, model, device)
|
|
167
|
-
elsif input
|
|
168
|
-
load_drive2031_modules(modules, input, device)
|
|
153
|
+
if input
|
|
154
|
+
load_ieee_drive_modules(modules, input, device)
|
|
169
155
|
else
|
|
170
|
-
|
|
156
|
+
detach_ieee_drive
|
|
171
157
|
end
|
|
172
158
|
@ieee.push_lines
|
|
173
159
|
end
|
|
174
160
|
|
|
175
|
-
def
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def load_drive8050_modules(modules, input, model, device)
|
|
183
|
-
detach_drive2031
|
|
184
|
-
drive = @drive8050
|
|
185
|
-
detach_drive8050 if drive && (drive.device != device || drive.model_name != model)
|
|
186
|
-
unless @drive8050
|
|
187
|
-
unit = model == "8250" ? Badline::Drives::Model8250.new(device:) : Badline::Drives::Model8050.new(device:)
|
|
188
|
-
attach_drive8050(unit)
|
|
189
|
-
end
|
|
190
|
-
@drive8050.load_modules(modules, input, "BL-D#{device}")
|
|
161
|
+
def load_ieee_drive_modules(modules, input, device)
|
|
162
|
+
model = input.text("model", Badline::Drives::IEEEModels::DEFAULT)
|
|
163
|
+
model = Badline::Drives::IEEEModels::DEFAULT unless Badline::Drives::IEEEModels::NAMES.include?(model)
|
|
164
|
+
drive = @true_drive
|
|
165
|
+
detach_ieee_drive if drive && (drive.device != device || drive.model_name != model)
|
|
166
|
+
drive = @true_drive || plug_ieee_drive(model, device:)
|
|
167
|
+
drive.load_modules(modules, input, "BL-D#{device}")
|
|
191
168
|
end
|
|
192
169
|
end
|
|
193
170
|
end
|
|
@@ -28,13 +28,30 @@ module Badline
|
|
|
28
28
|
# The PET's under BASIC 1, outside the zero page.
|
|
29
29
|
PET_BASIC1_LAYOUT = Layout.new(address: 0x020f, count: 0x020d, capacity: 10)
|
|
30
30
|
|
|
31
|
-
def type_text(text)
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
def type_text(text) = type_on_init(text.bytes.map { |b| ascii_to_petscii(b) })
|
|
32
|
+
|
|
33
|
+
# The keys type_text left for the KERNAL to boot first, under
|
|
34
|
+
# host.init_keys, so that a machine restored before then types them.
|
|
35
|
+
def save_init_keys(out)
|
|
36
|
+
keys = @init_keys
|
|
37
|
+
out.ints("host.init_keys", keys) if keys
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def load_init_keys(input)
|
|
41
|
+
keys = input.ints("host.init_keys", [])
|
|
42
|
+
type_on_init(keys.dup) unless keys.empty?
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
private
|
|
37
46
|
|
|
47
|
+
def type_on_init(codes)
|
|
48
|
+
@init_keys = codes if booting?
|
|
49
|
+
on_init do
|
|
50
|
+
@init_keys = nil
|
|
51
|
+
@pending_keys = codes
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
38
55
|
def feed_keyboard
|
|
39
56
|
buffer = keyboard_buffer
|
|
40
57
|
return unless ram.peek(buffer.count).zero?
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Machine
|
|
5
|
+
# The sound of one logic line a machine drives into its speaker or
|
|
6
|
+
# audio output, such as the PET's CB2 or the KIM-1's PB7. The class
|
|
7
|
+
# that includes it answers #line_high?, the line's level this cycle.
|
|
8
|
+
#
|
|
9
|
+
# The machine calls #cycle! once a cycle while #recording?, from #record
|
|
10
|
+
# on, so a sound nobody records costs only that check. Each sample is
|
|
11
|
+
# the share of its cycles the line spent high, through the output
|
|
12
|
+
# stage: a one-pole low-pass at 5 kHz and a one-pole high-pass at
|
|
13
|
+
# 100 Hz, so a line left high or low settles to silence.
|
|
14
|
+
module LineSound
|
|
15
|
+
LOW_PASS_HZ = 5_000
|
|
16
|
+
HIGH_PASS_HZ = 100
|
|
17
|
+
|
|
18
|
+
# A swing from the line low to the line high, as a signed 16-bit
|
|
19
|
+
# sample.
|
|
20
|
+
SCALE = 24_000.0
|
|
21
|
+
|
|
22
|
+
attr_reader :recording
|
|
23
|
+
alias recording? recording
|
|
24
|
+
|
|
25
|
+
# Starts collecting the output, averaged down to `rate` samples a
|
|
26
|
+
# second at `clock_hz` cycles a second, for #drain_samples. The
|
|
27
|
+
# output stage takes the line's level now as settled.
|
|
28
|
+
def record(rate:, clock_hz: 1_000_000)
|
|
29
|
+
@rate = rate
|
|
30
|
+
@clock_hz = clock_hz
|
|
31
|
+
@remainder = 0
|
|
32
|
+
@window = next_window
|
|
33
|
+
@left = @window
|
|
34
|
+
@sum = 0
|
|
35
|
+
@high = line_high?
|
|
36
|
+
@run = 0
|
|
37
|
+
@low = @drift = @high ? 1.0 : 0.0
|
|
38
|
+
@low_pass = 1.0 - Math.exp(-2.0 * Math::PI * LOW_PASS_HZ / rate)
|
|
39
|
+
@high_pass = 1.0 - Math.exp(-2.0 * Math::PI * HIGH_PASS_HZ / rate)
|
|
40
|
+
@samples = []
|
|
41
|
+
@recording = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Takes the line's level for this cycle, while recording.
|
|
45
|
+
def cycle!
|
|
46
|
+
high = line_high?
|
|
47
|
+
if high == @high
|
|
48
|
+
@run += 1
|
|
49
|
+
else
|
|
50
|
+
hold(@high, @run)
|
|
51
|
+
@high = high
|
|
52
|
+
@run = 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The samples closed since the last drain.
|
|
57
|
+
def drain_samples
|
|
58
|
+
return [] unless @recording
|
|
59
|
+
|
|
60
|
+
hold(@high, @run)
|
|
61
|
+
@run = 0
|
|
62
|
+
samples = @samples
|
|
63
|
+
@samples = []
|
|
64
|
+
samples
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def init_line_sound
|
|
70
|
+
@recording = false
|
|
71
|
+
@high = true
|
|
72
|
+
@run = 0
|
|
73
|
+
@rate = 0
|
|
74
|
+
@clock_hz = 0
|
|
75
|
+
@remainder = 0
|
|
76
|
+
@window = 0
|
|
77
|
+
@left = 0
|
|
78
|
+
@sum = 0
|
|
79
|
+
@low = 0.0
|
|
80
|
+
@drift = 0.0
|
|
81
|
+
@low_pass = 0.0
|
|
82
|
+
@high_pass = 0.0
|
|
83
|
+
@samples = []
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Adds `cycles` cycles with the line high or low, closing every
|
|
87
|
+
# sample they finish.
|
|
88
|
+
def hold(high, cycles)
|
|
89
|
+
left = @left
|
|
90
|
+
while cycles >= left
|
|
91
|
+
@sum += left if high
|
|
92
|
+
cycles -= left
|
|
93
|
+
close
|
|
94
|
+
left = @left
|
|
95
|
+
end
|
|
96
|
+
@sum += cycles if high
|
|
97
|
+
@left = left - cycles
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def close
|
|
101
|
+
@low += @low_pass * (@sum.fdiv(@window) - @low)
|
|
102
|
+
@drift += @high_pass * (@low - @drift)
|
|
103
|
+
sample = ((@low - @drift) * SCALE).round
|
|
104
|
+
sample = 32_767 if sample > 32_767
|
|
105
|
+
sample = -32_768 if sample < -32_768
|
|
106
|
+
@samples << sample
|
|
107
|
+
@sum = 0
|
|
108
|
+
@window = next_window
|
|
109
|
+
@left = @window
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Each window is a whole number of cycles, and they add up to the
|
|
113
|
+
# clock's cycles over a second.
|
|
114
|
+
def next_window
|
|
115
|
+
@remainder += @clock_hz
|
|
116
|
+
cycles = @remainder / @rate
|
|
117
|
+
@remainder -= cycles * @rate
|
|
118
|
+
cycles
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
data/lib/badline/machine.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "badline/machine/clocking"
|
|
4
4
|
require "badline/machine/keyboard_buffer"
|
|
5
|
+
require "badline/machine/line_sound"
|
|
5
6
|
require "badline/machine/settling"
|
|
6
7
|
|
|
7
8
|
module Badline
|
|
@@ -58,6 +59,11 @@ module Badline
|
|
|
58
59
|
# and +capture_output+, and besides +model_label+, +cartridge_slots+ and
|
|
59
60
|
# +detach_cartridges+ for its two cartridges.
|
|
60
61
|
#
|
|
62
|
+
# The KIM-1 answers the identity, clocking, lifecycle and sound parts,
|
|
63
|
+
# with no video chip: a front end draws its panel (KIM1::Panel), and
|
|
64
|
+
# #true_drive is nil. Its keys are the panel's, through +press_key+,
|
|
65
|
+
# +release_key+, +type_keys+, +press_stop+ and +press_reset+.
|
|
66
|
+
#
|
|
61
67
|
# The PET answers the identity, clocking, lifecycle, video, sound and
|
|
62
68
|
# keyboard parts, with no joysticks, RESTORE or control ports, and of
|
|
63
69
|
# the rest +ram+, +load_prg+, +type_text+, +mount+, +unmount+,
|
|
@@ -69,22 +75,26 @@ module Badline
|
|
|
69
75
|
# Media the machine named doesn't take.
|
|
70
76
|
class Unsupported < ArgumentError; end
|
|
71
77
|
|
|
72
|
-
# A new machine of `family`, :c64, :vic20, :c128, :plus4, :pet or :
|
|
78
|
+
# A new machine of `family`, :c64, :vic20, :c128, :plus4, :pet, :cbm2 or :kim1, the
|
|
73
79
|
# model of it `model` names. A C64's model is one of Model::ALL, with
|
|
74
80
|
# its SID `sid_model` when not nil and an REU of `reu` K when not nil.
|
|
75
|
-
# A VIC-20's is "pal", with the RAM expansion `ram` names
|
|
81
|
+
# A VIC-20's is "pal" or "ntsc", with the RAM expansion `ram` names
|
|
76
82
|
# (Vic20::Bus::RAM_CONFIGURATIONS), unexpanded when nil. A C128's is
|
|
77
83
|
# one of C128::Model::ALL, with its SID `sid_model` when not nil, built
|
|
78
84
|
# for C64 mode. A Plus/4's is one of Plus4::Model::ALL or c116, with
|
|
79
85
|
# the model's own RAM. A PET's is one of PET::Model::ALL, with its own
|
|
80
|
-
# RAM and ROMs, its CRTC's editor the 50 Hz one
|
|
86
|
+
# RAM and ROMs, its CRTC's editor the 50 Hz one, the superpet a
|
|
87
|
+
# PET::SuperPET with its CPU switch at 6502. A CBM-II's is one of
|
|
81
88
|
# CBM2::Model::ALL, with VICE's KERNAL, 901244-04a, at 50 Hz, or a 510,
|
|
82
|
-
# one of CBM2::P500::Model::ALL, with its region's mains.
|
|
89
|
+
# one of CBM2::P500::Model::ALL, with its region's mains. A KIM-1 is
|
|
90
|
+
# the one model, with the RAM `ram` names
|
|
91
|
+
# (KIM1::Bus::RAM_CONFIGURATIONS), 1K when nil.
|
|
83
92
|
def self.build(family, model:, sid_model: nil, reu: nil, ram: nil)
|
|
93
|
+
return KIM1.new(ram:) if family == :kim1
|
|
84
94
|
return vic20(model, ram) if family == :vic20
|
|
85
95
|
return c128(model, sid_model, :c64) if family == :c128
|
|
86
96
|
return Plus4.new(model:) if family == :plus4
|
|
87
|
-
return PET.new(model:) if family == :pet
|
|
97
|
+
return model == "superpet" ? PET::SuperPET.new : PET.new(model:) if family == :pet
|
|
88
98
|
return CBM2::P500.new(model:) if family == :cbm2 && model.start_with?("510")
|
|
89
99
|
return CBM2.new(model:) if family == :cbm2
|
|
90
100
|
raise ArgumentError, "no machine family named #{family}" unless family == :c64
|
|
@@ -110,6 +120,7 @@ module Badline
|
|
|
110
120
|
return plus4_for(media, options) if family == :plus4
|
|
111
121
|
return pet_for(media, options) if family == :pet
|
|
112
122
|
return cbm2_for(media, options) if family == :cbm2
|
|
123
|
+
return kim1_for(media, options) if family == :kim1
|
|
113
124
|
|
|
114
125
|
sid_model = options.sid_model || Media.sid_model(media, otherwise: nil)
|
|
115
126
|
return build(:c64, model: options.model, sid_model:, reu: options.reu) unless family == :c128
|
|
@@ -119,11 +130,7 @@ module Badline
|
|
|
119
130
|
machine
|
|
120
131
|
end
|
|
121
132
|
|
|
122
|
-
def self.vic20(model, ram)
|
|
123
|
-
raise ArgumentError, "no VIC-20 model named #{model}" unless model == "pal"
|
|
124
|
-
|
|
125
|
-
Vic20.new(ram: ram || :unexpanded)
|
|
126
|
-
end
|
|
133
|
+
def self.vic20(model, ram) = Vic20.new(ram: ram || :unexpanded, model:)
|
|
127
134
|
private_class_method :vic20
|
|
128
135
|
|
|
129
136
|
def self.c128(model, sid_model, mode) = C128.new(model:, sid_model:, mode:)
|
|
@@ -160,27 +167,51 @@ module Badline
|
|
|
160
167
|
# A PET with the RAM --ram names (PET::Model::RAM_SIZES), or with the
|
|
161
168
|
# model's own without it, the editor at the frame rate --60hz or
|
|
162
169
|
# --editor asks for, the ROMs --basic, --editor and --characters fit it
|
|
163
|
-
# with,
|
|
170
|
+
# with, the 8296 board's jumpers --jumpers names, and the true drive
|
|
171
|
+
# --drive-type names. The SuperPET takes the frame rate, the 8032's
|
|
172
|
+
# BASIC and editors, the true drive, the CPU --cpu names and the bank
|
|
173
|
+
# RAM switch --bank-ram sets, and with the 6809 disks alone.
|
|
164
174
|
def self.pet_for(media, options)
|
|
165
175
|
unsupported = !media.nil? && Media::PETMedia::UNSUPPORTED.include?(File.extname(media).downcase)
|
|
166
176
|
raise Unsupported, "doesn't go in a PET" if unsupported
|
|
167
177
|
|
|
178
|
+
if options.model == "superpet"
|
|
179
|
+
disk = media.nil? || File.directory?(media) || %i[disk ieee_disk].include?(Media::Extensions.kind(media))
|
|
180
|
+
raise Unsupported, "doesn't go in the SuperPET's 6809 side" unless disk || options.cpu == "6502"
|
|
181
|
+
|
|
182
|
+
return PET::SuperPET.new(refresh_hz: options.refresh_hz, basic: options.basic, editor: options.editor,
|
|
183
|
+
cpu: options.cpu, bank_ram: options.bank_ram, true_drive_model: options.drive_type)
|
|
184
|
+
end
|
|
185
|
+
|
|
168
186
|
PET.new(model: options.model, ram_kb: options.ram_kb, refresh_hz: options.refresh_hz, basic: options.basic,
|
|
169
|
-
editor: options.editor, characters: options.characters, jumpers: options.jumpers
|
|
187
|
+
editor: options.editor, characters: options.characters, jumpers: options.jumpers,
|
|
188
|
+
true_drive_model: options.drive_type)
|
|
170
189
|
end
|
|
171
190
|
private_class_method :pet_for
|
|
172
191
|
|
|
173
192
|
# A CBM-II with the KERNAL --kernal names, on the mains --60hz asks
|
|
174
|
-
# for: a B series' 50 Hz without it, and a P500's its region's
|
|
193
|
+
# for: a B series' 50 Hz without it, and a P500's its region's, and
|
|
194
|
+
# the true drive --drive-type names.
|
|
175
195
|
def self.cbm2_for(media, options)
|
|
176
196
|
unsupported = !media.nil? && Media::CBM2Media::UNSUPPORTED.include?(File.extname(media).downcase)
|
|
177
197
|
raise Unsupported, "doesn't go in a CBM-II" if unsupported
|
|
178
|
-
return CBM2::P500.new(model: options.model, mains_hz: options.mains_hz) if options.p500?
|
|
179
198
|
|
|
180
|
-
|
|
199
|
+
drive = options.drive_type
|
|
200
|
+
return CBM2::P500.new(model: options.model, mains_hz: options.mains_hz, true_drive_model: drive) if options.p500?
|
|
201
|
+
|
|
202
|
+
CBM2.new(model: options.model, kernal: options.kernal, mains_hz: options.mains_hz || 50, true_drive_model: drive)
|
|
181
203
|
end
|
|
182
204
|
private_class_method :cbm2_for
|
|
183
205
|
|
|
206
|
+
# A KIM-1 with the RAM --ram names, on a teletype at --baud with
|
|
207
|
+
# --tty, for media it takes (Media::KIM1Media).
|
|
208
|
+
def self.kim1_for(media, options)
|
|
209
|
+
raise Unsupported, "doesn't go in a KIM-1" unless media.nil? || Media::KIM1Media.takes?(media)
|
|
210
|
+
|
|
211
|
+
KIM1.new(tty: options.tty?, baud: options.baud, ram: options.ram)
|
|
212
|
+
end
|
|
213
|
+
private_class_method :kim1_for
|
|
214
|
+
|
|
184
215
|
def self.c64_media?(media)
|
|
185
216
|
return false if media.nil? || !File.file?(media)
|
|
186
217
|
|
|
@@ -7,17 +7,21 @@ module Badline
|
|
|
7
7
|
# It loads on its own, for Options.
|
|
8
8
|
#
|
|
9
9
|
# - :disk, a disk image that the traps mount and a true drive reads
|
|
10
|
-
# - :ieee_disk, an 8050's or
|
|
11
|
-
# drive
|
|
10
|
+
# - :ieee_disk, an 8050's, 8250's or 2040's disk image, for a PET's
|
|
11
|
+
# IEEE-488 drive
|
|
12
|
+
# - :hard_disk, a D9060's or D9090's hard disk, which only a PET's or a
|
|
13
|
+
# CBM-II's true drive reads
|
|
12
14
|
# - :gcr, a disk's raw GCR, which only a true drive reads
|
|
13
15
|
# - :archive, a .t64, which the traps mount read-only
|
|
14
16
|
# - :tape, :cartridge and :tune
|
|
17
|
+
# - :papertape, a MOS papertape, which only the KIM-1 loads
|
|
15
18
|
# - :disk_list, a list of the disks of a set (DiskList)
|
|
16
19
|
module Extensions
|
|
17
20
|
KINDS = {
|
|
18
21
|
".d64" => :disk, ".d71" => :disk, ".d81" => :disk, ".g64" => :gcr, ".g71" => :gcr, ".t64" => :archive,
|
|
19
|
-
".d80" => :ieee_disk, ".d82" => :ieee_disk, ".
|
|
20
|
-
".m3u" => :disk_list, ".vfl" => :disk_list
|
|
22
|
+
".d80" => :ieee_disk, ".d82" => :ieee_disk, ".d67" => :ieee_disk, ".d90" => :hard_disk, ".tap" => :tape,
|
|
23
|
+
".crt" => :cartridge, ".sid" => :tune, ".m3u" => :disk_list, ".vfl" => :disk_list, ".ptp" => :papertape,
|
|
24
|
+
".pap" => :papertape
|
|
21
25
|
}.freeze
|
|
22
26
|
|
|
23
27
|
# The kind of the medium at `path`, by its extension.
|