badline 0.13.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 +72 -1
- data/CLAUDE.md +42 -12
- data/CONTRIBUTING.md +4 -3
- data/README.md +76 -13
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +174 -0
- data/doc/snapshot-format.md +1197 -0
- data/doc/snapshots.md +90 -40
- data/lib/badline/audio/bare_player.rb +46 -17
- data/lib/badline/audio/machine_player.rb +6 -10
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +7 -6
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +36 -4
- data/lib/badline/c128/drives.rb +6 -25
- data/lib/badline/c128/keyed_modules.rb +117 -0
- data/lib/badline/c128/keyed_state.rb +91 -0
- data/lib/badline/c128/keys.rb +9 -0
- data/lib/badline/c128/mmu.rb +9 -8
- data/lib/badline/c128/mmu_pages.rb +27 -0
- data/lib/badline/c128/modes.rb +19 -9
- data/lib/badline/c128/z80_turns.rb +1 -1
- data/lib/badline/c128.rb +8 -8
- data/lib/badline/c64/attachments.rb +0 -44
- data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
- data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
- data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
- data/lib/badline/c64/bus/debug_register.rb +32 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +3 -1
- data/lib/badline/c64/cartridge/action_replay.rb +6 -15
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
- data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
- data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
- data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
- 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 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +2 -11
- data/lib/badline/c64/cartridge/super_games.rb +3 -12
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
- data/lib/badline/c64/cartridge.rb +4 -30
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +115 -0
- data/lib/badline/c64/keyed_state.rb +68 -0
- data/lib/badline/c64/reu/dma.rb +0 -4
- data/lib/badline/c64/reu/keyed_state.rb +140 -0
- data/lib/badline/c64/reu/ram.rb +0 -15
- data/lib/badline/c64/reu.rb +1 -3
- data/lib/badline/c64/true_drives.rb +56 -5
- data/lib/badline/c64.rb +7 -3
- data/lib/badline/chips/cia/interrupt_register.rb +0 -17
- data/lib/badline/chips/cia/keyed_parts.rb +213 -0
- data/lib/badline/chips/cia/serial.rb +0 -4
- data/lib/badline/chips/cia/time_of_day.rb +0 -22
- data/lib/badline/chips/cia/timer.rb +0 -20
- data/lib/badline/chips/cia.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +18 -2
- data/lib/badline/chips/cpu/keyed_state.rb +177 -0
- data/lib/badline/chips/cpu/microcode.rb +16 -0
- data/lib/badline/chips/cpu/port.rb +20 -3
- data/lib/badline/chips/cpu.rb +5 -0
- data/lib/badline/chips/sid/decimator.rb +10 -8
- data/lib/badline/chips/sid/envelope.rb +1 -6
- data/lib/badline/chips/sid/filter.rb +0 -14
- data/lib/badline/chips/sid/keyed_parts.rb +286 -0
- data/lib/badline/chips/sid/voice.rb +0 -10
- data/lib/badline/chips/sid/waveform.rb +0 -2
- data/lib/badline/chips/sid.rb +1 -2
- data/lib/badline/chips/ted/beam.rb +118 -0
- 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/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +190 -0
- data/lib/badline/chips/tia.rb +61 -0
- data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
- data/lib/badline/chips/vdc/painter.rb +0 -12
- data/lib/badline/chips/vdc/raster.rb +0 -16
- data/lib/badline/chips/vdc/window.rb +0 -9
- data/lib/badline/chips/vdc.rb +1 -2
- data/lib/badline/chips/via/control_lines.rb +0 -15
- data/lib/badline/chips/via/interrupt_register.rb +0 -9
- data/lib/badline/chips/via/keyed_parts.rb +163 -0
- data/lib/badline/chips/via/shift_register.rb +0 -14
- data/lib/badline/chips/via/timer1.rb +0 -13
- data/lib/badline/chips/via/timer2.rb +0 -18
- data/lib/badline/chips/via.rb +1 -2
- data/lib/badline/chips/vic/border_mask.rb +0 -10
- data/lib/badline/chips/vic/collisions.rb +0 -11
- data/lib/badline/chips/vic/color_patches.rb +0 -11
- data/lib/badline/chips/vic/display_state.rb +0 -19
- data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
- data/lib/badline/chips/vic/keyed_parts.rb +228 -0
- data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
- data/lib/badline/chips/vic/keyed_state.rb +102 -0
- data/lib/badline/chips/vic/register_log.rb +0 -18
- data/lib/badline/chips/vic/registers.rb +0 -9
- data/lib/badline/chips/vic/sequencer.rb +0 -2
- data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
- data/lib/badline/chips/vic/sprite.rb +0 -2
- data/lib/badline/chips/vic/sprites.rb +0 -3
- data/lib/badline/chips/vic.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
- data/lib/badline/chips/vic_i/painter.rb +0 -4
- data/lib/badline/chips/vic_i/sound.rb +38 -46
- data/lib/badline/chips/vic_i.rb +2 -2
- data/lib/badline/chips/z80/core.rb +2 -2
- data/lib/badline/chips/z80/keyed_state.rb +73 -0
- data/lib/badline/chips.rb +10 -0
- data/lib/badline/core.rb +4 -21
- data/lib/badline/drives/datasette/keyed_state.rb +43 -0
- data/lib/badline/drives/datasette.rb +62 -12
- data/lib/badline/drives/engine/core.rb +3 -0
- data/lib/badline/drives/engine/serial_lines.rb +4 -7
- data/lib/badline/drives/model1541/bus.rb +0 -13
- data/lib/badline/drives/model1541/disk.rb +9 -8
- data/lib/badline/drives/model1541/keyed_state.rb +251 -0
- data/lib/badline/drives/model1541/mechanism.rb +0 -2
- data/lib/badline/drives/model1541.rb +1 -2
- data/lib/badline/drives/model1571/bus.rb +0 -13
- data/lib/badline/drives/model1571/keyed_state.rb +108 -0
- data/lib/badline/drives/model1571/wd1770.rb +0 -14
- data/lib/badline/drives/model1571.rb +1 -2
- data/lib/badline/drives/model1581/bus.rb +0 -13
- data/lib/badline/drives/model1581/disk.rb +0 -4
- data/lib/badline/drives/model1581/keyed_state.rb +280 -0
- data/lib/badline/drives/model1581/mechanism.rb +0 -21
- data/lib/badline/drives/model1581/slot.rb +0 -18
- data/lib/badline/drives/model1581/wd1772.rb +0 -2
- data/lib/badline/drives/model1581.rb +2 -2
- data/lib/badline/drives.rb +8 -0
- data/lib/badline/frontend/app.rb +3 -3
- data/lib/badline/frontend/boot.rb +4 -2
- data/lib/badline/frontend/controls.rb +25 -5
- data/lib/badline/frontend/drive_led.rb +44 -21
- 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 +4 -3
- 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/snapshots.rb +12 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +43 -12
- data/lib/badline/input/matrix_typist.rb +98 -0
- data/lib/badline/input.rb +1 -0
- data/lib/badline/kernal_trap/chrout.rb +45 -0
- data/lib/badline/kernal_trap/dos/channel.rb +0 -27
- data/lib/badline/kernal_trap/dos/channels.rb +0 -25
- data/lib/badline/kernal_trap/dos/memory.rb +0 -9
- data/lib/badline/kernal_trap/dos/status.rb +0 -8
- data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
- data/lib/badline/kernal_trap/dos.rb +0 -19
- data/lib/badline/kernal_trap/file_routine.rb +4 -4
- data/lib/badline/kernal_trap/keyed_state.rb +150 -0
- data/lib/badline/kernal_trap/layout.rb +75 -11
- data/lib/badline/kernal_trap/load.rb +14 -16
- data/lib/badline/kernal_trap/routine.rb +12 -0
- data/lib/badline/kernal_trap/save.rb +8 -18
- data/lib/badline/kernal_trap/serial.rb +1 -17
- data/lib/badline/kernal_trap.rb +2 -0
- data/lib/badline/machine/keyboard_buffer.rb +49 -0
- data/lib/badline/machine/settling.rb +29 -0
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- 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/memory.rb +3 -6
- data/lib/badline/options/family.rb +6 -2
- data/lib/badline/options/help.rb +66 -16
- data/lib/badline/options/table.rb +17 -2
- data/lib/badline/options/validation.rb +26 -2
- data/lib/badline/options.rb +42 -9
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +375 -0
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/cpu.rb +10 -0
- data/lib/badline/plus4/cpu_port.rb +93 -0
- data/lib/badline/plus4/keyboard_port.rb +76 -0
- 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/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +240 -0
- data/lib/badline/roms/README +38 -0
- data/lib/badline/roms/plus4/basic.rom +0 -0
- data/lib/badline/roms/plus4/function-high.rom +0 -0
- data/lib/badline/roms/plus4/function-low.rom +0 -0
- data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/c128_setup.rb +9 -20
- data/lib/badline/snapshot/c64_setup.rb +28 -24
- data/lib/badline/snapshot/container.rb +6 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
- data/lib/badline/snapshot/field_doc.rb +148 -0
- data/lib/badline/snapshot/image.rb +143 -28
- data/lib/badline/snapshot/keyed/cursor.rb +40 -0
- data/lib/badline/snapshot/keyed/modules.rb +66 -0
- data/lib/badline/snapshot/keyed/reader.rb +188 -0
- data/lib/badline/snapshot/keyed/recorder.rb +88 -0
- data/lib/badline/snapshot/keyed/report.rb +26 -0
- data/lib/badline/snapshot/keyed/writer.rb +55 -0
- data/lib/badline/snapshot/keyed.rb +64 -0
- data/lib/badline/snapshot/machine_state.rb +17 -64
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot/storage_setup.rb +29 -34
- data/lib/badline/snapshot/vic20_setup.rb +6 -15
- data/lib/badline/snapshot/vice/reu1764.rb +2 -2
- data/lib/badline/snapshot/vice.rb +7 -31
- data/lib/badline/snapshot.rb +48 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +9 -4
- data/lib/badline/storage/tap.rb +57 -20
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +3 -0
- data/lib/badline/vic20/bus/keyed_state.rb +52 -0
- data/lib/badline/vic20/bus.rb +1 -2
- data/lib/badline/vic20/keyed_modules.rb +73 -0
- data/lib/badline/vic20/keyed_state.rb +45 -0
- data/lib/badline/vic20.rb +5 -5
- data/lib/badline.rb +1 -0
- metadata +75 -42
- data/lib/badline/c128/bus/saved_state.rb +0 -53
- data/lib/badline/c128/saved_state.rb +0 -117
- data/lib/badline/c64/bus/saved_state.rb +0 -68
- data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
- data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
- data/lib/badline/c64/cartridge/saved_state.rb +0 -63
- data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
- data/lib/badline/c64/reu/saved_state.rb +0 -55
- data/lib/badline/c64/saved_state.rb +0 -112
- data/lib/badline/chips/cia/saved_state.rb +0 -49
- data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
- data/lib/badline/chips/cpu/saved_state.rb +0 -117
- data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
- data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
- data/lib/badline/chips/sid/saved_state.rb +0 -52
- data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
- data/lib/badline/chips/vdc/saved_state.rb +0 -36
- data/lib/badline/chips/via/saved_state.rb +0 -44
- data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
- data/lib/badline/chips/vic/saved_state.rb +0 -82
- data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
- data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
- data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
- data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
- data/lib/badline/chips/vic_i/saved_state.rb +0 -55
- data/lib/badline/chips/z80/saved_state.rb +0 -71
- data/lib/badline/chrout_trap.rb +0 -43
- data/lib/badline/debug_register.rb +0 -28
- data/lib/badline/drives/datasette/saved_state.rb +0 -37
- data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
- data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
- data/lib/badline/drives/model1541/saved_state.rb +0 -44
- data/lib/badline/drives/model1571/saved_state.rb +0 -47
- data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
- data/lib/badline/drives/model1581/saved_state.rb +0 -42
- data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
- data/lib/badline/keyboard_buffer.rb +0 -33
- data/lib/badline/snapshot/setup.rb +0 -31
- data/lib/badline/snapshot/state.rb +0 -179
- data/lib/badline/vic20/bus/saved_state.rb +0 -35
- data/lib/badline/vic20/saved_state.rb +0 -110
|
@@ -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
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
# The picture: a palette index a pixel (Palette), 384 pixels a line
|
|
7
|
+
# and every line of the frame. A line's pixels start at horizontal
|
|
8
|
+
# count 419, so the 40-column window, counts 451-315, runs from pixel
|
|
9
|
+
# 32 to 351, as in xplus4's screenshots.
|
|
10
|
+
#
|
|
11
|
+
# The picture's lines are the scanlines of the frame as they go out,
|
|
12
|
+
# which follow the line count until a write to $FF1C-$FF1D moves it:
|
|
13
|
+
# the frame's scanlines start over where the line count wraps to 0 at
|
|
14
|
+
# the end of the frame, and step with each line otherwise, as in
|
|
15
|
+
# xplus4's screenshots of TED/write-scanline-coarse.
|
|
16
|
+
#
|
|
17
|
+
# A line is painted at column 44, once its last pixel is out, from
|
|
18
|
+
# what the line fetched (Fetches) in the colours of its mode (Modes).
|
|
19
|
+
# A write to a register the picture reads paints the line up to the
|
|
20
|
+
# pixel the write lands on first, so the old value holds to there and
|
|
21
|
+
# the new one from there.
|
|
22
|
+
#
|
|
23
|
+
# Outside the window the border colour ($FF19) shows: above and below
|
|
24
|
+
# it, from the line count reaching line 4 to its reaching line 204 with
|
|
25
|
+
# $FF06 bit 3 set (25 rows), or 8 and 200 (24 rows), so a line count
|
|
26
|
+
# moved past line 4 leaves the frame's window shut;
|
|
27
|
+
# beside it, the 38-column window ($FF07 bit 3 clear) is pixels 39 to
|
|
28
|
+
# 342, as in xplus4's screenshots, where the datasheet's counts would
|
|
29
|
+
# make it 40 to 343. $FF06 bit 4 clear blanks the whole picture to the
|
|
30
|
+
# border colour. A line of the window with no row under way shows the
|
|
31
|
+
# background colour, $FF15.
|
|
32
|
+
#
|
|
33
|
+
# $FF07 bits 0-2 move the characters right by as many pixels, the
|
|
34
|
+
# window's pixels before the first character showing the background.
|
|
35
|
+
module Picture
|
|
36
|
+
WIDTH = 384
|
|
37
|
+
|
|
38
|
+
# The horizontal count of a line's first pixel.
|
|
39
|
+
FIRST_COUNT = 419
|
|
40
|
+
|
|
41
|
+
# The column whose cycle paints the line, and the first whose writes
|
|
42
|
+
# land on the next line.
|
|
43
|
+
PAINT_COLUMN = 44
|
|
44
|
+
NEXT_LINE_COLUMN = 52
|
|
45
|
+
|
|
46
|
+
WINDOW_LEFT = 32
|
|
47
|
+
WINDOW_RIGHT = 352
|
|
48
|
+
NARROW_LEFT = 39
|
|
49
|
+
NARROW_RIGHT = 343
|
|
50
|
+
|
|
51
|
+
# How many dots after its cycle's first a register write takes hold:
|
|
52
|
+
# where xplus4 shows a raster interrupt's colour change, a cell
|
|
53
|
+
# boundary with no horizontal scroll.
|
|
54
|
+
WRITE_DELAY = 19
|
|
55
|
+
|
|
56
|
+
# The picture, a palette index a pixel.
|
|
57
|
+
attr_reader :display
|
|
58
|
+
|
|
59
|
+
# Whether each line has been painted since the front end last
|
|
60
|
+
# cleared them.
|
|
61
|
+
attr_reader :dirty_lines
|
|
62
|
+
|
|
63
|
+
# Whether the TED paints its picture. It fetches either way.
|
|
64
|
+
attr_reader :render
|
|
65
|
+
|
|
66
|
+
def width = WIDTH
|
|
67
|
+
|
|
68
|
+
def height = lines_per_frame
|
|
69
|
+
|
|
70
|
+
def clear_dirty_lines!
|
|
71
|
+
@dirty_lines.fill(false)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def render=(value)
|
|
75
|
+
@render = value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def reset_picture
|
|
81
|
+
@display = Array.new(WIDTH * 312, 0)
|
|
82
|
+
@dirty_lines = Array.new(312, true)
|
|
83
|
+
@render = true
|
|
84
|
+
@painted = 0
|
|
85
|
+
@scanline = 0
|
|
86
|
+
@window_open = false
|
|
87
|
+
reset_modes
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The line count steps to +line+: the next scanline, and the
|
|
91
|
+
# window opens or shuts on its first and last lines.
|
|
92
|
+
def step_scanline(line)
|
|
93
|
+
@scanline = (@scanline + 1) % 312
|
|
94
|
+
rows25 = @registers[0x06].anybits?(0x08)
|
|
95
|
+
if line == (rows25 ? 4 : 8)
|
|
96
|
+
@window_open = true
|
|
97
|
+
elsif line == (rows25 ? 204 : 200)
|
|
98
|
+
@window_open = false
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The line count wraps to 0: the frame's first scanline.
|
|
103
|
+
def end_picture_frame
|
|
104
|
+
@scanline = -1
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The picture's part of a cycle in +column+.
|
|
108
|
+
def picture_cycle(column)
|
|
109
|
+
return unless column == PAINT_COLUMN
|
|
110
|
+
|
|
111
|
+
paint(WIDTH) if @render
|
|
112
|
+
@painted = 0
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Paints the line up to the pixel a register write lands on.
|
|
116
|
+
def paint_to_write
|
|
117
|
+
return unless @render
|
|
118
|
+
|
|
119
|
+
column = @column
|
|
120
|
+
return if column >= PAINT_COLUMN && column < NEXT_LINE_COLUMN
|
|
121
|
+
|
|
122
|
+
x = ((column * 8) + WRITE_DELAY - FIRST_COUNT) % 456
|
|
123
|
+
x = WIDTH if x > WIDTH
|
|
124
|
+
paint(x)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Paints the line's pixels from where it was left up to +stop+.
|
|
128
|
+
def paint(stop)
|
|
129
|
+
start = @painted
|
|
130
|
+
return if stop <= start
|
|
131
|
+
|
|
132
|
+
@painted = stop
|
|
133
|
+
scanline = @scanline
|
|
134
|
+
@dirty_lines[scanline] = true
|
|
135
|
+
base = scanline * WIDTH
|
|
136
|
+
border = @registers[0x19] & 0x7f
|
|
137
|
+
return fill_span(base, start, stop, border) unless @window_open && @registers[0x06].anybits?(0x10)
|
|
138
|
+
|
|
139
|
+
narrow = !@registers[0x07].anybits?(0x08)
|
|
140
|
+
left = narrow ? NARROW_LEFT : WINDOW_LEFT
|
|
141
|
+
right = narrow ? NARROW_RIGHT : WINDOW_RIGHT
|
|
142
|
+
fill_span(base, start, stop.clamp(..left), border)
|
|
143
|
+
paint_window(base, start.clamp(left..), stop.clamp(..right))
|
|
144
|
+
fill_span(base, start.clamp(right..), stop, border)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def fill_span(base, start, stop, color)
|
|
148
|
+
@display.fill(color, base + start, stop - start) if stop > start
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def paint_window(base, start, stop)
|
|
152
|
+
return if stop <= start
|
|
153
|
+
|
|
154
|
+
background = @registers[0x15] & 0x7f
|
|
155
|
+
return fill_span(base, start, stop, background) unless @pattern_fetches
|
|
156
|
+
|
|
157
|
+
first = WINDOW_LEFT + (@registers[0x07] & 0x07)
|
|
158
|
+
if start < first
|
|
159
|
+
fill_span(base, start, stop.clamp(..first), background)
|
|
160
|
+
start = first
|
|
161
|
+
end
|
|
162
|
+
paint_characters(base, start, stop, first)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Paints the characters' pixels +start+ to +stop+, the first
|
|
166
|
+
# character's first pixel at +first+.
|
|
167
|
+
def paint_characters(base, start, stop, first)
|
|
168
|
+
mode = self.mode
|
|
169
|
+
x = start
|
|
170
|
+
while x < stop
|
|
171
|
+
index = (x - first) >> 3
|
|
172
|
+
origin = first + (index << 3)
|
|
173
|
+
to = (origin + 8).clamp(..stop)
|
|
174
|
+
character_colors(index, mode)
|
|
175
|
+
paint_pixels(base, x, to, origin)
|
|
176
|
+
x = to
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Chips
|
|
5
|
+
class TED
|
|
6
|
+
class Sound
|
|
7
|
+
# The sound's level averaged over each sample of the audio rate,
|
|
8
|
+
# which integrates the SND pin as the board's RC network does, then
|
|
9
|
+
# through the network's coupling capacitor, a one-pole high-pass at
|
|
10
|
+
# 2.7 Hz run at the audio rate.
|
|
11
|
+
#
|
|
12
|
+
# The corner comes from the Plus/4 schematic (310164, sheet 2): SND
|
|
13
|
+
# reaches the video connector through R10 (1K) and C18 (10 uF) into
|
|
14
|
+
# R11 (12K) and R12 (10K) beside Q3's emitter follower. The
|
|
15
|
+
# network's low-pass, C19 (2,700 pF) at about 70 kHz, is far above
|
|
16
|
+
# any audio rate.
|
|
17
|
+
class Output
|
|
18
|
+
HIGH_PASS_HZ = 2.7
|
|
19
|
+
|
|
20
|
+
# Scales the loudest level, both voices high at volume 8, to the
|
|
21
|
+
# top of a signed 16-bit sample.
|
|
22
|
+
SCALE = 32_767.0 / 19_976
|
|
23
|
+
|
|
24
|
+
attr_reader :samples
|
|
25
|
+
|
|
26
|
+
# `level` is the sound's level as the recording starts, which the
|
|
27
|
+
# stage takes as settled.
|
|
28
|
+
def initialize(clock_hz:, rate:, level:)
|
|
29
|
+
@clock_hz = clock_hz
|
|
30
|
+
@rate = rate
|
|
31
|
+
@remainder = 0
|
|
32
|
+
@window = next_window
|
|
33
|
+
@left = @window
|
|
34
|
+
@sum = 0
|
|
35
|
+
@drift = level.to_f
|
|
36
|
+
@high_pass = 1.0 - Math.exp(-2.0 * Math::PI * HIGH_PASS_HZ / rate)
|
|
37
|
+
@samples = []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Adds `cycles` cycles at `level`, closing every sample they finish.
|
|
41
|
+
def hold_level(level, cycles)
|
|
42
|
+
left = @left
|
|
43
|
+
while cycles >= left
|
|
44
|
+
@sum += level * left
|
|
45
|
+
cycles -= left
|
|
46
|
+
close
|
|
47
|
+
left = @left
|
|
48
|
+
end
|
|
49
|
+
@sum += level * cycles
|
|
50
|
+
@left = left - cycles
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The samples closed since the last drain.
|
|
54
|
+
def drain
|
|
55
|
+
samples = @samples
|
|
56
|
+
@samples = []
|
|
57
|
+
samples
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def close
|
|
63
|
+
average = @sum.fdiv(@window)
|
|
64
|
+
@drift += @high_pass * (average - @drift)
|
|
65
|
+
sample = ((average - @drift) * SCALE).round
|
|
66
|
+
sample = 32_767 if sample > 32_767
|
|
67
|
+
sample = -32_768 if sample < -32_768
|
|
68
|
+
@samples << sample
|
|
69
|
+
@sum = 0
|
|
70
|
+
@window = next_window
|
|
71
|
+
@left = @window
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Each window is a whole number of cycles, and they add up to the
|
|
75
|
+
# clock's cycles over a second.
|
|
76
|
+
def next_window
|
|
77
|
+
@remainder += @clock_hz
|
|
78
|
+
cycles = @remainder / @rate
|
|
79
|
+
@remainder -= cycles * @rate
|
|
80
|
+
cycles
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|