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
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/ted/sound/output"
4
+
5
+ module Badline
6
+ module Chips
7
+ class TED
8
+ # The TED's sound: two voices, the second a square wave or noise.
9
+ # Voice 1's 10-bit frequency is $FF0E with $FF12 bits 0-1 above it,
10
+ # voice 2's $FF0F with $FF10 bits 0-1. $FF11 holds the volume in bits
11
+ # 0-3 (0 silent, 8 and above the loudest), voice 1 on in bit 4, voice
12
+ # 2's square wave in bit 5 and its noise in bit 6 (the square wins if
13
+ # both are set), and the reload bit in bit 7.
14
+ #
15
+ # Each voice counts once every 4 cycles, and its output toggles every
16
+ # 1023 - x counts for a frequency x, so a square wave of the clock
17
+ # over 8 * (1023 - x). $3FF counts 1,024 between toggles, and $3FE
18
+ # stops the voice after its next toggle, with its output high. A new
19
+ # frequency takes hold at the next toggle. The voices count whether
20
+ # they are on or not.
21
+ #
22
+ # The noise is an 8-bit shift register that shifts at each of voice
23
+ # 2's toggles: bits 1, 4, 5 and 7 XORed go in at bit 0, and bit 1 is
24
+ # the noise's output.
25
+ #
26
+ # While the reload bit is set, the counters hold at their frequencies,
27
+ # the outputs stay high and the noise's register holds all ones, with
28
+ # the noise silent. Writing it clear starts each voice on a full high
29
+ # half-wave. Samples play this way, with the voices held high and the
30
+ # volume as a 9-level D/A converter.
31
+ #
32
+ # The output takes a level from the volume and how many voices are on
33
+ # and high. The levels are measured from xplus4 recordings, whose two
34
+ # voices together come out above twice one.
35
+ #
36
+ # The voices catch up to the current cycle whenever a write lands, the
37
+ # samples are drained or #catch_up! asks, whether or not #record
38
+ # collects the output, so their state doesn't depend on whether the
39
+ # host plays it.
40
+ class Sound
41
+ VOICES = 2
42
+ CYCLES_PER_COUNT = 4
43
+
44
+ # The frequency that stops a voice with its output high.
45
+ HELD = 0x3fe
46
+
47
+ NOISE_TAPS = 0xb2
48
+ NOISE_PERIOD = 255
49
+
50
+ # The output's level for each volume 0-8, with no voice, one voice
51
+ # and both voices high.
52
+ LEVELS = [
53
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,
54
+ 0, 586, 1610, 2634, 3658, 4682, 5706, 6730, 7754,
55
+ 0, 1172, 3284, 5526, 7984, 10_658, 13_548, 16_654, 19_976
56
+ ].freeze
57
+
58
+ # Far enough ahead that a voice stopped at $3FE, or held by the
59
+ # reload bit, never toggles.
60
+ NEVER = 1 << 60
61
+
62
+ attr_reader :volume, :control
63
+
64
+ # `clock` answers the machine's cycles, `clock_hz` the rate they run at.
65
+ def initialize(clock, clock_hz)
66
+ @clock = clock
67
+ @clock_hz = clock_hz
68
+ @output = nil
69
+ @time = 0
70
+ @frequencies = [0, 0]
71
+ @intervals = [0, 0]
72
+ @phases = [1, 1]
73
+ @next_toggles = [0, 0]
74
+ power_on!
75
+ end
76
+
77
+ def model = :mos7360
78
+
79
+ # The power-on state: every register clear, both voices starting on
80
+ # a high half-wave and the noise's register full of ones.
81
+ def power_on!
82
+ now = @clock.cycles
83
+ run_to(now)
84
+ @volume = 0
85
+ @control = 0
86
+ @reloading = false
87
+ @noise = 0xff
88
+ VOICES.times do |voice|
89
+ @frequencies[voice] = 0
90
+ @intervals[voice] = interval_for(0)
91
+ @phases[voice] = 1
92
+ @next_toggles[voice] = now + @intervals[voice]
93
+ end
94
+ @level = level_now
95
+ end
96
+
97
+ # Starts collecting the output, averaged down to `rate` samples a
98
+ # second, for #drain_samples. `clock_hz` is the rate the cycles are
99
+ # taken to run at.
100
+ def record(rate:, clock_hz: @clock_hz)
101
+ run_to(@clock.cycles)
102
+ @output = Output.new(clock_hz:, rate:, level: @level)
103
+ end
104
+
105
+ def recording? = !@output.nil?
106
+
107
+ # Runs the voices up to the current cycle.
108
+ def catch_up! = run_to(@clock.cycles)
109
+
110
+ # The samples recorded since the last drain.
111
+ def drain_samples
112
+ output = @output
113
+ return [] if output.nil?
114
+
115
+ run_to(@clock.cycles)
116
+ output.drain
117
+ end
118
+
119
+ # A write to $FF0E-$FF12, which takes hold from the next cycle.
120
+ def write(register, value)
121
+ run_to(@clock.cycles + 1)
122
+ case register
123
+ when 0x0e then tune(0, (@frequencies[0] & 0x300) | value)
124
+ when 0x0f then tune(1, (@frequencies[1] & 0x300) | value)
125
+ when 0x10 then tune(1, (@frequencies[1] & 0xff) | ((value & 0x03) << 8))
126
+ when 0x11 then write_control(value)
127
+ when 0x12 then tune(0, (@frequencies[0] & 0xff) | ((value & 0x03) << 8))
128
+ end
129
+ @level = level_now
130
+ end
131
+
132
+ def frequency(voice) = @frequencies[voice]
133
+
134
+ def reloading? = @reloading
135
+
136
+ # A voice's square-wave output as of the current cycle, 1 high.
137
+ def phase(voice)
138
+ run_to(@clock.cycles)
139
+ @phases[voice]
140
+ end
141
+
142
+ # The noise's shift register as of the current cycle.
143
+ def noise
144
+ run_to(@clock.cycles)
145
+ @noise
146
+ end
147
+
148
+ # The output's level as of the current cycle.
149
+ def level
150
+ run_to(@clock.cycles)
151
+ @level
152
+ end
153
+
154
+ private
155
+
156
+ # Runs the voices up to the start of cycle `target`, the output holding
157
+ # each level for the cycles it lasts.
158
+ def run_to(target)
159
+ time = @time
160
+ return if target <= time
161
+
162
+ output = @output
163
+ return skip_to(target) if output.nil?
164
+
165
+ following = next_toggle
166
+ while following < target
167
+ output.hold_level(@level, following - time)
168
+ time = following
169
+ toggle_due(time)
170
+ @level = level_now
171
+ following = next_toggle
172
+ end
173
+ output.hold_level(@level, target - time)
174
+ @time = target
175
+ end
176
+
177
+ # Moves each voice through its toggles before `target` at once,
178
+ # the noise by as many shifts as voice 2 toggles, less whole turns
179
+ # of its sequence.
180
+ def skip_to(target)
181
+ VOICES.times do |voice|
182
+ due = @next_toggles[voice]
183
+ next if due >= target
184
+
185
+ interval = @intervals[voice]
186
+ toggles = ((target - 1 - due) / interval) + 1
187
+ @next_toggles[voice] = due + (toggles * interval)
188
+ @phases[voice] ^= toggles & 1
189
+ next unless voice == 1
190
+
191
+ (toggles % NOISE_PERIOD).times { shift_noise }
192
+ end
193
+ @level = level_now
194
+ @time = target
195
+ end
196
+
197
+ def next_toggle
198
+ toggles = @next_toggles
199
+ following = toggles[0]
200
+ following = toggles[1] if toggles[1] < following
201
+ following
202
+ end
203
+
204
+ def toggle_due(time)
205
+ toggles = @next_toggles
206
+ VOICES.times do |voice|
207
+ next unless toggles[voice] == time
208
+
209
+ @phases[voice] ^= 1
210
+ toggles[voice] = time + @intervals[voice]
211
+ shift_noise if voice == 1
212
+ end
213
+ end
214
+
215
+ def shift_noise
216
+ noise = @noise
217
+ taps = noise & NOISE_TAPS
218
+ taps ^= taps >> 4
219
+ taps ^= taps >> 2
220
+ taps ^= taps >> 1
221
+ @noise = ((noise << 1) | (taps & 1)) & 0xff
222
+ end
223
+
224
+ # The cycles between a voice's toggles at frequency `value`, and
225
+ # never again after the next one at $3FE.
226
+ def interval_for(value)
227
+ return NEVER if value == HELD
228
+
229
+ (((HELD - value) & 0x3ff) + 1) * CYCLES_PER_COUNT
230
+ end
231
+
232
+ # A new frequency for `voice`, which takes hold at its next toggle. A
233
+ # voice stopped at $3FE starts counting now.
234
+ def tune(voice, value)
235
+ @frequencies[voice] = value
236
+ @intervals[voice] = interval_for(value)
237
+ return if @reloading || @next_toggles[voice] < NEVER
238
+
239
+ @next_toggles[voice] = @time + @intervals[voice]
240
+ end
241
+
242
+ def write_control(value)
243
+ reload = value >= 0x80
244
+ if reload && !@reloading
245
+ hold_voices
246
+ elsif !reload && @reloading
247
+ release_voices
248
+ end
249
+ @reloading = reload
250
+ @control = value
251
+ volume = value & 0x0f
252
+ volume = 8 if volume > 8
253
+ @volume = volume
254
+ end
255
+
256
+ def hold_voices
257
+ @phases[0] = 1
258
+ @phases[1] = 1
259
+ @noise = 0xff
260
+ @next_toggles[0] = NEVER
261
+ @next_toggles[1] = NEVER
262
+ end
263
+
264
+ def release_voices
265
+ VOICES.times { |voice| @next_toggles[voice] = @time + @intervals[voice] }
266
+ end
267
+
268
+ def level_now
269
+ control = @control
270
+ high = 0
271
+ high += 1 if control.anybits?(0x10) && high?(0)
272
+ if control.anybits?(0x20)
273
+ high += 1 if high?(1)
274
+ elsif control.anybits?(0x40) && !@reloading
275
+ high += 1 if @noise.anybits?(0x02) || @frequencies[1] == HELD
276
+ end
277
+ LEVELS[(high * 9) + @volume]
278
+ end
279
+
280
+ def high?(voice) = @phases[voice] == 1 || @frequencies[voice] == HELD
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class TED
6
+ # The three 16-bit timers at $FF00-$FF05, which count down once a
7
+ # single clock cycle whatever clock the CPU runs at. Each raises its
8
+ # interrupt flag as it counts down to zero. Timer 1's next count then
9
+ # loads the value last written to it, and timers 2 and 3 roll over to
10
+ # $FFFF.
11
+ #
12
+ # A write to a timer's low byte stops it until its high byte is
13
+ # written. Timer 1's writes set its reload value and its count
14
+ # together, and timers 2 and 3's set the count.
15
+ module Timers
16
+ # The interrupt flag each timer raises in $FF09.
17
+ TIMER_FLAGS = [0x08, 0x10, 0x40].freeze
18
+
19
+ # The counts, the reload value of timer 1, and which timers run.
20
+ attr_reader :timer_counts, :timer_running
21
+
22
+ private
23
+
24
+ def reset_timers
25
+ @timer_counts = [0, 0, 0]
26
+ @timer_running = [false, false, false]
27
+ @timer1_reload = 0
28
+ end
29
+
30
+ def count_timers
31
+ i = 0
32
+ while i < 3
33
+ count_timer(i) if @timer_running[i]
34
+ i += 1
35
+ end
36
+ end
37
+
38
+ def count_timer(index)
39
+ count = @timer_counts[index]
40
+ if count.zero? && index.zero?
41
+ @timer_counts[0] = @timer1_reload
42
+ return
43
+ end
44
+
45
+ count = (count - 1) & 0xffff
46
+ @timer_counts[index] = count
47
+ @interrupt_flags |= TIMER_FLAGS[index] if count.zero?
48
+ end
49
+
50
+ def read_timer(register)
51
+ count = @timer_counts[register >> 1]
52
+ register.even? ? count & 0xff : count >> 8
53
+ end
54
+
55
+ def write_timer(register, value)
56
+ index = register >> 1
57
+ count = @timer_counts[index]
58
+ count = register.even? ? (count & 0xff00) | value : (count & 0x00ff) | (value << 8)
59
+ @timer_counts[index] = count
60
+ @timer1_reload = count if index.zero?
61
+ @timer_running[index] = register.odd?
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/ted/timers"
4
+ require "badline/chips/ted/beam"
5
+ require "badline/chips/ted/fetches"
6
+ require "badline/chips/ted/palette"
7
+ require "badline/chips/ted/modes"
8
+ require "badline/chips/ted/picture"
9
+ require "badline/chips/ted/sound"
10
+ require "badline/chips/ted/keyed_state"
11
+
12
+ module Badline
13
+ module Chips
14
+ # The TED of the Commodore 264 series, the 7360 and 8360: its registers
15
+ # at $FF00-$FF1F, the raster, the three timers, the interrupt, the
16
+ # keyboard latch, the clock it gives the CPU (Beam), what it fetches
17
+ # for the picture (Fetches) and the picture (Picture). Writes to the
18
+ # sound's registers, $FF0E-$FF12, go on to the Sound attached as
19
+ # #sound.
20
+ #
21
+ # The machine clocks it once a single clock cycle, 57 to a line, and
22
+ # asks it then how many cycles the CPU runs in that one (#cpu_slots)
23
+ # and whether BA is low (#ba_low?).
24
+ #
25
+ # $FF08 is the keyboard latch: a write latches the K0-K7 inputs, which
26
+ # the keyboard port answers for the byte written (KeyboardPort#latch),
27
+ # and a read returns them. $FF09 holds the interrupt flags, each
28
+ # cleared by writing a 1 to it, with bit 7 set while the interrupt
29
+ # line is pulled. $FF0A holds the mask and bit 8 of the raster compare,
30
+ # and $FF0B the compare's low byte. $FF13 bit 0 reads whether the CPU
31
+ # sees ROM at $8000-$FFFF (#rom_selected=). $FF1C and $FF1D read and
32
+ # set the raster line, and $FF1E reads the horizontal position's top 8
33
+ # bits. $FF1A and $FF1B read and set the character position reload,
34
+ # and $FF1F the row's line in bits 0-2 and in bits 3-6 the blink
35
+ # counter, which counts frames.
36
+ class TED
37
+ include Timers
38
+ include Beam
39
+ include Fetches
40
+ include Palette
41
+ include Modes
42
+ include Picture
43
+
44
+ # The clock a TED is fed, its single clock: the crystal divided by 20
45
+ # on PAL machines and by 16 on NTSC ones (the Plus/4 hardware manual's
46
+ # TED datasheet).
47
+ Profile = Data.define(:name, :clock_hz)
48
+
49
+ PAL = Profile.new(name: :pal, clock_hz: 886_724)
50
+ NTSC = Profile.new(name: :ntsc, clock_hz: 894_886)
51
+
52
+ RASTER_FLAG = 0x02
53
+
54
+ # The interrupt sources a mask bit can enable: the raster, the light
55
+ # pen and the three timers.
56
+ SOURCES = 0x5e
57
+
58
+ # The registers the picture reads as it paints: $FF06, $FF07, the
59
+ # cursor and the five colours.
60
+ PICTURE_REGISTERS = 0x03e030c0
61
+
62
+ attr_reader :profile, :interrupt_flags, :interrupt_mask
63
+
64
+ # What answers the keyboard latch: an object with #latch(value).
65
+ attr_writer :keyboard_port
66
+
67
+ # Whether the CPU sees ROM at $8000-$FFFF, which $FF13 bit 0 reads.
68
+ attr_writer :rom_selected
69
+
70
+ # The Sound the sound's registers drive.
71
+ attr_accessor :sound
72
+
73
+ def initialize(profile = PAL)
74
+ @profile = profile
75
+ @keyboard_port = nil
76
+ @rom_selected = true
77
+ @bus = nil
78
+ @sound = nil
79
+ power_on!
80
+ end
81
+
82
+ def power_on!
83
+ @registers = Array.new(0x20, 0)
84
+ @interrupt_flags = 0
85
+ @interrupt_mask = 0
86
+ @raster_compare = 0
87
+ @key_latch = 0xff
88
+ @blink = 0
89
+ reset_timers
90
+ reset_fetches
91
+ reset_picture
92
+ reset_beam
93
+ end
94
+
95
+ # Counts the timers, moves the beam on a cycle and makes the cycle's
96
+ # fetches, unless $FF07's freeze bit holds them, then decides the
97
+ # CPU's clock for the cycle.
98
+ def cycle!
99
+ unless frozen?
100
+ count_timers
101
+ move_beam
102
+ fetch(@column)
103
+ picture_cycle(@column)
104
+ end
105
+ decide_clock
106
+ end
107
+
108
+ # Whether the interrupt line is pulled: a flag raised whose mask bit
109
+ # is set.
110
+ def irq? = @interrupt_flags.anybits?(@interrupt_mask & SOURCES)
111
+
112
+ def peek(addr)
113
+ register = addr & 0x3f
114
+ return 0xff if register >= 0x20
115
+
116
+ case register
117
+ when 0x00..0x05 then read_timer(register)
118
+ when 0x08 then @key_latch
119
+ when 0x09 then read_interrupt_flags
120
+ when 0x0a then @interrupt_mask | 0xa0
121
+ when 0x0b then @raster_compare & 0xff
122
+ when 0x13 then (@registers[0x13] & 0xfe) | (@rom_selected ? 1 : 0)
123
+ when 0x1a then 0xfc | (@char_position >> 8)
124
+ when 0x1b then @char_position & 0xff
125
+ when 0x1c then 0xfe | (@line >> 8)
126
+ when 0x1d then @line & 0xff
127
+ when 0x1e then @column << 2
128
+ when 0x1f then 0x80 | (@blink << 3) | @row_line
129
+ else @registers[register]
130
+ end
131
+ end
132
+
133
+ def poke(addr, value)
134
+ register = addr & 0x3f
135
+ return if register >= 0x20
136
+
137
+ paint_to_write if (PICTURE_REGISTERS >> register).anybits?(1)
138
+ @registers[register] = value
139
+ case register
140
+ when 0x00..0x05 then write_timer(register, value)
141
+ when 0x08 then @key_latch = @keyboard_port ? @keyboard_port.latch(value) : 0xff
142
+ when 0x09 then @interrupt_flags &= ~value
143
+ when 0x0a then write_interrupt_mask(value)
144
+ when 0x0b then @raster_compare = (@raster_compare & 0x100) | value
145
+ when 0x0e..0x12 then @sound&.write(register, value)
146
+ when 0x1a then @char_position = (@char_position & 0xff) | ((value & 0x03) << 8)
147
+ when 0x1b then @char_position = (@char_position & 0x300) | value
148
+ when 0x1c then self.line = (@line & 0xff) | ((value & 0x01) << 8)
149
+ when 0x1d then self.line = (@line & 0x100) | value
150
+ when 0x1f then write_row_line(value)
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ # $FF07 bit 5 stops the beam and the timers.
157
+ def frozen? = @registers[0x07].anybits?(0x20)
158
+
159
+ def read_interrupt_flags
160
+ value = @interrupt_flags | 0x21
161
+ irq? ? value | 0x80 : value
162
+ end
163
+
164
+ def write_row_line(value)
165
+ @row_line = value & 0x07
166
+ @blink = (value >> 3) & 0x0f
167
+ end
168
+
169
+ # The end of a frame: the fetches' counters start over.
170
+ def end_frame
171
+ end_fetch_frame
172
+ end_picture_frame
173
+ end
174
+
175
+ def write_interrupt_mask(value)
176
+ @interrupt_mask = value & 0x5f
177
+ @raster_compare = (@raster_compare & 0xff) | ((value & 0x01) << 8)
178
+ end
179
+
180
+ def line=(value)
181
+ @line = value
182
+ raster_reached
183
+ end
184
+
185
+ def raster_reached
186
+ @interrupt_flags |= RASTER_FLAG if @line == @raster_compare
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ # The 6523 TIA, a tri-port interface adapter: three 8-bit ports, A, B
6
+ # and C at registers 0-2, each with its direction register at 3-5, a 1
7
+ # making the pin an output. Its three address lines repeat the
8
+ # registers every 8 bytes, and registers 6 and 7 read $FF.
9
+ #
10
+ # A pin read as an output returns what the port drives, and one read as
11
+ # an input returns what the other side drives, #inputs, per port.
12
+ class TIA
13
+ attr_reader :ports, :directions
14
+
15
+ # What the other side drives onto each port's pins.
16
+ attr_reader :inputs
17
+
18
+ def initialize
19
+ @inputs = [0, 0, 0]
20
+ reset!
21
+ end
22
+
23
+ def reset!
24
+ @ports = [0, 0, 0]
25
+ @directions = [0, 0, 0]
26
+ end
27
+
28
+ def peek(addr)
29
+ register = addr & 0x07
30
+ return 0xff if register > 5
31
+ return @directions[register - 3] if register >= 3
32
+
33
+ direction = @directions[register]
34
+ (@ports[register] & direction) | (@inputs[register] & ~direction & 0xff)
35
+ end
36
+
37
+ def poke(addr, value)
38
+ register = addr & 0x07
39
+ return if register > 5
40
+
41
+ if register >= 3
42
+ @directions[register - 3] = value
43
+ else
44
+ @ports[register] = value
45
+ end
46
+ end
47
+
48
+ # The ports, their directions and what the other side drives, in a
49
+ # keyed snapshot module (Snapshot::Keyed, BL-TIA1 and BL-TIA2).
50
+ def save_fields(out)
51
+ out.ints("ports", @ports).ints("directions", @directions).ints("inputs", @inputs)
52
+ end
53
+
54
+ def load_fields(input)
55
+ input.ints_into("ports", @ports)
56
+ input.ints_into("directions", @directions)
57
+ input.ints_into("inputs", @inputs)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VDC
6
+ class Raster
7
+ # The raster's fields in a keyed snapshot module, under `prefix`:
8
+ # the frame count, where the beam is in the character rows and the
9
+ # frame, and the frame's size and sync as it latched them.
10
+ module KeyedState
11
+ def save_fields(out, prefix)
12
+ out.int("#{prefix}frame", @frame).int("#{prefix}row", @row).int("#{prefix}row_line", @row_line)
13
+ out.bool("#{prefix}adjusting", @adjusting).int("#{prefix}frame_line", @frame_line)
14
+ out.int("#{prefix}frame_lines", @frame_lines).int("#{prefix}sync_start", @sync_start)
15
+ out.int("#{prefix}sync_lines", @sync_lines)
16
+ end
17
+
18
+ def load_fields(input, prefix)
19
+ @frame = input.int("#{prefix}frame", @frame)
20
+ @row = input.int("#{prefix}row", @row)
21
+ @row_line = input.int("#{prefix}row_line", @row_line)
22
+ @adjusting = input.bool("#{prefix}adjusting", @adjusting)
23
+ @frame_line = input.int("#{prefix}frame_line", @frame_line)
24
+ @frame_lines = input.int("#{prefix}frame_lines", @frame_lines)
25
+ @sync_start = input.int("#{prefix}sync_start", @sync_start)
26
+ @sync_lines = input.int("#{prefix}sync_lines", @sync_lines)
27
+ end
28
+ end
29
+
30
+ include KeyedState
31
+ end
32
+
33
+ class Window
34
+ # The start addresses the frame latched, under `prefix`.
35
+ module KeyedState
36
+ def save_fields(out, prefix)
37
+ out.int("#{prefix}display_start", @display_start).int("#{prefix}attribute_start", @attribute_start)
38
+ end
39
+
40
+ def load_fields(input, prefix)
41
+ @display_start = input.word("#{prefix}display_start", @display_start)
42
+ @attribute_start = input.word("#{prefix}attribute_start", @attribute_start)
43
+ end
44
+ end
45
+
46
+ include KeyedState
47
+ end
48
+
49
+ class Painter
50
+ # The display's size, as the frame latched it, and the window's
51
+ # start addresses. The picture is painted again.
52
+ module KeyedState
53
+ def save_fields(out)
54
+ out.int("display.width", @width).int("display.height", @height)
55
+ @window.save_fields(out, "window.")
56
+ end
57
+
58
+ def load_fields(input)
59
+ resize(input.int("display.width", @width), input.int("display.height", @height))
60
+ @window.load_fields(input, "window.")
61
+ end
62
+ end
63
+
64
+ include KeyedState
65
+ end
66
+
67
+ # The VDC's fields in a keyed snapshot module (Snapshot::Keyed): the
68
+ # registers, the RAM, the register selected, the data latches, the
69
+ # dot clock's phase and the line's length in the same units
70
+ # (dots·Hz, the line latched at its start), how long an access or
71
+ # block operation stays busy from now, the raster under raster.* and
72
+ # the display's size and window under display.* and window.*. The
73
+ # RAM's layout comes from R28 on load.
74
+ module KeyedState
75
+ def save_fields(out)
76
+ out.bytes("registers", @registers).bytes("ram", @memory.ram).int("selected", @selected)
77
+ out.int("read_latch", @read_latch).int("write_data", @write_data).int("phase", @phase)
78
+ out.int("line_units", @line_units).int("busy_for", @busy_until - @phase)
79
+ @raster.save_fields(out, "raster.")
80
+ @painter.save_fields(out)
81
+ out
82
+ end
83
+
84
+ def load_fields(input)
85
+ input.bytes_into("registers", @registers)
86
+ input.bytes_into("ram", @memory.ram)
87
+ @selected = input.masked("selected", @selected, 0x3f)
88
+ @read_latch = input.byte("read_latch", @read_latch)
89
+ @write_data = input.byte("write_data", @write_data)
90
+ @phase = input.int("phase", @phase)
91
+ @line_units = input.int("line_units", @line_units)
92
+ @busy_until = @phase + input.int("busy_for", @busy_until - @phase)
93
+ @raster.load_fields(input, "raster.")
94
+ @painter.load_fields(input)
95
+ end
96
+
97
+ def restored!
98
+ @memory.mode64 = @registers[28].anybits?(0x10)
99
+ end
100
+ end
101
+
102
+ include KeyedState
103
+ end
104
+ end
105
+ end