badline 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +15 -5
- data/README.md +68 -8
- data/doc/pinned-behaviour.md +145 -0
- data/doc/snapshot-format.md +188 -38
- data/doc/snapshots.md +18 -1
- data/lib/badline/audio/bare_player.rb +4 -8
- data/lib/badline/audio/machine_player.rb +2 -12
- data/lib/badline/c128/banks.rb +1 -3
- data/lib/badline/c128/bus.rb +19 -1
- data/lib/badline/c128/mmu_pages.rb +7 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus.rb +1 -0
- data/lib/badline/c64/cartridge/action_replay.rb +6 -2
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -0
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/keyed_state.rb +155 -34
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -0
- data/lib/badline/c64/cartridge/rgcd.rb +2 -1
- data/lib/badline/c64/cartridge/super_games.rb +3 -2
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -0
- data/lib/badline/c64/cartridge.rb +3 -3
- data/lib/badline/c64/keyed_modules.rb +6 -2
- data/lib/badline/chips/ted/beam.rb +13 -14
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted.rb +53 -6
- data/lib/badline/chips/tia.rb +12 -0
- data/lib/badline/drives/datasette/keyed_state.rb +7 -2
- data/lib/badline/drives/datasette.rb +22 -5
- data/lib/badline/frontend/boot.rb +3 -1
- data/lib/badline/frontend/controls.rb +7 -3
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +13 -3
- data/lib/badline/frontend/menu/menu_pages.rb +8 -8
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +19 -6
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +26 -6
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media.rb +33 -12
- data/lib/badline/options/family.rb +1 -0
- data/lib/badline/options/help.rb +30 -6
- data/lib/badline/options/table.rb +15 -2
- data/lib/badline/options/validation.rb +26 -4
- data/lib/badline/options.rb +37 -9
- data/lib/badline/plus4/bus.rb +77 -14
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/keyboard_port.rb +35 -10
- data/lib/badline/plus4/keyed_modules.rb +99 -0
- data/lib/badline/plus4/keyed_parts.rb +143 -0
- data/lib/badline/plus4/keyed_state.rb +41 -0
- data/lib/badline/plus4.rb +92 -16
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +7 -6
- data/lib/badline/snapshot/field_doc.rb +46 -7
- data/lib/badline/snapshot/image.rb +12 -3
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot.rb +9 -4
- data/lib/badline/storage/g64_image.rb +1 -1
- data/lib/badline/storage/tap.rb +21 -12
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- metadata +15 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
# What the TED reads for the picture, a single clock cycle at a time,
|
|
7
|
+
# and the counters that say where it reads.
|
|
8
|
+
#
|
|
9
|
+
# The two DMA lines of a character row (Beam) fetch a byte a cycle
|
|
10
|
+
# in phi2, in the 40 cycles of the character window, columns 54-56
|
|
11
|
+
# and 0-36: the attributes on the line before the row, from the video
|
|
12
|
+
# matrix base ($FF14 bits 3-7) with A10 clear, and the character
|
|
13
|
+
# pointers on the row's first line, with A10 set (the datasheet's
|
|
14
|
+
# video matrix and attribute addresses). The attributes go to a
|
|
15
|
+
# buffer of their own, and each takes hold as its character's
|
|
16
|
+
# pointer is fetched, so the row before shows its own colours to its
|
|
17
|
+
# last line, as xplus4 shows them.
|
|
18
|
+
#
|
|
19
|
+
# On every line of a row, from the cycle after each pointer's, the
|
|
20
|
+
# TED fetches the character's pattern byte in phi1, columns 55-56
|
|
21
|
+
# and 0-37: from the character base ($FF13 bits 2-7) with the
|
|
22
|
+
# pointer and the row's line counter, or from the bitmap base ($FF12
|
|
23
|
+
# bits 3-5) with the character position and the line counter in
|
|
24
|
+
# bitmap mode. With $FF07 bit 7 clear the pointer's bit 7 asks for
|
|
25
|
+
# reverse and the character base supplies A10; extended colour mode
|
|
26
|
+
# forces A9 and A10 low. The byte stays on the bus, where a read of
|
|
27
|
+
# an address nothing answers finds it (Plus/4 openio's outrun2).
|
|
28
|
+
#
|
|
29
|
+
# The matrix fetches see ROM or RAM as the CPU does at $8000-$FFFF,
|
|
30
|
+
# and the pattern fetches ROM with $FF12 bit 2 set or RAM with it
|
|
31
|
+
# clear (Plus4::Bus#ted_fetch and #ted_pattern_fetch).
|
|
32
|
+
#
|
|
33
|
+
# The counters: the character position reload ($FF1A-$FF1B), the
|
|
34
|
+
# character position the row starts at, which steps by 40 at the end
|
|
35
|
+
# of a row's eighth line, at horizontal count 290; and the vertical
|
|
36
|
+
# sub-address ($FF1F bits 0-2), the row's line, which steps with the
|
|
37
|
+
# line count on the display window's lines once the frame's first
|
|
38
|
+
# row is under way, wrapping from 7 to 0 whatever the DMA lines do.
|
|
39
|
+
# The attribute line reads the row the reload points to after its
|
|
40
|
+
# own latch: 40 on from it on a row's eighth line, the reload itself
|
|
41
|
+
# otherwise. The pointer line is the line after an attribute line. A
|
|
42
|
+
# frame starts with both counters at 0 and no row under way, and the
|
|
43
|
+
# frame's first pointer line puts a row under way to the end of the
|
|
44
|
+
# frame. The display window's lines with a row under way and the
|
|
45
|
+
# display on fetch pattern bytes.
|
|
46
|
+
module Fetches
|
|
47
|
+
# The first and last cycles of the pattern fetches.
|
|
48
|
+
PATTERN_COLUMN = 55
|
|
49
|
+
PATTERN_END_COLUMN = 37
|
|
50
|
+
|
|
51
|
+
# The cycle that latches the next row's character position.
|
|
52
|
+
LATCH_COLUMN = 36
|
|
53
|
+
|
|
54
|
+
# The cycle that counts the blink counter, horizontal count 336.
|
|
55
|
+
BLINK_COLUMN = 42
|
|
56
|
+
|
|
57
|
+
CHARACTERS = 40
|
|
58
|
+
|
|
59
|
+
# The pattern bytes, pointers and attributes of the characters on
|
|
60
|
+
# the line being fetched.
|
|
61
|
+
attr_reader :patterns, :pointers, :attributes
|
|
62
|
+
|
|
63
|
+
# The character position reload, the row's line, and whether a row
|
|
64
|
+
# is under way.
|
|
65
|
+
attr_reader :char_position, :row_line, :row_active
|
|
66
|
+
|
|
67
|
+
# The bus the TED fetches from, the machine's.
|
|
68
|
+
def connect(bus)
|
|
69
|
+
@bus = bus
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def reset_fetches
|
|
75
|
+
@patterns = Array.new(CHARACTERS, 0)
|
|
76
|
+
@pointers = Array.new(CHARACTERS, 0)
|
|
77
|
+
@attributes = Array.new(CHARACTERS, 0)
|
|
78
|
+
@fetched_attributes = Array.new(CHARACTERS, 0)
|
|
79
|
+
@char_position = 0
|
|
80
|
+
@line_position = 0
|
|
81
|
+
@row_line = 0
|
|
82
|
+
@row_active = false
|
|
83
|
+
@dma_kind = Beam::NO_DMA
|
|
84
|
+
@pattern_fetches = false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The fetches and counters of the cycle in +column+.
|
|
88
|
+
def fetch(column)
|
|
89
|
+
if column <= Beam::DMA_END_COLUMN || column >= Beam::DMA_COLUMN
|
|
90
|
+
window_fetch(column)
|
|
91
|
+
elsif column == PATTERN_END_COLUMN
|
|
92
|
+
pattern_fetch(column) if @pattern_fetches
|
|
93
|
+
elsif column == Beam::BA_COLUMN
|
|
94
|
+
start_line_fetches
|
|
95
|
+
elsif column == BLINK_COLUMN
|
|
96
|
+
count_blink
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A cycle of the character window.
|
|
101
|
+
def window_fetch(column)
|
|
102
|
+
dma_fetch(column) unless @dma_kind == Beam::NO_DMA
|
|
103
|
+
pattern_fetch(column) if @pattern_fetches && column != Beam::DMA_COLUMN
|
|
104
|
+
latch_char_position if column == LATCH_COLUMN
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The blink counter counts a frame on one line of the vertical
|
|
108
|
+
# blank, the line xplus4 counts it on.
|
|
109
|
+
def count_blink
|
|
110
|
+
return unless @line == (lines_per_frame == 312 ? 256 : 228)
|
|
111
|
+
|
|
112
|
+
@blink = (@blink + 1) & 0x0f
|
|
113
|
+
blink_carry if @blink.zero?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Column 51: what the line fetches, decided where BA is.
|
|
117
|
+
def start_line_fetches
|
|
118
|
+
@dma_kind = dma_kind
|
|
119
|
+
@row_active = true if @dma_kind == Beam::POINTER_DMA
|
|
120
|
+
@line_position = @char_position
|
|
121
|
+
@pattern_fetches = @row_active && @line <= Beam::LAST_DISPLAY_LINE && display_enabled? && !@bus.nil?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The attributes on a line whose low three bits match $FF06's
|
|
125
|
+
# scroll, below line 203, and the pointers on the line after one
|
|
126
|
+
# that fetched attributes (the datasheet's DMA timing, step 8),
|
|
127
|
+
# whatever the scroll says by then.
|
|
128
|
+
def dma_kind
|
|
129
|
+
return Beam::NO_DMA unless display_enabled?
|
|
130
|
+
return Beam::POINTER_DMA if @dma_kind == Beam::ATTRIBUTE_DMA
|
|
131
|
+
return Beam::NO_DMA unless @line < Beam::LAST_DISPLAY_LINE
|
|
132
|
+
|
|
133
|
+
(@line - @registers[0x06]).nobits?(0x07) ? Beam::ATTRIBUTE_DMA : Beam::NO_DMA
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def display_enabled? = @registers[0x06].anybits?(0x10)
|
|
137
|
+
|
|
138
|
+
def dma_fetch(column)
|
|
139
|
+
bus = @bus
|
|
140
|
+
return if bus.nil?
|
|
141
|
+
|
|
142
|
+
index = column >= Beam::DMA_COLUMN ? column - Beam::DMA_COLUMN : column + (Beam::CYCLES_PER_LINE - Beam::DMA_COLUMN)
|
|
143
|
+
matrix = (@registers[0x14] & 0xf8) << 8
|
|
144
|
+
if @dma_kind == Beam::ATTRIBUTE_DMA
|
|
145
|
+
position = latching? ? @char_position + CHARACTERS : @char_position
|
|
146
|
+
@fetched_attributes[index] = bus.ted_fetch(matrix | ((position + index) & 0x3ff))
|
|
147
|
+
else
|
|
148
|
+
@pointers[index] = bus.ted_fetch(matrix | 0x400 | ((@char_position + index) & 0x3ff))
|
|
149
|
+
@attributes[index] = @fetched_attributes[index]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def pattern_fetch(column)
|
|
154
|
+
index = column >= PATTERN_COLUMN ? column - PATTERN_COLUMN : column + (Beam::CYCLES_PER_LINE - PATTERN_COLUMN)
|
|
155
|
+
@patterns[index] = @bus.ted_pattern_fetch(pattern_address(index), @registers[0x12].anybits?(0x04))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def pattern_address(index)
|
|
159
|
+
return bitmap_address(index) if @registers[0x06].anybits?(0x20)
|
|
160
|
+
|
|
161
|
+
pointer = @pointers[index]
|
|
162
|
+
base = @registers[0x13] & 0xf8
|
|
163
|
+
if @registers[0x06].anybits?(0x40)
|
|
164
|
+
pointer &= 0x3f
|
|
165
|
+
elsif !@registers[0x07].anybits?(0x80)
|
|
166
|
+
pointer = (pointer & 0x7f) | ((@registers[0x13] & 0x04) << 5)
|
|
167
|
+
end
|
|
168
|
+
(base << 8) | (pointer << 3) | @row_line
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def bitmap_address(index)
|
|
172
|
+
((@registers[0x12] & 0x38) << 10) | (((@line_position + index) & 0x3ff) << 3) | @row_line
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The end of a row's eighth line moves the reload on to the next
|
|
176
|
+
# row.
|
|
177
|
+
def latch_char_position
|
|
178
|
+
@char_position = (@char_position + CHARACTERS) & 0x3ff if latching?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Whether the line ends a row, and latches the next row's position.
|
|
182
|
+
def latching? = @row_active && @row_line == 7 && @line <= Beam::LAST_DISPLAY_LINE
|
|
183
|
+
|
|
184
|
+
# The line count steps to +line+: the row's next line, up to the
|
|
185
|
+
# last line of the display window.
|
|
186
|
+
def step_row_line(line)
|
|
187
|
+
@row_line = (@row_line + 1) & 0x07 if @row_active && line <= Beam::LAST_DISPLAY_LINE
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The end of the frame.
|
|
191
|
+
def end_fetch_frame
|
|
192
|
+
@char_position = 0
|
|
193
|
+
@row_line = 0
|
|
194
|
+
@row_active = false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
class Sound
|
|
7
|
+
# The sound's fields in a keyed snapshot module (Snapshot::Keyed,
|
|
8
|
+
# BL-TEDSOUND): each voice's output and when it next toggles, in
|
|
9
|
+
# cycles from the machine's clock, absent while it never will, the
|
|
10
|
+
# noise's shift register, and how far behind the clock the voices
|
|
11
|
+
# last ran. The frequencies, the volume and the reload bit come from
|
|
12
|
+
# the TED's registers on load (#restored!), and the output's level
|
|
13
|
+
# from those.
|
|
14
|
+
module KeyedState
|
|
15
|
+
def save_fields(out)
|
|
16
|
+
now = @clock.cycles
|
|
17
|
+
run_to(now)
|
|
18
|
+
VOICES.times do |voice|
|
|
19
|
+
due = @next_toggles[voice]
|
|
20
|
+
out.int("voice#{voice + 1}.phase", @phases[voice])
|
|
21
|
+
out.optional_int("voice#{voice + 1}.toggle_in", due >= NEVER ? nil : due - now)
|
|
22
|
+
end
|
|
23
|
+
out.int("noise", @noise).int("ran_ago", now - @time)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_fields(input)
|
|
27
|
+
now = @clock.cycles
|
|
28
|
+
VOICES.times do |voice|
|
|
29
|
+
@phases[voice] = input.masked("voice#{voice + 1}.phase", @phases[voice], 0x01)
|
|
30
|
+
due = input.optional_int("voice#{voice + 1}.toggle_in")
|
|
31
|
+
@next_toggles[voice] = due.nil? ? NEVER : now + due
|
|
32
|
+
end
|
|
33
|
+
@noise = input.byte("noise", @noise)
|
|
34
|
+
@time = now - input.int("ran_ago", now - @time)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The frequencies, the volume and the reload bit as `ted`'s
|
|
38
|
+
# registers $FF0E-$FF12 hold them, and the level they give.
|
|
39
|
+
def restored!(ted)
|
|
40
|
+
@frequencies[0] = ted.peek(0xff0e) | ((ted.peek(0xff12) & 0x03) << 8)
|
|
41
|
+
@frequencies[1] = ted.peek(0xff0f) | ((ted.peek(0xff10) & 0x03) << 8)
|
|
42
|
+
VOICES.times { |voice| @intervals[voice] = interval_for(@frequencies[voice]) }
|
|
43
|
+
control = ted.peek(0xff11)
|
|
44
|
+
@control = control
|
|
45
|
+
@reloading = control >= 0x80
|
|
46
|
+
@volume = [control & 0x0f, 8].min
|
|
47
|
+
@level = level_now
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
include KeyedState
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The DMA a line makes, as a snapshot names it, in Beam's order.
|
|
55
|
+
DMA_KINDS = %w[none attributes pointers].freeze
|
|
56
|
+
|
|
57
|
+
# The shape of the line going out, as the picture paints it. A
|
|
58
|
+
# snapshot whose line.layout differs repaints the line from its
|
|
59
|
+
# start.
|
|
60
|
+
LINE_LAYOUT = 1
|
|
61
|
+
|
|
62
|
+
# The TED's fields in a keyed snapshot module (Snapshot::Keyed,
|
|
63
|
+
# BL-TED): the registers as written, the interrupt flags, the keyboard
|
|
64
|
+
# latch, the blink counter, the timers, the beam and the clock it gave
|
|
65
|
+
# the CPU last, the fetches' buffers and counters, and the picture:
|
|
66
|
+
# where the scanline is, whether the window is open, how far the line
|
|
67
|
+
# going out is painted (line.*), and the picture itself. The interrupt
|
|
68
|
+
# mask and the raster compare come from $FF0A and $FF0B on load.
|
|
69
|
+
module KeyedState
|
|
70
|
+
def save_fields(out)
|
|
71
|
+
out.bytes("registers", @registers).int("interrupt_flags", @interrupt_flags).int("key_latch", @key_latch)
|
|
72
|
+
out.int("blink", @blink).bool("blink_on", @blink_on)
|
|
73
|
+
save_timer_fields(out)
|
|
74
|
+
out.int("column", @column).int("line", @line).bool("ba", @ba_low).int("cpu_slots", @cpu_slots)
|
|
75
|
+
save_fetch_fields(out)
|
|
76
|
+
out.int("scanline", @scanline).bool("window_open", @window_open)
|
|
77
|
+
out.int("line.layout", LINE_LAYOUT).int("line.painted", @painted).bytes("display", @display)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def load_fields(input)
|
|
81
|
+
input.bytes_into("registers", @registers)
|
|
82
|
+
@interrupt_flags = input.byte("interrupt_flags", @interrupt_flags)
|
|
83
|
+
@key_latch = input.byte("key_latch", @key_latch)
|
|
84
|
+
@blink = input.masked("blink", @blink, 0x0f)
|
|
85
|
+
@blink_on = input.bool("blink_on", @blink_on)
|
|
86
|
+
load_timer_fields(input)
|
|
87
|
+
@column = input.int("column", @column) % Beam::CYCLES_PER_LINE
|
|
88
|
+
@line = input.masked("line", @line, 0x1ff)
|
|
89
|
+
@ba_low = input.bool("ba", @ba_low)
|
|
90
|
+
@cpu_slots = input.int("cpu_slots", @cpu_slots).clamp(0, 2)
|
|
91
|
+
load_fetch_fields(input)
|
|
92
|
+
@scanline = input.int("scanline", @scanline)
|
|
93
|
+
@window_open = input.bool("window_open", @window_open)
|
|
94
|
+
load_line_fields(input)
|
|
95
|
+
input.bytes_into("display", @display)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The interrupt mask and the raster compare from $FF0A and $FF0B,
|
|
99
|
+
# and every line of the picture changed.
|
|
100
|
+
def restored!
|
|
101
|
+
mask = @registers[0x0a]
|
|
102
|
+
@interrupt_mask = mask & 0x5f
|
|
103
|
+
@raster_compare = ((mask & 0x01) << 8) | @registers[0x0b]
|
|
104
|
+
@dirty_lines.fill(true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def save_timer_fields(out)
|
|
110
|
+
3.times do |index|
|
|
111
|
+
out.int("timer#{index + 1}.count", @timer_counts[index])
|
|
112
|
+
out.bool("timer#{index + 1}.running", @timer_running[index])
|
|
113
|
+
end
|
|
114
|
+
out.int("timer1.reload", @timer1_reload)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def load_timer_fields(input)
|
|
118
|
+
3.times do |index|
|
|
119
|
+
@timer_counts[index] = input.word("timer#{index + 1}.count", @timer_counts[index])
|
|
120
|
+
@timer_running[index] = input.bool("timer#{index + 1}.running", @timer_running[index])
|
|
121
|
+
end
|
|
122
|
+
@timer1_reload = input.word("timer1.reload", @timer1_reload)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def save_fetch_fields(out)
|
|
126
|
+
out.bytes("fetch.patterns", @patterns).bytes("fetch.pointers", @pointers)
|
|
127
|
+
out.bytes("fetch.attributes", @attributes).bytes("fetch.next_attributes", @fetched_attributes)
|
|
128
|
+
out.int("fetch.char_position", @char_position).int("fetch.line_position", @line_position)
|
|
129
|
+
out.int("fetch.row_line", @row_line).bool("fetch.row_active", @row_active)
|
|
130
|
+
out.text("fetch.dma", DMA_KINDS.fetch(@dma_kind)).bool("fetch.patterns_on", @pattern_fetches)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def load_fetch_fields(input)
|
|
134
|
+
input.bytes_into("fetch.patterns", @patterns)
|
|
135
|
+
input.bytes_into("fetch.pointers", @pointers)
|
|
136
|
+
input.bytes_into("fetch.attributes", @attributes)
|
|
137
|
+
input.bytes_into("fetch.next_attributes", @fetched_attributes)
|
|
138
|
+
@char_position = input.masked("fetch.char_position", @char_position, 0x3ff)
|
|
139
|
+
@line_position = input.masked("fetch.line_position", @line_position, 0x3ff)
|
|
140
|
+
@row_line = input.masked("fetch.row_line", @row_line, 0x07)
|
|
141
|
+
@row_active = input.bool("fetch.row_active", @row_active)
|
|
142
|
+
@dma_kind = DMA_KINDS.index(input.choice("fetch.dma", DMA_KINDS, DMA_KINDS.fetch(@dma_kind))) || 0
|
|
143
|
+
@pattern_fetches = input.bool("fetch.patterns_on", @pattern_fetches)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def load_line_fields(input)
|
|
147
|
+
line = input.int("line.layout", 0) == LINE_LAYOUT
|
|
148
|
+
input.adjust("line in progress from another renderer, restarted") unless line || input.missing?
|
|
149
|
+
@painted = line ? input.int("line.painted", @painted).clamp(0, Picture::WIDTH) : 0
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
include KeyedState
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
# The colours a character's pixels take in each mode, by $FF06 bits 5
|
|
7
|
+
# (bitmap) and 6 (extended colour) and $FF07 bit 4 (multicolour), with
|
|
8
|
+
# a pattern byte's set bits as 1 (the datasheet's "Character modes"
|
|
9
|
+
# and "Bit map mode", Härsfalvi's "Talking to TED"). Each was checked
|
|
10
|
+
# against xplus4 screenshots of the same registers and memory.
|
|
11
|
+
#
|
|
12
|
+
# - Text: 1 is the attribute's colour and luminance, bits 0-6, and 0
|
|
13
|
+
# the background, $FF15. With $FF07 bit 7 clear a pointer's bit 7
|
|
14
|
+
# reverses the character. The cursor ($FF0C-$FF0D, a character
|
|
15
|
+
# position) reverses its character, and an attribute's bit 7 makes
|
|
16
|
+
# its character flash to the background, each in turns of 16 frames
|
|
17
|
+
# of the blink counter.
|
|
18
|
+
# - Multicolour text: a character whose attribute has bit 3 set takes
|
|
19
|
+
# its bits in pairs of double-width pixels, 00 the background, 01
|
|
20
|
+
# $FF16, 10 $FF17 and 11 the attribute's bits 0-2 and 4-6. Other
|
|
21
|
+
# characters show as in text. No reverse, cursor or flash.
|
|
22
|
+
# - Extended colour: the pointer's bits 6-7 pick the background,
|
|
23
|
+
# $FF15-$FF18. No reverse, cursor or flash.
|
|
24
|
+
# - Bitmap: 0 takes its colour from the video matrix's low nibble and
|
|
25
|
+
# its luminance from the attribute's bits 4-6, 1 from the matrix's
|
|
26
|
+
# high nibble and the attribute's bits 0-2.
|
|
27
|
+
# - Multicolour bitmap: 00 the background, 01 the bitmap's 1, 10 its
|
|
28
|
+
# 0, 11 $FF16, as xplus4 shows them. The datasheet gives 01 the
|
|
29
|
+
# luminance of bits 4-6 as well.
|
|
30
|
+
# - Extended colour with either of the others: black.
|
|
31
|
+
module Modes
|
|
32
|
+
TEXT = 0x00
|
|
33
|
+
MULTICOLOR_TEXT = 0x10
|
|
34
|
+
BITMAP = 0x20
|
|
35
|
+
MULTICOLOR_BITMAP = 0x30
|
|
36
|
+
EXTENDED_COLOR = 0x40
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def reset_modes
|
|
41
|
+
@pixel_bits = 0
|
|
42
|
+
@pixel_colors = Array.new(4, 0)
|
|
43
|
+
@multicolor_pixels = false
|
|
44
|
+
@blink_on = false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The mode, $FF06 bits 5 and 6 with $FF07 bit 4.
|
|
48
|
+
def mode = (@registers[0x06] & 0x60) | (@registers[0x07] & 0x10)
|
|
49
|
+
|
|
50
|
+
# Sets the pixel bits and colours of character +index+ in +mode+.
|
|
51
|
+
def character_colors(index, mode)
|
|
52
|
+
pattern = @patterns[index]
|
|
53
|
+
pointer = @pointers[index]
|
|
54
|
+
attribute = @attributes[index]
|
|
55
|
+
case mode
|
|
56
|
+
when TEXT then text_colors(index, pattern, pointer, attribute)
|
|
57
|
+
when MULTICOLOR_TEXT then multicolor_text_colors(pattern, attribute)
|
|
58
|
+
when BITMAP then bitmap_colors(pattern, pointer, attribute)
|
|
59
|
+
when MULTICOLOR_BITMAP then multicolor_bitmap_colors(pattern, pointer, attribute)
|
|
60
|
+
when EXTENDED_COLOR then hires(pattern, @registers[0x15 + (pointer >> 6)] & 0x7f, attribute & 0x7f)
|
|
61
|
+
else hires(0, 0, 0)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Paints pixels +from+ to +to+ of the line at +base+, of a character
|
|
66
|
+
# whose first pixel is +origin+, in the colours last set.
|
|
67
|
+
def paint_pixels(base, from, to, origin)
|
|
68
|
+
display = @display
|
|
69
|
+
bits = @pixel_bits
|
|
70
|
+
colors = @pixel_colors
|
|
71
|
+
multicolor = @multicolor_pixels
|
|
72
|
+
x = from
|
|
73
|
+
while x < to
|
|
74
|
+
offset = x - origin
|
|
75
|
+
color = multicolor ? colors[(bits >> (6 - (offset & 0x06))) & 0x03] : colors[(bits >> (7 - offset)) & 0x01]
|
|
76
|
+
display[base + x] = color
|
|
77
|
+
x += 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hires(pattern, background, foreground)
|
|
82
|
+
@pixel_bits = pattern
|
|
83
|
+
@pixel_colors[0] = background
|
|
84
|
+
@pixel_colors[1] = foreground
|
|
85
|
+
@multicolor_pixels = false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def multicolor(pattern, color1, color2, color3)
|
|
89
|
+
@pixel_bits = pattern
|
|
90
|
+
colors = @pixel_colors
|
|
91
|
+
colors[0] = @registers[0x15] & 0x7f
|
|
92
|
+
colors[1] = color1
|
|
93
|
+
colors[2] = color2
|
|
94
|
+
colors[3] = color3
|
|
95
|
+
@multicolor_pixels = true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def text_colors(index, pattern, pointer, attribute)
|
|
99
|
+
pattern ^= 0xff if pointer >= 0x80 && !@registers[0x07].anybits?(0x80)
|
|
100
|
+
if @blink_on
|
|
101
|
+
pattern ^= 0xff if ((@line_position + index) & 0x3ff) == cursor_position
|
|
102
|
+
elsif attribute >= 0x80
|
|
103
|
+
pattern = 0
|
|
104
|
+
end
|
|
105
|
+
hires(pattern, @registers[0x15] & 0x7f, attribute & 0x7f)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def cursor_position = ((@registers[0x0c] & 0x03) << 8) | @registers[0x0d]
|
|
109
|
+
|
|
110
|
+
def multicolor_text_colors(pattern, attribute)
|
|
111
|
+
if attribute.anybits?(0x08)
|
|
112
|
+
multicolor(pattern, @registers[0x16] & 0x7f, @registers[0x17] & 0x7f, attribute & 0x77)
|
|
113
|
+
else
|
|
114
|
+
hires(pattern, @registers[0x15] & 0x7f, attribute & 0x7f)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def bitmap_colors(pattern, pointer, attribute)
|
|
119
|
+
hires(pattern, (pointer & 0x0f) | (attribute & 0x70), (pointer >> 4) | ((attribute & 0x07) << 4))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def multicolor_bitmap_colors(pattern, pointer, attribute)
|
|
123
|
+
multicolor(pattern, (pointer >> 4) | ((attribute & 0x07) << 4), (pointer & 0x0f) | (attribute & 0x70),
|
|
124
|
+
@registers[0x16] & 0x7f)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The blink counter's carry turns the cursor and flash over.
|
|
128
|
+
def blink_carry
|
|
129
|
+
@blink_on = !@blink_on
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
# The TED's 128 colours.
|
|
7
|
+
module Palette
|
|
8
|
+
# The colours by index, (luminance << 4) | colour, as RGB, from the
|
|
9
|
+
# Plus/4 hardware manual's TED chapter. Luma comes from each
|
|
10
|
+
# luminance level's voltage (4.4.3): level 0, 2.0 V, is black, the
|
|
11
|
+
# eight luminances are levels 1-8, and 4.8 V is white. Chroma takes
|
|
12
|
+
# the PAL column's hue phase (4.4.4), with U along 0 degrees and V
|
|
13
|
+
# along 90, at an amplitude of half CHROMA OUT's 1 Vp-p over the
|
|
14
|
+
# 2.8 V from black to white (4.4.2). Colour 0 is black at every
|
|
15
|
+
# luminance and colour 1 has no chroma. BT.601 turns them into RGB,
|
|
16
|
+
# B-Y = U / 0.492 and R-Y = V / 0.877, clamped. NTSC machines take
|
|
17
|
+
# the same palette. The datasheet's NTSC column is the PAL one less
|
|
18
|
+
# 33 degrees, but for orange, which it gives 129 on PAL and 90 on
|
|
19
|
+
# NTSC.
|
|
20
|
+
PALETTE = [
|
|
21
|
+
0x000000, 0x242424, 0x570f10, 0x003a39, 0x4e045c, 0x004400, 0x19197f, 0x303000,
|
|
22
|
+
0x4d1b00, 0x402600, 0x173d00, 0x580830, 0x00401c, 0x05266e, 0x390a7a, 0x084200,
|
|
23
|
+
0x000000, 0x323232, 0x651c1d, 0x004847, 0x5c126a, 0x055205, 0x26278c, 0x3e3e00,
|
|
24
|
+
0x5a2900, 0x4e3300, 0x254a00, 0x66163d, 0x004e2a, 0x13347c, 0x461787, 0x165000,
|
|
25
|
+
0x000000, 0x404040, 0x722a2b, 0x0d5555, 0x692077, 0x126013, 0x34349a, 0x4b4b00,
|
|
26
|
+
0x683706, 0x5b4100, 0x325800, 0x73234b, 0x0c5c38, 0x21418a, 0x542595, 0x235d00,
|
|
27
|
+
0x000000, 0x525252, 0x853c3d, 0x1f6867, 0x7b328a, 0x257225, 0x4646ac, 0x5e5e00,
|
|
28
|
+
0x7a4918, 0x6d5303, 0x456a00, 0x85365d, 0x1e6e4a, 0x33549c, 0x6637a7, 0x366f04,
|
|
29
|
+
0x000000, 0x767676, 0xa96162, 0x448c8b, 0xa056ae, 0x49964a, 0x6b6bd1, 0x82821c,
|
|
30
|
+
0x9f6d3c, 0x927828, 0x698f1d, 0xaa5a82, 0x43926e, 0x5778c0, 0x8b5ccc, 0x5a9429,
|
|
31
|
+
0x000000, 0x929292, 0xc47c7d, 0x5fa7a7, 0xbb72c9, 0x64b265, 0x8686ec, 0x9d9d38,
|
|
32
|
+
0xba8857, 0xad9343, 0x84aa38, 0xc5759d, 0x5eae8a, 0x7293dc, 0xa677e7, 0x75af44,
|
|
33
|
+
0x000000, 0xbfbfbf, 0xf2aaaa, 0x8dd5d4, 0xe99ff7, 0x92df92, 0xb4b4ff, 0xcbcb65,
|
|
34
|
+
0xe8b685, 0xdbc071, 0xb2d766, 0xf3a3cb, 0x8cdbb7, 0xa0c1ff, 0xd4a4ff, 0xa3dd72,
|
|
35
|
+
0x000000, 0xffffff, 0xffe9ea, 0xccffff, 0xffdfff, 0xd2ffd2, 0xf3f3ff, 0xffffa5,
|
|
36
|
+
0xfff6c5, 0xffffb1, 0xf2ffa6, 0xffe3ff, 0xcbfff7, 0xe0ffff, 0xffe4ff, 0xe3ffb1
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
def palette = PALETTE
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|