badline 0.13.0 → 0.14.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 (254) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/CLAUDE.md +27 -7
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +8 -5
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +29 -0
  8. data/doc/snapshot-format.md +1047 -0
  9. data/doc/snapshots.md +72 -39
  10. data/lib/badline/audio/bare_player.rb +50 -17
  11. data/lib/badline/audio/machine_player.rb +15 -9
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +6 -3
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +17 -3
  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 +20 -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/keyed_state.rb +41 -0
  31. data/lib/badline/c64/bus.rb +2 -1
  32. data/lib/badline/c64/cartridge/action_replay.rb +0 -13
  33. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  34. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  35. data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
  36. data/lib/badline/c64/cartridge/flash.rb +3 -3
  37. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  38. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  39. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  40. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  41. data/lib/badline/c64/cartridge/pagefox.rb +0 -10
  42. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  43. data/lib/badline/c64/cartridge/rgcd.rb +0 -10
  44. data/lib/badline/c64/cartridge/super_games.rb +0 -10
  45. data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
  46. data/lib/badline/c64/cartridge.rb +1 -27
  47. data/lib/badline/c64/kernal_traps.rb +28 -25
  48. data/lib/badline/c64/keyed_modules.rb +111 -0
  49. data/lib/badline/c64/keyed_state.rb +68 -0
  50. data/lib/badline/c64/reu/dma.rb +0 -4
  51. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  52. data/lib/badline/c64/reu/ram.rb +0 -15
  53. data/lib/badline/c64/reu.rb +1 -3
  54. data/lib/badline/c64/true_drives.rb +56 -5
  55. data/lib/badline/c64.rb +7 -3
  56. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  57. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  58. data/lib/badline/chips/cia/serial.rb +0 -4
  59. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  60. data/lib/badline/chips/cia/timer.rb +0 -20
  61. data/lib/badline/chips/cia.rb +2 -2
  62. data/lib/badline/chips/cpu/core.rb +18 -2
  63. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  64. data/lib/badline/chips/cpu/microcode.rb +16 -0
  65. data/lib/badline/chips/cpu/port.rb +20 -3
  66. data/lib/badline/chips/cpu.rb +5 -0
  67. data/lib/badline/chips/sid/decimator.rb +10 -8
  68. data/lib/badline/chips/sid/envelope.rb +1 -6
  69. data/lib/badline/chips/sid/filter.rb +0 -14
  70. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  71. data/lib/badline/chips/sid/voice.rb +0 -10
  72. data/lib/badline/chips/sid/waveform.rb +0 -2
  73. data/lib/badline/chips/sid.rb +1 -2
  74. data/lib/badline/chips/ted/beam.rb +119 -0
  75. data/lib/badline/chips/ted/timers.rb +66 -0
  76. data/lib/badline/chips/ted.rb +143 -0
  77. data/lib/badline/chips/tia.rb +49 -0
  78. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  79. data/lib/badline/chips/vdc/painter.rb +0 -12
  80. data/lib/badline/chips/vdc/raster.rb +0 -16
  81. data/lib/badline/chips/vdc/window.rb +0 -9
  82. data/lib/badline/chips/vdc.rb +1 -2
  83. data/lib/badline/chips/via/control_lines.rb +0 -15
  84. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  85. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  86. data/lib/badline/chips/via/shift_register.rb +0 -14
  87. data/lib/badline/chips/via/timer1.rb +0 -13
  88. data/lib/badline/chips/via/timer2.rb +0 -18
  89. data/lib/badline/chips/via.rb +1 -2
  90. data/lib/badline/chips/vic/border_mask.rb +0 -10
  91. data/lib/badline/chips/vic/collisions.rb +0 -11
  92. data/lib/badline/chips/vic/color_patches.rb +0 -11
  93. data/lib/badline/chips/vic/display_state.rb +0 -19
  94. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  95. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  96. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  97. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  98. data/lib/badline/chips/vic/register_log.rb +0 -18
  99. data/lib/badline/chips/vic/registers.rb +0 -9
  100. data/lib/badline/chips/vic/sequencer.rb +0 -2
  101. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  102. data/lib/badline/chips/vic/sprite.rb +0 -2
  103. data/lib/badline/chips/vic/sprites.rb +0 -3
  104. data/lib/badline/chips/vic.rb +6 -2
  105. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  106. data/lib/badline/chips/vic_i/painter.rb +0 -4
  107. data/lib/badline/chips/vic_i/sound.rb +38 -46
  108. data/lib/badline/chips/vic_i.rb +2 -2
  109. data/lib/badline/chips/z80/core.rb +2 -2
  110. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  111. data/lib/badline/chips.rb +10 -0
  112. data/lib/badline/core.rb +4 -21
  113. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  114. data/lib/badline/drives/datasette.rb +40 -7
  115. data/lib/badline/drives/engine/core.rb +3 -0
  116. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  117. data/lib/badline/drives/model1541/bus.rb +0 -13
  118. data/lib/badline/drives/model1541/disk.rb +9 -8
  119. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  120. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  121. data/lib/badline/drives/model1541.rb +1 -2
  122. data/lib/badline/drives/model1571/bus.rb +0 -13
  123. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  124. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  125. data/lib/badline/drives/model1571.rb +1 -2
  126. data/lib/badline/drives/model1581/bus.rb +0 -13
  127. data/lib/badline/drives/model1581/disk.rb +0 -4
  128. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  129. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  130. data/lib/badline/drives/model1581/slot.rb +0 -18
  131. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  132. data/lib/badline/drives/model1581.rb +2 -2
  133. data/lib/badline/drives.rb +8 -0
  134. data/lib/badline/frontend/app.rb +3 -3
  135. data/lib/badline/frontend/boot.rb +1 -1
  136. data/lib/badline/frontend/controls.rb +19 -3
  137. data/lib/badline/frontend/drive_led.rb +44 -21
  138. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  139. data/lib/badline/frontend/snapshots.rb +12 -1
  140. data/lib/badline/frontend/timeline.rb +28 -10
  141. data/lib/badline/input/matrix_typist.rb +98 -0
  142. data/lib/badline/input.rb +1 -0
  143. data/lib/badline/kernal_trap/chrout.rb +45 -0
  144. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  145. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  146. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  147. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  148. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  149. data/lib/badline/kernal_trap/dos.rb +0 -19
  150. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  151. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  152. data/lib/badline/kernal_trap/layout.rb +75 -11
  153. data/lib/badline/kernal_trap/load.rb +14 -16
  154. data/lib/badline/kernal_trap/routine.rb +12 -0
  155. data/lib/badline/kernal_trap/save.rb +8 -18
  156. data/lib/badline/kernal_trap/serial.rb +1 -17
  157. data/lib/badline/kernal_trap.rb +2 -0
  158. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  159. data/lib/badline/machine/settling.rb +29 -0
  160. data/lib/badline/machine.rb +5 -2
  161. data/lib/badline/media/boot_disk.rb +14 -2
  162. data/lib/badline/media/extensions.rb +10 -0
  163. data/lib/badline/memory.rb +3 -6
  164. data/lib/badline/options/family.rb +5 -2
  165. data/lib/badline/options/help.rb +42 -16
  166. data/lib/badline/options/table.rb +2 -0
  167. data/lib/badline/options/validation.rb +2 -0
  168. data/lib/badline/options.rb +6 -1
  169. data/lib/badline/plus4/acia.rb +45 -0
  170. data/lib/badline/plus4/bus.rb +312 -0
  171. data/lib/badline/plus4/cpu.rb +10 -0
  172. data/lib/badline/plus4/cpu_port.rb +93 -0
  173. data/lib/badline/plus4/keyboard_port.rb +51 -0
  174. data/lib/badline/plus4/model.rb +38 -0
  175. data/lib/badline/plus4/user_port.rb +31 -0
  176. data/lib/badline/plus4.rb +164 -0
  177. data/lib/badline/roms/README +38 -0
  178. data/lib/badline/roms/plus4/basic.rom +0 -0
  179. data/lib/badline/roms/plus4/function-high.rom +0 -0
  180. data/lib/badline/roms/plus4/function-low.rom +0 -0
  181. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  182. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  183. data/lib/badline/snapshot/c128_setup.rb +9 -20
  184. data/lib/badline/snapshot/c64_setup.rb +28 -24
  185. data/lib/badline/snapshot/container.rb +4 -0
  186. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  187. data/lib/badline/snapshot/field_doc.rb +109 -0
  188. data/lib/badline/snapshot/image.rb +134 -28
  189. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  190. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  191. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  192. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  193. data/lib/badline/snapshot/keyed/report.rb +26 -0
  194. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  195. data/lib/badline/snapshot/keyed.rb +64 -0
  196. data/lib/badline/snapshot/machine_state.rb +17 -64
  197. data/lib/badline/snapshot/storage_setup.rb +29 -34
  198. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  199. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  200. data/lib/badline/snapshot/vice.rb +7 -31
  201. data/lib/badline/snapshot.rb +43 -20
  202. data/lib/badline/storage/crt_file.rb +3 -1
  203. data/lib/badline/storage/g64_image.rb +8 -3
  204. data/lib/badline/storage/tap.rb +43 -15
  205. data/lib/badline/version.rb +1 -1
  206. data/lib/badline/vic20/attachments.rb +3 -0
  207. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  208. data/lib/badline/vic20/bus.rb +1 -2
  209. data/lib/badline/vic20/keyed_modules.rb +73 -0
  210. data/lib/badline/vic20/keyed_state.rb +45 -0
  211. data/lib/badline/vic20.rb +5 -5
  212. data/lib/badline.rb +1 -0
  213. metadata +61 -42
  214. data/lib/badline/c128/bus/saved_state.rb +0 -53
  215. data/lib/badline/c128/saved_state.rb +0 -117
  216. data/lib/badline/c64/bus/saved_state.rb +0 -68
  217. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  218. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  219. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  220. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  221. data/lib/badline/c64/reu/saved_state.rb +0 -55
  222. data/lib/badline/c64/saved_state.rb +0 -112
  223. data/lib/badline/chips/cia/saved_state.rb +0 -49
  224. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  225. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  226. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  227. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  228. data/lib/badline/chips/sid/saved_state.rb +0 -52
  229. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  230. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  231. data/lib/badline/chips/via/saved_state.rb +0 -44
  232. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  233. data/lib/badline/chips/vic/saved_state.rb +0 -82
  234. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  235. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  236. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  237. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  238. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  239. data/lib/badline/chips/z80/saved_state.rb +0 -71
  240. data/lib/badline/chrout_trap.rb +0 -43
  241. data/lib/badline/debug_register.rb +0 -28
  242. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  243. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  244. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  245. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  246. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  247. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  248. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  249. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  250. data/lib/badline/keyboard_buffer.rb +0 -33
  251. data/lib/badline/snapshot/setup.rb +0 -31
  252. data/lib/badline/snapshot/state.rb +0 -179
  253. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  254. 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,119 @@
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 single clock cycle in the line, 0-56.
50
+ attr_reader :column
51
+
52
+ # The raster line.
53
+ attr_reader :line
54
+
55
+ # How many cycles the CPU runs in this cycle: 0 while the TED holds
56
+ # the bus for DMA, 1 at single clock and 2 at double clock.
57
+ attr_reader :cpu_slots
58
+
59
+ # Whether BA is low, from 3 cycles before DMA to its end.
60
+ def ba_low? = @ba_low
61
+
62
+ # Lines a frame, by $FF07 bit 6.
63
+ def lines_per_frame = @registers[0x07].anybits?(0x40) ? 262 : 312
64
+
65
+ private
66
+
67
+ def reset_beam
68
+ @column = 0
69
+ @line = 0
70
+ @dma_line = false
71
+ @ba_low = false
72
+ @cpu_slots = 1
73
+ end
74
+
75
+ def move_beam
76
+ column = @column + 1
77
+ column = 0 if column == CYCLES_PER_LINE
78
+ @column = column
79
+ return unless column == LINE_STEP_COLUMN
80
+
81
+ line = @line + 1
82
+ line = 0 if line >= lines_per_frame
83
+ self.line = line
84
+ end
85
+
86
+ def decide_clock
87
+ column = @column
88
+ @dma_line = dma_line? if column == BA_COLUMN
89
+ if @dma_line && (column >= BA_COLUMN || column <= DMA_END_COLUMN)
90
+ @ba_low = true
91
+ @cpu_slots = column >= DMA_COLUMN || column <= DMA_END_COLUMN ? 0 : 1
92
+ return
93
+ end
94
+
95
+ @ba_low = false
96
+ @cpu_slots = single_clock?(column) ? 1 : 2
97
+ end
98
+
99
+ def single_clock?(column)
100
+ return true if REFRESH_COLUMNS.include?(column) || @registers[0x13].anybits?(0x02) || frozen?
101
+
102
+ display_window? && !DOUBLE_COLUMNS.include?(column)
103
+ end
104
+
105
+ def display_window? = @registers[0x06].anybits?(0x10) && @line <= LAST_DISPLAY_LINE
106
+
107
+ def dma_line?
108
+ return false unless @registers[0x06].anybits?(0x10)
109
+
110
+ phase = (@line - (@registers[0x06] & 0x07)) & 0x07
111
+ return false if phase > 1
112
+
113
+ row = phase.zero? ? @line + 1 : @line
114
+ row <= LAST_DISPLAY_LINE
115
+ end
116
+ end
117
+ end
118
+ end
119
+ 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