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
|
@@ -195,7 +195,7 @@ module Badline
|
|
|
195
195
|
@unfiltered = 0
|
|
196
196
|
route_voice(voice1.output >> 7, 0x1)
|
|
197
197
|
route_voice(voice2.output >> 7, 0x2)
|
|
198
|
-
if @routing
|
|
198
|
+
if @routing.anybits?(0x4)
|
|
199
199
|
@input += voice3.output >> 7
|
|
200
200
|
elsif !@voice3_off
|
|
201
201
|
@unfiltered += voice3.output >> 7
|
|
@@ -216,9 +216,9 @@ module Badline
|
|
|
216
216
|
|
|
217
217
|
def filtered
|
|
218
218
|
value = 0
|
|
219
|
-
value += @lowpass if @mode
|
|
220
|
-
value += @bandpass if @mode
|
|
221
|
-
value += @highpass if @mode
|
|
219
|
+
value += @lowpass if @mode.anybits?(0x1)
|
|
220
|
+
value += @bandpass if @mode.anybits?(0x2)
|
|
221
|
+
value += @highpass if @mode.anybits?(0x4)
|
|
222
222
|
value
|
|
223
223
|
end
|
|
224
224
|
end
|
|
@@ -234,7 +234,7 @@ module Badline
|
|
|
234
234
|
def compare_pulse(phase = @accumulator >> 12) = (@pulse = @test || phase >= @pulse_width ? 0xfff : 0x000)
|
|
235
235
|
|
|
236
236
|
# More than one bit set in the selection.
|
|
237
|
-
def combined?(selected) = selected
|
|
237
|
+
def combined?(selected) = selected.anybits?(selected - 1)
|
|
238
238
|
|
|
239
239
|
# The one point in the cycle where the waveform selector drives the DAC:
|
|
240
240
|
# either it latches a fresh value, or nothing drives the line and the
|
|
@@ -274,8 +274,8 @@ module Badline
|
|
|
274
274
|
def advance
|
|
275
275
|
previous = @accumulator
|
|
276
276
|
@accumulator = (previous + @frequency) & 0xffffff
|
|
277
|
-
@msb_rising = @accumulator & ~previous
|
|
278
|
-
if @accumulator & ~previous
|
|
277
|
+
@msb_rising = (@accumulator & ~previous).anybits?(MSB)
|
|
278
|
+
if (@accumulator & ~previous).anybits?(0x080000)
|
|
279
279
|
@shift_pipeline = 2
|
|
280
280
|
elsif @shift_pipeline != 0 && (@shift_pipeline -= 1).zero?
|
|
281
281
|
write_back_shift
|
|
@@ -292,12 +292,12 @@ module Badline
|
|
|
292
292
|
# TriXOR = !Saw & ((!V3 & Ring) ^ bit23), where V3 is the modulating
|
|
293
293
|
# voice's MSB (SID/ringmod).
|
|
294
294
|
def inverted?(selected)
|
|
295
|
-
return false if selected
|
|
295
|
+
return false if selected.anybits?(0x2)
|
|
296
296
|
|
|
297
|
-
msb = @accumulator
|
|
297
|
+
msb = @accumulator.anybits?(MSB)
|
|
298
298
|
return msb unless @ring
|
|
299
299
|
|
|
300
|
-
msb ==
|
|
300
|
+
msb == @sync_source.accumulator.anybits?(MSB)
|
|
301
301
|
end
|
|
302
302
|
end
|
|
303
303
|
end
|
|
@@ -86,13 +86,15 @@ module Badline
|
|
|
86
86
|
LINE_LAYOUT = 1
|
|
87
87
|
|
|
88
88
|
# The VIC-I's fields in a keyed snapshot module (Snapshot::Keyed): the
|
|
89
|
-
# registers, the beam, the
|
|
90
|
-
#
|
|
89
|
+
# registers, the beam, the frame's length in lines and whether the
|
|
90
|
+
# next interlaced field is the long one, the text window's counters
|
|
91
|
+
# and the fetch in flight, the painter's lines under line.*, the picture, and the sound
|
|
91
92
|
# under sound.* What the registers decode to, where the line's fetches
|
|
92
93
|
# end and the next event are worked out on load.
|
|
93
94
|
module KeyedState
|
|
94
95
|
def save_fields(out)
|
|
95
96
|
out.bytes("registers", @registers).int("raster", @rasterline).int("column", @column)
|
|
97
|
+
out.int("frame_lines", @frame_lines).bool("long_field", @long_field)
|
|
96
98
|
out.bool("window_open", @window_open).bool("window_done", @window_done).int("row_start", @row_start)
|
|
97
99
|
out.int("matrix", @matrix).int("char_line", @char_line).int("text_rows", @text_rows)
|
|
98
100
|
out.int("fetch_from", @fetch_from).int("fetched", @fetched).int("code", @code)
|
|
@@ -106,6 +108,8 @@ module Badline
|
|
|
106
108
|
input.bytes_into("registers", @registers)
|
|
107
109
|
@rasterline = input.int("raster", @rasterline)
|
|
108
110
|
@column = input.int("column", @column)
|
|
111
|
+
@frame_lines = input.int("frame_lines", @lines_per_frame)
|
|
112
|
+
@long_field = input.bool("long_field", true)
|
|
109
113
|
@window_open = input.bool("window_open", @window_open)
|
|
110
114
|
@window_done = input.bool("window_done", @window_done)
|
|
111
115
|
@row_start = input.int("row_start", @row_start)
|
|
@@ -122,6 +126,7 @@ module Badline
|
|
|
122
126
|
end
|
|
123
127
|
|
|
124
128
|
def restored!
|
|
129
|
+
@frame_wrap = @frame_lines == @interlaced_lines ? @count_column : @wrap_column
|
|
125
130
|
@origin_x = @registers[0] & 0x7f
|
|
126
131
|
@columns = @registers[2] & 0x7f
|
|
127
132
|
@origin_y = @registers[1]
|
data/lib/badline/chips/vic_i.rb
CHANGED
|
@@ -7,13 +7,15 @@ require "badline/chips/vic_i/keyed_parts"
|
|
|
7
7
|
|
|
8
8
|
module Badline
|
|
9
9
|
module Chips
|
|
10
|
-
# The VIC-I, the 6561 on PAL machines
|
|
10
|
+
# The VIC-I, the 6561 on PAL machines or the 6560 on NTSC ones: its
|
|
11
|
+
# sixteen registers, the
|
|
11
12
|
# raster, the text window's fetches over the V-bus and the picture. The
|
|
12
13
|
# writes to the sound's registers, $900A-$900E, go on to the Sound
|
|
13
14
|
# attached as #sound.
|
|
14
15
|
#
|
|
15
16
|
# The registers repeat through $9000-$90FF. $9003 bit 7 and $9004 read
|
|
16
|
-
# the raster
|
|
17
|
+
# the raster count, its bit 0 and bits 8-1, which on the 6561 is the
|
|
18
|
+
# raster line (see Profile for the 6560's). $9006 and $9007 read the
|
|
17
19
|
# light pen, which nothing triggers yet, and $9008 and $9009 the two
|
|
18
20
|
# pots, which read $FF with nothing plugged in. The other registers
|
|
19
21
|
# read back what was written to them.
|
|
@@ -35,23 +37,48 @@ module Badline
|
|
|
35
37
|
# of a text row where the row started, and the next row starts where
|
|
36
38
|
# the last line of the row left it. It starts the frame at zero.
|
|
37
39
|
#
|
|
38
|
-
# The picture is 4 pixels a cycle, so 284 a line
|
|
39
|
-
# pixels come out in the two cycles after its
|
|
40
|
-
# display's lines start 6 cycles after the raster
|
|
41
|
-
# xvic's view of the frame does, so the character
|
|
42
|
-
# into the line lands 48 pixels in. Lines 0-27 are
|
|
40
|
+
# The picture is 4 pixels a cycle, so 284 a line on the 6561 and 260 on
|
|
41
|
+
# the 6560. A character's 8 pixels come out in the two cycles after its
|
|
42
|
+
# pattern fetch. The display's lines start 6 cycles after the raster
|
|
43
|
+
# line moves, as xvic's view of the frame does, so the character
|
|
44
|
+
# fetched 16 cycles into the line lands 48 pixels in. Lines 0-27 are
|
|
45
|
+
# blanked.
|
|
43
46
|
class VICI
|
|
44
47
|
include TextWindow
|
|
45
48
|
|
|
46
49
|
# What a VIC-I sets for the machine: the clock, the raster's cycles
|
|
47
|
-
# per line and lines per frame,
|
|
48
|
-
#
|
|
49
|
-
# the
|
|
50
|
-
|
|
50
|
+
# per line and lines per frame, how many lines at the top of the frame
|
|
51
|
+
# it blanks and how many it shows from there, and where in the line
|
|
52
|
+
# the raster count $9003 and $9004 read moves on, and how many lines
|
|
53
|
+
# the long field of an interlaced frame has, or 0 without interlace.
|
|
54
|
+
#
|
|
55
|
+
# The 6561's count is the raster line. The 6560's runs ahead of it: it
|
|
56
|
+
# reads the next line from +count_column+ on, and on the last line it
|
|
57
|
+
# reads one past the last line from there, then 0 from
|
|
58
|
+
# +wrap_column+ on, so the count's first and last values each last
|
|
59
|
+
# about half a line. The 6561's +count_column+ is past the end of the
|
|
60
|
+
# line, so it never moves early.
|
|
61
|
+
#
|
|
62
|
+
# With $9000 bit 7 set at the start of a frame, the 6560 interlaces:
|
|
63
|
+
# its frames alternate between the long field, +interlaced_lines+
|
|
64
|
+
# long, and the short one, a line shorter, the long one first. On the
|
|
65
|
+
# long field's last line the count wraps to 0 where it would move on,
|
|
66
|
+
# so its first and last values each last a whole line.
|
|
67
|
+
Profile = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :blanked_lines, :shown_lines,
|
|
68
|
+
:count_column, :wrap_column, :interlaced_lines)
|
|
51
69
|
|
|
52
70
|
# The 6561: 71 cycles by 312 lines, lines 0-27 blanked (Marko
|
|
53
71
|
# Mäkelä's VIC-I.txt).
|
|
54
|
-
PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28
|
|
72
|
+
PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28,
|
|
73
|
+
shown_lines: 284, count_column: 71, wrap_column: 71, interlaced_lines: 0)
|
|
74
|
+
|
|
75
|
+
# The 6560: 65 cycles by 261 lines on a 14.31818 MHz crystal divided
|
|
76
|
+
# by 14. The count's columns and the lines shown are xvic's, measured
|
|
77
|
+
# as a black box: the count moves on 28 cycles into the line, and
|
|
78
|
+
# wraps to 0 on the last line 61 cycles in. Interlaced, its fields are
|
|
79
|
+
# 263 and 262 lines.
|
|
80
|
+
NTSC = Profile.new(clock_hz: 1_022_727, cycles_per_line: 65, lines_per_frame: 261, blanked_lines: 28,
|
|
81
|
+
shown_lines: 233, count_column: 28, wrap_column: 61, interlaced_lines: 263)
|
|
55
82
|
|
|
56
83
|
PIXELS_PER_CYCLE = 4
|
|
57
84
|
|
|
@@ -71,6 +98,10 @@ module Badline
|
|
|
71
98
|
0xb27240, 0xf2b27f, 0xc2655d, 0xbdffff, 0xf299ff, 0x8de57f, 0x806eda, 0xffffa5
|
|
72
99
|
].freeze
|
|
73
100
|
|
|
101
|
+
# The display's lines: the frame's, or the long interlaced field's
|
|
102
|
+
# where that is longer.
|
|
103
|
+
attr_reader :height
|
|
104
|
+
|
|
74
105
|
attr_reader :rasterline, :column, :profile
|
|
75
106
|
attr_accessor :sound
|
|
76
107
|
|
|
@@ -78,7 +109,11 @@ module Badline
|
|
|
78
109
|
@profile = profile
|
|
79
110
|
@cycles_per_line = profile.cycles_per_line
|
|
80
111
|
@lines_per_frame = profile.lines_per_frame
|
|
81
|
-
@
|
|
112
|
+
@count_column = profile.count_column
|
|
113
|
+
@wrap_column = profile.wrap_column
|
|
114
|
+
@interlaced_lines = profile.interlaced_lines
|
|
115
|
+
@height = [@lines_per_frame, @interlaced_lines].max
|
|
116
|
+
@painter = Painter.new(@height, @cycles_per_line * PIXELS_PER_CYCLE, profile.blanked_lines)
|
|
82
117
|
@patterns = @painter.patterns
|
|
83
118
|
@colors = @painter.colors
|
|
84
119
|
@counts = @painter.counts
|
|
@@ -106,11 +141,14 @@ module Badline
|
|
|
106
141
|
@origin_x = @origin_y = @columns = @rows = 0
|
|
107
142
|
@char_shift = 3
|
|
108
143
|
@screen_base = @char_base = 0
|
|
144
|
+
@long_field = true
|
|
145
|
+
@frame_lines = @lines_per_frame
|
|
146
|
+
@frame_wrap = @wrap_column
|
|
109
147
|
new_frame
|
|
110
148
|
@fetch_from = @fetch_to = 0
|
|
111
149
|
@fetched = @slot = 0
|
|
112
150
|
@code = 0
|
|
113
|
-
@painter.power_on!(@
|
|
151
|
+
@painter.power_on!(@frame_lines - 1)
|
|
114
152
|
schedule
|
|
115
153
|
end
|
|
116
154
|
|
|
@@ -131,14 +169,12 @@ module Badline
|
|
|
131
169
|
# The sixteen registers as written.
|
|
132
170
|
def register_file = @registers.dup
|
|
133
171
|
|
|
134
|
-
# The display, a palette index a pixel,
|
|
135
|
-
# of the frame.
|
|
172
|
+
# The display, a palette index a pixel, 4 to a cycle of the line, and
|
|
173
|
+
# every line of the frame.
|
|
136
174
|
def display = @painter.display
|
|
137
175
|
|
|
138
176
|
def width = @painter.width
|
|
139
177
|
|
|
140
|
-
def height = @lines_per_frame
|
|
141
|
-
|
|
142
178
|
# Whether each line of the display has changed since the front end
|
|
143
179
|
# last cleared them.
|
|
144
180
|
def dirty_lines = @painter.dirty_lines
|
|
@@ -157,8 +193,8 @@ module Badline
|
|
|
157
193
|
def peek(addr)
|
|
158
194
|
register = addr & 0x0f
|
|
159
195
|
case register
|
|
160
|
-
when 0x03 then ((
|
|
161
|
-
when 0x04 then
|
|
196
|
+
when 0x03 then ((raster_count & 0x01) << 7) | (@registers[3] & 0x7f)
|
|
197
|
+
when 0x04 then raster_count >> 1
|
|
162
198
|
when 0x06, 0x07 then 0
|
|
163
199
|
when 0x08, 0x09 then 0xff
|
|
164
200
|
else @registers[register]
|
|
@@ -181,6 +217,16 @@ module Badline
|
|
|
181
217
|
end
|
|
182
218
|
end
|
|
183
219
|
|
|
220
|
+
# The raster count $9003 and $9004 read (see Profile).
|
|
221
|
+
def raster_count
|
|
222
|
+
line = @rasterline
|
|
223
|
+
column = @column
|
|
224
|
+
return line if column < @count_column
|
|
225
|
+
return 0 if line == @frame_lines - 1 && column >= @frame_wrap
|
|
226
|
+
|
|
227
|
+
line + 1
|
|
228
|
+
end
|
|
229
|
+
|
|
184
230
|
private
|
|
185
231
|
|
|
186
232
|
def event(column)
|
|
@@ -205,9 +251,10 @@ module Badline
|
|
|
205
251
|
|
|
206
252
|
def start_line
|
|
207
253
|
line = @rasterline + 1
|
|
208
|
-
if line == @
|
|
254
|
+
if line == @frame_lines
|
|
209
255
|
line = 0
|
|
210
256
|
new_frame
|
|
257
|
+
next_field
|
|
211
258
|
end
|
|
212
259
|
@rasterline = line
|
|
213
260
|
next_text_line if @window_open
|
|
@@ -215,6 +262,22 @@ module Badline
|
|
|
215
262
|
place_fetches
|
|
216
263
|
end
|
|
217
264
|
|
|
265
|
+
# How many lines the frame starting has, and where on its last line
|
|
266
|
+
# the count wraps: the field's, if $9000 bit 7 asks the 6560 to
|
|
267
|
+
# interlace, else the profile's.
|
|
268
|
+
def next_field
|
|
269
|
+
if @interlaced_lines.positive? && @registers[0] >= 0x80
|
|
270
|
+
long = @long_field
|
|
271
|
+
@frame_lines = long ? @interlaced_lines : @interlaced_lines - 1
|
|
272
|
+
@frame_wrap = long ? @count_column : @wrap_column
|
|
273
|
+
@long_field = !long
|
|
274
|
+
else
|
|
275
|
+
@frame_lines = @lines_per_frame
|
|
276
|
+
@frame_wrap = @wrap_column
|
|
277
|
+
@long_field = true
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
218
281
|
def place_fetches
|
|
219
282
|
line = @rasterline
|
|
220
283
|
@slot = line * Painter::SLOTS
|
data/lib/badline/data_path.rb
CHANGED
|
@@ -24,10 +24,10 @@ module Badline
|
|
|
24
24
|
File.join(xdg, "badline")
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# The quicksaves, autosaves or
|
|
28
|
-
# along with data_path if it doesn't exist yet.
|
|
27
|
+
# The quicksaves, autosaves, saves or tapes subfolder of data_path,
|
|
28
|
+
# created along with data_path if it doesn't exist yet.
|
|
29
29
|
def data_folder(name)
|
|
30
|
-
raise ArgumentError, "unknown data folder: #{name}" unless %w[quicksaves autosaves saves].include?(name)
|
|
30
|
+
raise ArgumentError, "unknown data folder: #{name}" unless %w[quicksaves autosaves saves tapes].include?(name)
|
|
31
31
|
|
|
32
32
|
path = File.join(data_path, name)
|
|
33
33
|
FileUtils.mkdir_p(path)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
# The true drives the PET's and the CBM-II's IEEE-488 bus takes, by the
|
|
6
|
+
# names --drive-type gives them, xpet's drive types, and the disk images
|
|
7
|
+
# each reads. Options reads it to check --drive-type, and
|
|
8
|
+
# Media::TrueDrive to pick the drive an image goes in, so it loads
|
|
9
|
+
# nothing else. Machine::IEEEDrives builds each.
|
|
10
|
+
module IEEEModels
|
|
11
|
+
# The images each model reads, by extension.
|
|
12
|
+
MEDIA = {
|
|
13
|
+
"2031" => %w[.d64 .g64], "8050" => %w[.d80], "8250" => %w[.d82], "1001" => %w[.d82], "4040" => %w[.d64 .d67],
|
|
14
|
+
"3040" => %w[.d64 .d67], "2040" => %w[.d67], "9060" => %w[.d90], "9090" => %w[.d90]
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
# The model --true-drive plugs in without --drive-type.
|
|
18
|
+
DEFAULT = "2031"
|
|
19
|
+
|
|
20
|
+
# The model an image goes in when neither the drive on device 8 nor
|
|
21
|
+
# the machine's own model reads it. A .d90 goes by its size
|
|
22
|
+
# (hard_disk_model).
|
|
23
|
+
FOR_IMAGES = { ".d64" => "2031", ".g64" => "2031", ".d80" => "8050", ".d82" => "8250", ".d67" => "2040" }.freeze
|
|
24
|
+
|
|
25
|
+
NAMES = MEDIA.keys.freeze
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Whether the image at path is a hard disk's, which only a true drive
|
|
30
|
+
# reads.
|
|
31
|
+
def hard_disk?(path) = File.extname(path).casecmp?(".d90")
|
|
32
|
+
|
|
33
|
+
# The hard disk a .d90 image belongs to, by its size: a D9090 for a
|
|
34
|
+
# D9090's, and otherwise a D9060.
|
|
35
|
+
def hard_disk_model(path) = Storage::D90Image.heads_for(File.size?(path).to_i) == 6 ? "9090" : "9060"
|
|
36
|
+
|
|
37
|
+
# Whether the model reads the image at path.
|
|
38
|
+
def reads?(model, path) = MEDIA.fetch(model, []).include?(File.extname(path).downcase)
|
|
39
|
+
|
|
40
|
+
# The model the image at path goes in: the drive's on device 8,
|
|
41
|
+
# `present`, if it reads it, or else the machine's own, `chosen`, or
|
|
42
|
+
# else the image's own, or nil for an image none reads. A .d90 goes
|
|
43
|
+
# in the machine's own model if that is a hard disk, and otherwise in
|
|
44
|
+
# the one its size names.
|
|
45
|
+
def taking(path, present, chosen)
|
|
46
|
+
return reads?(chosen, path) ? chosen : hard_disk_model(path) if hard_disk?(path)
|
|
47
|
+
return present if present && reads?(present, path)
|
|
48
|
+
return chosen if reads?(chosen, path)
|
|
49
|
+
|
|
50
|
+
FOR_IMAGES[File.extname(path).downcase]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model8050"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Drives
|
|
7
|
+
# The SFD-1001: the 8250LP's board redrawn as one PCB with one
|
|
8
|
+
# double-sided drive, a Matsushita JU-570-2 (schematic 251406). Both
|
|
9
|
+
# processors see an 8250. The interface processor runs DOS 2.7, which
|
|
10
|
+
# doesn't know there is one drive, and the controller runs 251257-02A
|
|
11
|
+
# from the EPROM on its 6530 adaptor (251252-01): the 8250LP's program
|
|
12
|
+
# with an interrupt every 3 ms for the JU-570's step rate, a longer head
|
|
13
|
+
# settle, and every job for drive 1 ended with an error. Drive 1's
|
|
14
|
+
# stepper, motor and LED aren't wired, so the unit takes no disk there,
|
|
15
|
+
# and its LED never lights.
|
|
16
|
+
class Model1001 < Model8250
|
|
17
|
+
# The LED bit of drive 1, which the board leaves unwired.
|
|
18
|
+
DRIVE1_LED = 0x01
|
|
19
|
+
|
|
20
|
+
def model_name = "1001"
|
|
21
|
+
|
|
22
|
+
# +fdc_rom+ defaults to 251257-02A, the rest as Model8050's.
|
|
23
|
+
def initialize(dos_rom: nil, fdc_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
24
|
+
super(dos_rom:, fdc_rom: fdc_rom || ROM.read("1001/fdc-251257-02a.rom"), host_clock_hz:, device:)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def leds = super & ~DRIVE1_LED
|
|
28
|
+
|
|
29
|
+
# Puts a Disk in drive 0, the only one. Drive 1 raises ArgumentError.
|
|
30
|
+
def insert(disk, drive: 0)
|
|
31
|
+
raise ArgumentError, "the 1001 has one drive, not drive #{drive}" unless drive.zero?
|
|
32
|
+
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -34,6 +34,12 @@ module Badline
|
|
|
34
34
|
|
|
35
35
|
def model_name = "2031"
|
|
36
36
|
|
|
37
|
+
def boot_cycles = BOOT_CYCLES
|
|
38
|
+
|
|
39
|
+
# Whether the drive is where VICE saves it: its CPU between
|
|
40
|
+
# instructions.
|
|
41
|
+
def settled_for_vice? = cpu.boundary?
|
|
42
|
+
|
|
37
43
|
# +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
|
|
38
44
|
# path. +device+ is the number the jumpers set, 8 to 11.
|
|
39
45
|
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# A .d64 image as the 4040's controller finds it on the disk, each
|
|
7
|
+
# track a ring of byte cells as on the 8050 (Model8050::Disk), laid
|
|
8
|
+
# out as DOS 2.1's format writes it, sector after sector:
|
|
9
|
+
#
|
|
10
|
+
# 4 sync, $08, checksum, sector, track, ID 2, ID 1,
|
|
11
|
+
# 9 x $00, 4 sync, $07, 256 data bytes, checksum, then $00
|
|
12
|
+
#
|
|
13
|
+
# nine cells being the header gap the interface processor gives the
|
|
14
|
+
# controller ($049D, from DOS 2.1's SECTRK table), past which a write
|
|
15
|
+
# puts its syncs. A .d67, DOS 1's disk, is laid out as DOS 1 writes a
|
|
16
|
+
# sector: 2 sync, the header, one $08, 2 sync, $07, the data and its
|
|
17
|
+
# checksum. DOS 1's format writes headers only, so this is the track as
|
|
18
|
+
# if every sector had been written. The disk's ID is the BAM's, at
|
|
19
|
+
# 18/0 $A2. A turn passes 80,000 / (16 - zone) cells, the 16 MHz bit
|
|
20
|
+
# clock's.
|
|
21
|
+
class Disk < Model8050::Disk
|
|
22
|
+
# How many tracks a disk holds: 35.
|
|
23
|
+
def side_tracks = 35
|
|
24
|
+
|
|
25
|
+
def sides = 1
|
|
26
|
+
|
|
27
|
+
# The cells a track of the zone holds in one turn at 300 rpm: the
|
|
28
|
+
# 200,000 µs of a turn over 2.5 µs times 16 less the zone a byte.
|
|
29
|
+
def self.cells_in(zone) = 80_000 / (16 - zone)
|
|
30
|
+
|
|
31
|
+
# The zone a track is recorded in, 3 outermost to 0 innermost.
|
|
32
|
+
def self.zone_of(track) = Storage::D64Image.zone(track)
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build(number)
|
|
37
|
+
id1, id2 = disk_id
|
|
38
|
+
cells = []
|
|
39
|
+
sectors = @image.sectors_in(number)
|
|
40
|
+
dos1 = @image.is_a?(Storage::D67Image)
|
|
41
|
+
sectors.times do |sector|
|
|
42
|
+
data = @image.read_block(number, sector)
|
|
43
|
+
header = [0x08, number ^ sector ^ id1 ^ id2, sector, number, id2, id1]
|
|
44
|
+
if dos1
|
|
45
|
+
cells.push(SYNC, SYNC, *header, 0x08, SYNC, SYNC, 0x07)
|
|
46
|
+
else
|
|
47
|
+
cells.push(SYNC, SYNC, SYNC, SYNC, *header, *Array.new(9, 0x00), SYNC, SYNC, SYNC, SYNC, 0x07)
|
|
48
|
+
end
|
|
49
|
+
cells.concat(data)
|
|
50
|
+
cells << checksum(data)
|
|
51
|
+
end
|
|
52
|
+
pad(cells, sectors, self.class.cells_in(self.class.zone_of(number)))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def disk_id
|
|
56
|
+
bam = @image.read_block(18, 0)
|
|
57
|
+
[bam[0xa2], bam[0xa3]]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# The interface processor's bus (schematic 320806, sheet 1). UB3
|
|
7
|
+
# decodes A12-A14 only, so the map repeats every 32 KB:
|
|
8
|
+
#
|
|
9
|
+
# $0000-$0FFF - the two 6532s, as on the 8050 (Model8050::IPBus)
|
|
10
|
+
# $1000-$4FFF - the common RAM, 1 KB in each block, mirrored through it
|
|
11
|
+
# $5000-$7FFF - the ROM sockets UJ1, UL1 and UH1, seen again at
|
|
12
|
+
# $D000-$FFFF, where the processor finds its vectors
|
|
13
|
+
#
|
|
14
|
+
# A socket left empty, as UJ1 is under DOS 1, reads as open bus: the
|
|
15
|
+
# last byte on the data bus.
|
|
16
|
+
class IPBus < Model8050::IPBus
|
|
17
|
+
def peek(addr)
|
|
18
|
+
addr &= 0x7fff
|
|
19
|
+
return @data = rom_peek(addr | 0x8000) if addr >= 0x5000
|
|
20
|
+
|
|
21
|
+
@data = if addr < 0x1000
|
|
22
|
+
peek_riots(addr)
|
|
23
|
+
else
|
|
24
|
+
@common.peek((((addr >> 12) - 1) << 10) | (addr & 0x03ff))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def poke(addr, value)
|
|
29
|
+
@data = value
|
|
30
|
+
addr &= 0x7fff
|
|
31
|
+
return if addr >= 0x5000
|
|
32
|
+
|
|
33
|
+
if addr < 0x1000
|
|
34
|
+
poke_riots(addr, value)
|
|
35
|
+
else
|
|
36
|
+
poke_common((((addr >> 12) - 1) << 10) | (addr & 0x03ff), value)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def rom_peek(addr) = addr >= @rom.start ? @rom.peek(addr) : @data
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
class Disk
|
|
7
|
+
def image_format = @image.is_a?(Storage::D67Image) ? "d67" : "d64"
|
|
8
|
+
|
|
9
|
+
# The disk a BL-D8-DISK module holds, as Model8050::Disk's, from a
|
|
10
|
+
# .d64 or a .d67.
|
|
11
|
+
def self.from_fields(input, detached: false)
|
|
12
|
+
path = input.text("path", "")
|
|
13
|
+
read_only = input.bool("read_only", false) || detached
|
|
14
|
+
kind = input.choice("image.format", %w[d64 d67], "d64")
|
|
15
|
+
bytes = input.bytes("image", "".b).bytes
|
|
16
|
+
path = "" if detached || !File.file?(path)
|
|
17
|
+
image = if kind == "d67"
|
|
18
|
+
Storage::D67Image.new(path, read_only:, bytes:)
|
|
19
|
+
else
|
|
20
|
+
Storage::D64Image.new(path, read_only:, bytes:)
|
|
21
|
+
end
|
|
22
|
+
new(image).load_track_fields(input)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Mechanism
|
|
27
|
+
# The drives' fields in a keyed snapshot module (BL-D8-MECH), a value
|
|
28
|
+
# a drive in each list: the heads' half tracks, the stepper counts,
|
|
29
|
+
# each disk's place in its turn and its bit clock and the motors,
|
|
30
|
+
# then byte ready, the byte on VIA port A and SYNC. The disks load
|
|
31
|
+
# from their own modules.
|
|
32
|
+
module KeyedState
|
|
33
|
+
def save_fields(out)
|
|
34
|
+
out.ints("half_tracks", @half_tracks).ints("phases", @phases).ints("positions", @positions)
|
|
35
|
+
out.ints("bit_clocks", @clocks)
|
|
36
|
+
out.ints("motors", @motors.map { |on| on ? 1 : 0 }).ints("syncs", @syncs.map { |low| low ? 1 : 0 })
|
|
37
|
+
out.bool("ca1_low", @ca1_low).int("data_in", @data_in)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def load_fields(input, disks)
|
|
41
|
+
@disks = disks
|
|
42
|
+
input.ints_into("half_tracks", @half_tracks)
|
|
43
|
+
input.ints_into("phases", @phases)
|
|
44
|
+
input.ints_into("positions", @positions)
|
|
45
|
+
input.ints_into("bit_clocks", @clocks)
|
|
46
|
+
@motors = input.ints("motors", [0, 0]).map { |on| on == 1 }
|
|
47
|
+
@syncs = input.ints("syncs", [0, 0]).map { |low| low == 1 }
|
|
48
|
+
@ca1_low = input.bool("ca1_low", @ca1_low)
|
|
49
|
+
@data_in = input.byte("data_in", @data_in)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
include KeyedState
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|