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.
Files changed (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/CLAUDE.md +42 -12
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +76 -13
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +174 -0
  8. data/doc/snapshot-format.md +1197 -0
  9. data/doc/snapshots.md +90 -40
  10. data/lib/badline/audio/bare_player.rb +46 -17
  11. data/lib/badline/audio/machine_player.rb +6 -10
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +7 -6
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +36 -4
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +27 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/extra_sids.rb +80 -0
  31. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  32. data/lib/badline/c64/bus.rb +3 -1
  33. data/lib/badline/c64/cartridge/action_replay.rb +6 -15
  34. data/lib/badline/c64/cartridge/comal80.rb +1 -0
  35. data/lib/badline/c64/cartridge/dinamic.rb +3 -1
  36. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  37. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  38. data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
  39. data/lib/badline/c64/cartridge/flash.rb +3 -3
  40. data/lib/badline/c64/cartridge/fun_play.rb +2 -1
  41. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  42. data/lib/badline/c64/cartridge/game_system.rb +2 -1
  43. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  44. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  45. data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
  46. data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
  47. data/lib/badline/c64/cartridge/ocean.rb +1 -0
  48. data/lib/badline/c64/cartridge/pagefox.rb +1 -10
  49. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  50. data/lib/badline/c64/cartridge/rgcd.rb +2 -11
  51. data/lib/badline/c64/cartridge/super_games.rb +3 -12
  52. data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
  53. data/lib/badline/c64/cartridge.rb +4 -30
  54. data/lib/badline/c64/kernal_traps.rb +28 -25
  55. data/lib/badline/c64/keyed_modules.rb +115 -0
  56. data/lib/badline/c64/keyed_state.rb +68 -0
  57. data/lib/badline/c64/reu/dma.rb +0 -4
  58. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  59. data/lib/badline/c64/reu/ram.rb +0 -15
  60. data/lib/badline/c64/reu.rb +1 -3
  61. data/lib/badline/c64/true_drives.rb +56 -5
  62. data/lib/badline/c64.rb +7 -3
  63. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  64. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  65. data/lib/badline/chips/cia/serial.rb +0 -4
  66. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  67. data/lib/badline/chips/cia/timer.rb +0 -20
  68. data/lib/badline/chips/cia.rb +2 -2
  69. data/lib/badline/chips/cpu/core.rb +18 -2
  70. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  71. data/lib/badline/chips/cpu/microcode.rb +16 -0
  72. data/lib/badline/chips/cpu/port.rb +20 -3
  73. data/lib/badline/chips/cpu.rb +5 -0
  74. data/lib/badline/chips/sid/decimator.rb +10 -8
  75. data/lib/badline/chips/sid/envelope.rb +1 -6
  76. data/lib/badline/chips/sid/filter.rb +0 -14
  77. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  78. data/lib/badline/chips/sid/voice.rb +0 -10
  79. data/lib/badline/chips/sid/waveform.rb +0 -2
  80. data/lib/badline/chips/sid.rb +1 -2
  81. data/lib/badline/chips/ted/beam.rb +118 -0
  82. data/lib/badline/chips/ted/fetches.rb +199 -0
  83. data/lib/badline/chips/ted/keyed_state.rb +156 -0
  84. data/lib/badline/chips/ted/modes.rb +134 -0
  85. data/lib/badline/chips/ted/palette.rb +43 -0
  86. data/lib/badline/chips/ted/picture.rb +182 -0
  87. data/lib/badline/chips/ted/sound/output.rb +86 -0
  88. data/lib/badline/chips/ted/sound.rb +284 -0
  89. data/lib/badline/chips/ted/timers.rb +66 -0
  90. data/lib/badline/chips/ted.rb +190 -0
  91. data/lib/badline/chips/tia.rb +61 -0
  92. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  93. data/lib/badline/chips/vdc/painter.rb +0 -12
  94. data/lib/badline/chips/vdc/raster.rb +0 -16
  95. data/lib/badline/chips/vdc/window.rb +0 -9
  96. data/lib/badline/chips/vdc.rb +1 -2
  97. data/lib/badline/chips/via/control_lines.rb +0 -15
  98. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  99. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  100. data/lib/badline/chips/via/shift_register.rb +0 -14
  101. data/lib/badline/chips/via/timer1.rb +0 -13
  102. data/lib/badline/chips/via/timer2.rb +0 -18
  103. data/lib/badline/chips/via.rb +1 -2
  104. data/lib/badline/chips/vic/border_mask.rb +0 -10
  105. data/lib/badline/chips/vic/collisions.rb +0 -11
  106. data/lib/badline/chips/vic/color_patches.rb +0 -11
  107. data/lib/badline/chips/vic/display_state.rb +0 -19
  108. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  109. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  110. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  111. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  112. data/lib/badline/chips/vic/register_log.rb +0 -18
  113. data/lib/badline/chips/vic/registers.rb +0 -9
  114. data/lib/badline/chips/vic/sequencer.rb +0 -2
  115. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  116. data/lib/badline/chips/vic/sprite.rb +0 -2
  117. data/lib/badline/chips/vic/sprites.rb +0 -3
  118. data/lib/badline/chips/vic.rb +6 -2
  119. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  120. data/lib/badline/chips/vic_i/painter.rb +0 -4
  121. data/lib/badline/chips/vic_i/sound.rb +38 -46
  122. data/lib/badline/chips/vic_i.rb +2 -2
  123. data/lib/badline/chips/z80/core.rb +2 -2
  124. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  125. data/lib/badline/chips.rb +10 -0
  126. data/lib/badline/core.rb +4 -21
  127. data/lib/badline/drives/datasette/keyed_state.rb +43 -0
  128. data/lib/badline/drives/datasette.rb +62 -12
  129. data/lib/badline/drives/engine/core.rb +3 -0
  130. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  131. data/lib/badline/drives/model1541/bus.rb +0 -13
  132. data/lib/badline/drives/model1541/disk.rb +9 -8
  133. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  134. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  135. data/lib/badline/drives/model1541.rb +1 -2
  136. data/lib/badline/drives/model1571/bus.rb +0 -13
  137. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  138. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  139. data/lib/badline/drives/model1571.rb +1 -2
  140. data/lib/badline/drives/model1581/bus.rb +0 -13
  141. data/lib/badline/drives/model1581/disk.rb +0 -4
  142. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  143. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  144. data/lib/badline/drives/model1581/slot.rb +0 -18
  145. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  146. data/lib/badline/drives/model1581.rb +2 -2
  147. data/lib/badline/drives.rb +8 -0
  148. data/lib/badline/frontend/app.rb +3 -3
  149. data/lib/badline/frontend/boot.rb +4 -2
  150. data/lib/badline/frontend/controls.rb +25 -5
  151. data/lib/badline/frontend/drive_led.rb +44 -21
  152. data/lib/badline/frontend/keys.rb +14 -2
  153. data/lib/badline/frontend/media_slots.rb +11 -1
  154. data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
  155. data/lib/badline/frontend/menu/menu_media.rb +13 -3
  156. data/lib/badline/frontend/menu/menu_pages.rb +8 -8
  157. data/lib/badline/frontend/menu/menu_ports.rb +13 -1
  158. data/lib/badline/frontend/screen.rb +33 -12
  159. data/lib/badline/frontend/screens.rb +2 -1
  160. data/lib/badline/frontend/snapshots.rb +12 -1
  161. data/lib/badline/frontend/sound.rb +4 -4
  162. data/lib/badline/frontend/timeline.rb +43 -12
  163. data/lib/badline/input/matrix_typist.rb +98 -0
  164. data/lib/badline/input.rb +1 -0
  165. data/lib/badline/kernal_trap/chrout.rb +45 -0
  166. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  167. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  168. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  169. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  170. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  171. data/lib/badline/kernal_trap/dos.rb +0 -19
  172. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  173. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  174. data/lib/badline/kernal_trap/layout.rb +75 -11
  175. data/lib/badline/kernal_trap/load.rb +14 -16
  176. data/lib/badline/kernal_trap/routine.rb +12 -0
  177. data/lib/badline/kernal_trap/save.rb +8 -18
  178. data/lib/badline/kernal_trap/serial.rb +1 -17
  179. data/lib/badline/kernal_trap.rb +2 -0
  180. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  181. data/lib/badline/machine/settling.rb +29 -0
  182. data/lib/badline/machine.rb +29 -6
  183. data/lib/badline/media/boot_disk.rb +14 -2
  184. data/lib/badline/media/extensions.rb +10 -0
  185. data/lib/badline/media/plus4_media.rb +68 -0
  186. data/lib/badline/media/program.rb +20 -6
  187. data/lib/badline/media.rb +33 -12
  188. data/lib/badline/memory.rb +3 -6
  189. data/lib/badline/options/family.rb +6 -2
  190. data/lib/badline/options/help.rb +66 -16
  191. data/lib/badline/options/table.rb +17 -2
  192. data/lib/badline/options/validation.rb +26 -2
  193. data/lib/badline/options.rb +42 -9
  194. data/lib/badline/plus4/acia.rb +45 -0
  195. data/lib/badline/plus4/bus.rb +375 -0
  196. data/lib/badline/plus4/cassette_sense.rb +21 -0
  197. data/lib/badline/plus4/cpu.rb +10 -0
  198. data/lib/badline/plus4/cpu_port.rb +93 -0
  199. data/lib/badline/plus4/keyboard_port.rb +76 -0
  200. data/lib/badline/plus4/keyed_modules.rb +99 -0
  201. data/lib/badline/plus4/keyed_parts.rb +143 -0
  202. data/lib/badline/plus4/keyed_state.rb +41 -0
  203. data/lib/badline/plus4/model.rb +38 -0
  204. data/lib/badline/plus4/user_port.rb +31 -0
  205. data/lib/badline/plus4.rb +240 -0
  206. data/lib/badline/roms/README +38 -0
  207. data/lib/badline/roms/plus4/basic.rom +0 -0
  208. data/lib/badline/roms/plus4/function-high.rom +0 -0
  209. data/lib/badline/roms/plus4/function-low.rom +0 -0
  210. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  211. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  212. data/lib/badline/snapshot/c128_setup.rb +9 -20
  213. data/lib/badline/snapshot/c64_setup.rb +28 -24
  214. data/lib/badline/snapshot/container.rb +6 -0
  215. data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
  216. data/lib/badline/snapshot/field_doc.rb +148 -0
  217. data/lib/badline/snapshot/image.rb +143 -28
  218. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  219. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  220. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  221. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  222. data/lib/badline/snapshot/keyed/report.rb +26 -0
  223. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  224. data/lib/badline/snapshot/keyed.rb +64 -0
  225. data/lib/badline/snapshot/machine_state.rb +17 -64
  226. data/lib/badline/snapshot/plus4_setup.rb +39 -0
  227. data/lib/badline/snapshot/storage_setup.rb +29 -34
  228. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  229. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  230. data/lib/badline/snapshot/vice.rb +7 -31
  231. data/lib/badline/snapshot.rb +48 -20
  232. data/lib/badline/storage/crt_file.rb +3 -1
  233. data/lib/badline/storage/g64_image.rb +9 -4
  234. data/lib/badline/storage/tap.rb +57 -20
  235. data/lib/badline/support/checkpoint.rb +3 -2
  236. data/lib/badline/support/plus4_checkpoint.rb +42 -0
  237. data/lib/badline/version.rb +1 -1
  238. data/lib/badline/vic20/attachments.rb +3 -0
  239. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  240. data/lib/badline/vic20/bus.rb +1 -2
  241. data/lib/badline/vic20/keyed_modules.rb +73 -0
  242. data/lib/badline/vic20/keyed_state.rb +45 -0
  243. data/lib/badline/vic20.rb +5 -5
  244. data/lib/badline.rb +1 -0
  245. metadata +75 -42
  246. data/lib/badline/c128/bus/saved_state.rb +0 -53
  247. data/lib/badline/c128/saved_state.rb +0 -117
  248. data/lib/badline/c64/bus/saved_state.rb +0 -68
  249. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  250. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  251. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  252. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  253. data/lib/badline/c64/reu/saved_state.rb +0 -55
  254. data/lib/badline/c64/saved_state.rb +0 -112
  255. data/lib/badline/chips/cia/saved_state.rb +0 -49
  256. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  257. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  258. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  259. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  260. data/lib/badline/chips/sid/saved_state.rb +0 -52
  261. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  262. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  263. data/lib/badline/chips/via/saved_state.rb +0 -44
  264. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  265. data/lib/badline/chips/vic/saved_state.rb +0 -82
  266. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  267. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  268. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  269. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  270. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  271. data/lib/badline/chips/z80/saved_state.rb +0 -71
  272. data/lib/badline/chrout_trap.rb +0 -43
  273. data/lib/badline/debug_register.rb +0 -28
  274. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  275. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  276. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  277. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  278. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  279. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  280. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  281. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  282. data/lib/badline/keyboard_buffer.rb +0 -33
  283. data/lib/badline/snapshot/setup.rb +0 -31
  284. data/lib/badline/snapshot/state.rb +0 -179
  285. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  286. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/chips/sid/filter/saved_state"
4
-
5
3
  module Badline
6
4
  module Chips
7
5
  class SID
@@ -13,8 +11,6 @@ module Badline
13
11
  # and the outputs selected by MODE/VOL are summed back together with the
14
12
  # unfiltered ones.
15
13
  class Filter
16
- include SavedState
17
-
18
14
  # Cutoff frequency in Hz at each chip's breakpoints. The 6581 curve was
19
15
  # sampled from a real chip by reSID and bends sharply around $300; the
20
16
  # 8580's is close to a straight line from 0 to 12.5 kHz. The curve
@@ -95,16 +91,6 @@ module Badline
95
91
  @output = 0
96
92
  end
97
93
 
98
- def save_state(out)
99
- out.int(@lowpass).int(@highpass).int(@output)
100
- end
101
-
102
- def load_state(input)
103
- @lowpass = input.int
104
- @highpass = input.int
105
- @output = input.int
106
- end
107
-
108
94
  # Takes the input in 1/2^Filter::FRACTION units, and reads the
109
95
  # output out in whole ones. A step of several cycles moves each pole
110
96
  # at most onto its input.
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class SID
6
+ class Waveform
7
+ # An oscillator's fields in a keyed snapshot module, under `prefix`:
8
+ # the phase, the noise register and its pipeline, the delayed
9
+ # shapers, the sync edge, the floating output and the pulse level.
10
+ # What the registers set comes from them on load.
11
+ module KeyedState
12
+ # The fields reSID keeps for an oscillator between cycles, for VICE's
13
+ # SIDEXTENDED module: the phase, the noise register, its shift pipeline
14
+ # and reset countdown, the floating output's countdown and the pulse
15
+ # level.
16
+ def resid_fields
17
+ [@accumulator, @shift_register, @shift_pipeline, @shift_register_reset, @floating_ttl, @pulse]
18
+ end
19
+
20
+ # Takes the fields resid_fields gives from `fields`, from `at` on.
21
+ def restore_resid_fields(fields, at)
22
+ @accumulator = fields[at]
23
+ @shift_register = fields[at + 1]
24
+ @shift_pipeline = fields[at + 2]
25
+ @shift_register_reset = fields[at + 3]
26
+ @floating_ttl = fields[at + 4]
27
+ @pulse = fields[at + 5]
28
+ @stale = true
29
+ end
30
+
31
+ def save_fields(out, prefix)
32
+ out.int("#{prefix}accumulator", @accumulator).int("#{prefix}shift_register", @shift_register)
33
+ out.int("#{prefix}shift_register_reset", @shift_register_reset)
34
+ out.int("#{prefix}shift_pipeline", @shift_pipeline).int("#{prefix}delayed_triangle", @delayed_triangle)
35
+ out.int("#{prefix}delayed_sawtooth", @delayed_sawtooth).bool("#{prefix}msb_rising", @msb_rising)
36
+ out.int("#{prefix}floating", @floating).int("#{prefix}floating_ttl", @floating_ttl)
37
+ out.int("#{prefix}pulse", @pulse)
38
+ end
39
+
40
+ def load_fields(input, prefix)
41
+ @accumulator = input.masked("#{prefix}accumulator", @accumulator, 0xffffff)
42
+ @shift_register = input.masked("#{prefix}shift_register", @shift_register, 0x7fffff)
43
+ @shift_register_reset = input.int("#{prefix}shift_register_reset", @shift_register_reset)
44
+ @shift_pipeline = input.int("#{prefix}shift_pipeline", @shift_pipeline)
45
+ @delayed_triangle = input.masked("#{prefix}delayed_triangle", @delayed_triangle, 0xfff)
46
+ @delayed_sawtooth = input.masked("#{prefix}delayed_sawtooth", @delayed_sawtooth, 0xfff)
47
+ @msb_rising = input.bool("#{prefix}msb_rising", @msb_rising)
48
+ @floating = input.masked("#{prefix}floating", @floating, 0xfff)
49
+ @floating_ttl = input.int("#{prefix}floating_ttl", @floating_ttl)
50
+ @pulse = input.masked("#{prefix}pulse", @pulse, 0xfff)
51
+ end
52
+
53
+ # The frequency, pulse width and control bits as the registers
54
+ # hold them, without the test bit's or the waveform change's
55
+ # effects on the noise register, which already happened.
56
+ def restore_registers(frequency, pulse_width, control)
57
+ @frequency = frequency
58
+ @pulse_width = pulse_width & 0xfff
59
+ @selected = (control >> 4) & 0x0f
60
+ @test = control.anybits?(0x08)
61
+ @ring = control.anybits?(0x04)
62
+ @sync = control.anybits?(0x02)
63
+ @stale = true
64
+ end
65
+ end
66
+
67
+ include KeyedState
68
+ end
69
+
70
+ class Envelope
71
+ # The envelope generator's fields in a keyed snapshot module, under
72
+ # `prefix`: the state, the counters and pipelines between them. The
73
+ # rates and the gate come from the registers on load.
74
+ module KeyedState
75
+ # The fields reSID keeps for an envelope between cycles, for VICE's
76
+ # SIDEXTENDED module: the rate and exponential counters, the level, the
77
+ # state as an index into STATES, whether it holds at zero, the two
78
+ # periods and the pipeline.
79
+ def resid_fields
80
+ [@rate_counter, @exponential_counter, @counter, STATES.index(@state).to_i, @hold_zero ? 1 : 0,
81
+ @rate_period, @exponential_period, @envelope_pipeline]
82
+ end
83
+
84
+ # Takes the fields resid_fields gives from `fields`, from `at` on.
85
+ def restore_resid_fields(fields, at)
86
+ @rate_counter = fields[at]
87
+ @exponential_counter = fields[at + 1]
88
+ @counter = @env3 = fields[at + 2]
89
+ state = fields[at + 3]
90
+ @state = @next_state = STATES.fetch(state.clamp(0, STATES.length - 1)).to_sym
91
+ @hold_zero = fields[at + 4] != 0
92
+ @rate_period = fields[at + 5]
93
+ @exponential_period = fields[at + 6]
94
+ @envelope_pipeline = fields[at + 7]
95
+ end
96
+
97
+ def save_fields(out, prefix)
98
+ out.text("#{prefix}state", @state.to_s).text("#{prefix}next_state", @next_state.to_s)
99
+ out.int("#{prefix}counter", @counter).int("#{prefix}env3", @env3)
100
+ out.int("#{prefix}rate_counter", @rate_counter)
101
+ out.int("#{prefix}rate_period", @rate_period).bool("#{prefix}reset_rate_counter", @reset_rate_counter)
102
+ out.int("#{prefix}exponential_counter", @exponential_counter)
103
+ out.int("#{prefix}exponential_period", @exponential_period)
104
+ out.int("#{prefix}state_pipeline", @state_pipeline).int("#{prefix}envelope_pipeline", @envelope_pipeline)
105
+ out.int("#{prefix}exponential_pipeline", @exponential_pipeline).bool("#{prefix}hold_zero", @hold_zero)
106
+ end
107
+
108
+ def load_fields(input, prefix)
109
+ @state = state_named(input.choice("#{prefix}state", STATE_NAMES, @state.to_s))
110
+ @next_state = state_named(input.choice("#{prefix}next_state", STATE_NAMES, @next_state.to_s))
111
+ @counter = input.byte("#{prefix}counter", @counter)
112
+ @env3 = input.byte("#{prefix}env3", @env3)
113
+ @rate_counter = input.masked("#{prefix}rate_counter", @rate_counter, 0x7fff)
114
+ @rate_period = input.int("#{prefix}rate_period", @rate_period)
115
+ @reset_rate_counter = input.bool("#{prefix}reset_rate_counter", @reset_rate_counter)
116
+ load_pipeline_fields(input, prefix)
117
+ end
118
+
119
+ # The rates and the gate as the registers hold them.
120
+ def restore_registers(attack_decay, sustain_release, control)
121
+ @attack = (attack_decay >> 4) & 0x0f
122
+ @decay = attack_decay & 0x0f
123
+ @sustain = (sustain_release >> 4) & 0x0f
124
+ @release = sustain_release & 0x0f
125
+ @gate = control.anybits?(0x01)
126
+ end
127
+
128
+ private
129
+
130
+ def load_pipeline_fields(input, prefix)
131
+ @exponential_counter = input.int("#{prefix}exponential_counter", @exponential_counter)
132
+ @exponential_period = input.int("#{prefix}exponential_period", @exponential_period)
133
+ @state_pipeline = input.int("#{prefix}state_pipeline", @state_pipeline)
134
+ @envelope_pipeline = input.int("#{prefix}envelope_pipeline", @envelope_pipeline)
135
+ @exponential_pipeline = input.int("#{prefix}exponential_pipeline", @exponential_pipeline)
136
+ @hold_zero = input.bool("#{prefix}hold_zero", @hold_zero)
137
+ end
138
+
139
+ def state_named(name)
140
+ case name
141
+ when "attack" then :attack
142
+ when "decay_sustain" then :decay_sustain
143
+ else :release
144
+ end
145
+ end
146
+ end
147
+
148
+ STATE_NAMES = STATES.map(&:to_s).freeze
149
+
150
+ include KeyedState
151
+ end
152
+
153
+ class Voice
154
+ # A voice's oscillator under `prefix`wave. and its envelope under
155
+ # `prefix`env.
156
+ module KeyedState
157
+ def save_fields(out, prefix)
158
+ @waveform.save_fields(out, "#{prefix}wave.")
159
+ @envelope.save_fields(out, "#{prefix}env.")
160
+ end
161
+
162
+ def load_fields(input, prefix)
163
+ @waveform.load_fields(input, "#{prefix}wave.")
164
+ @envelope.load_fields(input, "#{prefix}env.")
165
+ end
166
+ end
167
+
168
+ include KeyedState
169
+ end
170
+
171
+ class Filter
172
+ class External
173
+ # The output filter's integrators and output, under `prefix`.
174
+ module KeyedState
175
+ def save_fields(out, prefix)
176
+ out.int("#{prefix}lowpass", @lowpass).int("#{prefix}highpass", @highpass).int("#{prefix}output", @output)
177
+ end
178
+
179
+ def load_fields(input, prefix)
180
+ @lowpass = input.int("#{prefix}lowpass", @lowpass)
181
+ @highpass = input.int("#{prefix}highpass", @highpass)
182
+ @output = input.int("#{prefix}output", @output)
183
+ end
184
+ end
185
+
186
+ include KeyedState
187
+ end
188
+
189
+ # The filter's integrators and its last input, under `prefix`, and
190
+ # the output filter's under ext. The cutoff, resonance, routing,
191
+ # mode and volume come from the registers on load.
192
+ module KeyedState
193
+ def save_fields(out, prefix)
194
+ out.int("#{prefix}highpass", @highpass).int("#{prefix}bandpass", @bandpass)
195
+ out.int("#{prefix}lowpass", @lowpass).int("#{prefix}unfiltered", @unfiltered).int("#{prefix}input", @input)
196
+ @external.save_fields(out, "ext.")
197
+ end
198
+
199
+ def load_fields(input, prefix)
200
+ @highpass = input.int("#{prefix}highpass", @highpass)
201
+ @bandpass = input.int("#{prefix}bandpass", @bandpass)
202
+ @lowpass = input.int("#{prefix}lowpass", @lowpass)
203
+ @unfiltered = input.int("#{prefix}unfiltered", @unfiltered)
204
+ @input = input.int("#{prefix}input", @input)
205
+ @external.load_fields(input, "ext.")
206
+ end
207
+
208
+ # $D415 to $D418 as written.
209
+ def restore_registers(cutoff_low, cutoff_high, resonance, mode_volume)
210
+ self.cutoff = (cutoff_high << 3) | (cutoff_low & 0x07)
211
+ write_resonance(resonance)
212
+ write_mode_volume(mode_volume)
213
+ end
214
+ end
215
+
216
+ include KeyedState
217
+ end
218
+
219
+ # The SID's fields in a keyed snapshot module (Snapshot::Keyed): the
220
+ # registers as written, the data bus latch and its decay, each
221
+ # voice's under voice1. to voice3., and the filter's under filter.
222
+ # and ext. Saving catches the DSP up first, so no span or deferred
223
+ # write is left to save. What the registers decode to is worked out
224
+ # from them on load.
225
+ module KeyedState
226
+ def save_fields(out)
227
+ catch_up
228
+ out.bytes("registers", @registers.read(0, 0x20)).int("bus_value", @bus_value).int("bus_ttl", @bus_ttl)
229
+ @voice1.save_fields(out, "voice1.")
230
+ @voice2.save_fields(out, "voice2.")
231
+ @voice3.save_fields(out, "voice3.")
232
+ @filter.save_fields(out, "filter.")
233
+ out
234
+ end
235
+
236
+ def load_fields(input)
237
+ @registers.write(0, input.bytes_into("registers", @registers.read(0, 0x20)))
238
+ @bus_value = input.byte("bus_value", @bus_value)
239
+ @bus_ttl = input.int("bus_ttl", @bus_ttl)
240
+ @pending_cycles = 0
241
+ @deferred_writes.clear
242
+ @voice1.load_fields(input, "voice1.")
243
+ @voice2.load_fields(input, "voice2.")
244
+ @voice3.load_fields(input, "voice3.")
245
+ @filter.load_fields(input, "filter.")
246
+ end
247
+
248
+ # How far the recording has got into the sample it is averaging,
249
+ # under `prefix`, for a host that carries on recording from a
250
+ # snapshot: it calls #record, then #load_recording_fields.
251
+ def save_recording_fields(out, prefix)
252
+ decimator = @decimator
253
+ raise ArgumentError, "the SID isn't recording" if decimator.nil?
254
+
255
+ decimator.save_fields(out, prefix)
256
+ end
257
+
258
+ def load_recording_fields(input, prefix)
259
+ decimator = @decimator
260
+ raise ArgumentError, "the SID isn't recording" if decimator.nil?
261
+
262
+ decimator.load_fields(input, prefix)
263
+ end
264
+
265
+ def restored!
266
+ restore_voice(@voice1, 0x00)
267
+ restore_voice(@voice2, 0x07)
268
+ restore_voice(@voice3, 0x0e)
269
+ @filter.restore_registers(register(0x15), register(0x16), register(0x17), register(0x18))
270
+ update_span_rules
271
+ end
272
+
273
+ private
274
+
275
+ def restore_voice(voice, base)
276
+ control = register(base + 4)
277
+ voice.waveform.restore_registers(register(base) | (register(base + 1) << 8),
278
+ register(base + 2) | (register(base + 3) << 8), control)
279
+ voice.envelope.restore_registers(register(base + 5), register(base + 6), control)
280
+ end
281
+ end
282
+
283
+ include KeyedState
284
+ end
285
+ end
286
+ end
@@ -45,16 +45,6 @@ module Badline
45
45
  @envelope = Envelope.new
46
46
  end
47
47
 
48
- def save_state(out)
49
- @waveform.save_state(out)
50
- @envelope.save_state(out)
51
- end
52
-
53
- def load_state(input)
54
- @waveform.load_state(input)
55
- @envelope.load_state(input)
56
- end
57
-
58
48
  def control=(value)
59
49
  @waveform.control = value
60
50
  @envelope.control = value
@@ -3,7 +3,6 @@
3
3
  require "badline/chips/sid/waveform/combined"
4
4
  require "badline/chips/sid/waveform/fast_forward"
5
5
  require "badline/chips/sid/waveform/noise_writeback"
6
- require "badline/chips/sid/waveform/saved_state"
7
6
 
8
7
  module Badline
9
8
  module Chips
@@ -29,7 +28,6 @@ module Badline
29
28
  class Waveform
30
29
  include FastForward
31
30
  include NoiseWriteback
32
- include SavedState
33
31
 
34
32
  # All bits high at power up, odd ones stored inverted (SID/oscinit).
35
33
  POWER_ON_ACCUMULATOR = 0x555555
@@ -6,7 +6,7 @@ require "badline/chips/sid/voice"
6
6
  require "badline/chips/sid/filter"
7
7
  require "badline/chips/sid/decimator"
8
8
  require "badline/chips/sid/catch_up"
9
- require "badline/chips/sid/saved_state"
9
+ require "badline/chips/sid/keyed_parts"
10
10
 
11
11
  module Badline
12
12
  module Chips
@@ -37,7 +37,6 @@ module Badline
37
37
  class SID
38
38
  include Addressable
39
39
  include CatchUp
40
- include SavedState
41
40
 
42
41
  VOICES = 3
43
42
 
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class TED
6
+ # The raster and the clock the TED gives the CPU, a single clock
7
+ # cycle at a time.
8
+ #
9
+ # A line is 456 dots, horizontal counts 0-455, and a single clock
10
+ # cycle 8 of them, so a line is 57 cycles; #column counts them, the
11
+ # cycle at count 8 * column. The line count steps at count 376, in
12
+ # column 47, and runs 0-311 on PAL and 0-261 with $FF07 bit 6 set
13
+ # (NTSC). The datasheet's horizontal and vertical decode tables give
14
+ # those counts.
15
+ #
16
+ # The CPU runs once in a single clock cycle and twice in a double
17
+ # clock one. The datasheet's "System Clock Doubling": in the display
18
+ # window's lines the clock is single from count 400 round to 344,
19
+ # columns 50-56 and 0-42, and double in columns 43-49. The window is
20
+ # lines 0-203: the datasheet says 0-204, but xplus4 runs line 204 at
21
+ # double clock, as the lines below it. In the other lines it is double
22
+ # but for the five refresh cycles at counts 304-344, columns 38-42,
23
+ # which are single everywhere. With $FF06 bit 4 clear (blank) every
24
+ # line is double but for refresh, and $FF13 bit 1 or $FF07 bit 5
25
+ # (freeze) makes every cycle single.
26
+ #
27
+ # So a display line gives the CPU 64 cycles and a border line 109,
28
+ # the counts measured on real machines.
29
+ #
30
+ # DMA: each character row takes the bus for 40 cycles on two lines,
31
+ # the line before the row for the attributes and the row's first
32
+ # line for the character pointers (the datasheet's register 20). A
33
+ # row starts on the line after one whose low three bits match $FF06
34
+ # bits 0-2, as the KERNAL's scroll of 3 puts the first row on line 4,
35
+ # where the datasheet's 25-row window starts, and a row starts only
36
+ # in lines 0-203. xplus4 takes the bus on the same lines. The fetches run in columns 54-56 and 0-36, and BA
37
+ # goes low 3 cycles before the first, in column 51, so the CPU stops
38
+ # at its next read. A DMA line leaves the CPU 24 cycles.
39
+ module Beam
40
+ CYCLES_PER_LINE = 57
41
+ LINE_STEP_COLUMN = 47
42
+ BA_COLUMN = 51
43
+ DMA_COLUMN = 54
44
+ DMA_END_COLUMN = 36
45
+ REFRESH_COLUMNS = (38..42)
46
+ DOUBLE_COLUMNS = (43..49)
47
+ LAST_DISPLAY_LINE = 203
48
+
49
+ # The DMA a line makes: none, the attributes or the pointers.
50
+ NO_DMA = 0
51
+ ATTRIBUTE_DMA = 1
52
+ POINTER_DMA = 2
53
+
54
+ # The single clock cycle in the line, 0-56.
55
+ attr_reader :column
56
+
57
+ # The raster line.
58
+ attr_reader :line
59
+
60
+ # How many cycles the CPU runs in this cycle: 0 while the TED holds
61
+ # the bus for DMA, 1 at single clock and 2 at double clock.
62
+ attr_reader :cpu_slots
63
+
64
+ # Whether BA is low, from 3 cycles before DMA to its end.
65
+ def ba_low? = @ba_low
66
+
67
+ # Lines a frame, by $FF07 bit 6.
68
+ def lines_per_frame = @registers[0x07].anybits?(0x40) ? 262 : 312
69
+
70
+ private
71
+
72
+ def reset_beam
73
+ @column = 0
74
+ @line = 0
75
+ @ba_low = false
76
+ @cpu_slots = 1
77
+ end
78
+
79
+ def move_beam
80
+ column = @column + 1
81
+ column = 0 if column == CYCLES_PER_LINE
82
+ @column = column
83
+ return unless column == LINE_STEP_COLUMN
84
+
85
+ line = @line + 1
86
+ if line >= lines_per_frame
87
+ line = 0
88
+ end_frame
89
+ end
90
+ step_row_line(line)
91
+ step_scanline(line)
92
+ self.line = line
93
+ end
94
+
95
+ # The clock for the cycle. Freeze leaves single clock, without DMA.
96
+ def decide_clock
97
+ column = @column
98
+ if @dma_kind != NO_DMA && (column >= BA_COLUMN || column <= DMA_END_COLUMN) && !frozen?
99
+ @ba_low = true
100
+ @cpu_slots = column >= DMA_COLUMN || column <= DMA_END_COLUMN ? 0 : 1
101
+ return
102
+ end
103
+
104
+ @ba_low = false
105
+ @cpu_slots = single_clock?(column) ? 1 : 2
106
+ end
107
+
108
+ def single_clock?(column)
109
+ return true if REFRESH_COLUMNS.include?(column) || @registers[0x13].anybits?(0x02) || frozen?
110
+
111
+ display_window? && !DOUBLE_COLUMNS.include?(column)
112
+ end
113
+
114
+ def display_window? = @registers[0x06].anybits?(0x10) && @line <= LAST_DISPLAY_LINE
115
+ end
116
+ end
117
+ end
118
+ end
@@ -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