badline 0.5.1 → 0.6.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +58 -0
  4. data/exe/badline-ruby +2 -1
  5. data/lib/badline/address_bus/saved_state.rb +72 -0
  6. data/lib/badline/address_bus.rb +1 -0
  7. data/lib/badline/audio/stream.rb +6 -0
  8. data/lib/badline/cartridge/action_replay.rb +15 -0
  9. data/lib/badline/cartridge/comal80.rb +8 -0
  10. data/lib/badline/cartridge/dinamic.rb +8 -0
  11. data/lib/badline/cartridge/easy_flash.rb +20 -1
  12. data/lib/badline/cartridge/epyx_fastload.rb +8 -0
  13. data/lib/badline/cartridge/final_cartridge3.rb +12 -0
  14. data/lib/badline/cartridge/flash.rb +41 -0
  15. data/lib/badline/cartridge/fun_play.rb +8 -0
  16. data/lib/badline/cartridge/g_mod2.rb +12 -0
  17. data/lib/badline/cartridge/game_system.rb +8 -0
  18. data/lib/badline/cartridge/geo_ram.rb +16 -0
  19. data/lib/badline/cartridge/magic_desk.rb +8 -0
  20. data/lib/badline/cartridge/ocean.rb +8 -0
  21. data/lib/badline/cartridge/pagefox.rb +12 -0
  22. data/lib/badline/cartridge/retro_replay.rb +44 -1
  23. data/lib/badline/cartridge/rgcd.rb +10 -0
  24. data/lib/badline/cartridge/super_games.rb +10 -0
  25. data/lib/badline/cartridge/zaxxon.rb +16 -0
  26. data/lib/badline/cartridge.rb +72 -0
  27. data/lib/badline/cia/interrupt_register.rb +17 -0
  28. data/lib/badline/cia/serial.rb +44 -0
  29. data/lib/badline/cia/state.rb +47 -0
  30. data/lib/badline/cia/timer.rb +33 -0
  31. data/lib/badline/cia.rb +8 -0
  32. data/lib/badline/computer/attachments.rb +132 -0
  33. data/lib/badline/computer/saved_state.rb +116 -0
  34. data/lib/badline/computer.rb +7 -30
  35. data/lib/badline/cpu/microcode.rb +8 -0
  36. data/lib/badline/cpu/saved_state.rb +115 -0
  37. data/lib/badline/cpu.rb +2 -0
  38. data/lib/badline/datasette.rb +27 -0
  39. data/lib/badline/drive1541/bus.rb +13 -0
  40. data/lib/badline/drive1541/disk.rb +7 -2
  41. data/lib/badline/drive1541/disk_state.rb +80 -0
  42. data/lib/badline/drive1541/mechanism.rb +2 -0
  43. data/lib/badline/drive1541/mechanism_state.rb +67 -0
  44. data/lib/badline/drive1541/rotation.rb +10 -0
  45. data/lib/badline/drive1541/serial_port.rb +10 -0
  46. data/lib/badline/drive1541.rb +34 -0
  47. data/lib/badline/gui/application.rb +41 -14
  48. data/lib/badline/gui/gamepads.rb +7 -0
  49. data/lib/badline/gui/snapshots.rb +67 -0
  50. data/lib/badline/gui.rb +1 -0
  51. data/lib/badline/kernal_trap/channel.rb +27 -0
  52. data/lib/badline/kernal_trap/drive/channels.rb +25 -0
  53. data/lib/badline/kernal_trap/drive/memory.rb +9 -0
  54. data/lib/badline/kernal_trap/drive/status.rb +8 -0
  55. data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
  56. data/lib/badline/kernal_trap/drive.rb +19 -0
  57. data/lib/badline/kernal_trap/save.rb +8 -0
  58. data/lib/badline/kernal_trap/serial.rb +16 -0
  59. data/lib/badline/memory.rb +8 -0
  60. data/lib/badline/options.rb +3 -2
  61. data/lib/badline/ram_expansion/plus256k.rb +16 -0
  62. data/lib/badline/ram_expansion/plus60k.rb +12 -0
  63. data/lib/badline/ram_expansion/unexpanded.rb +6 -0
  64. data/lib/badline/reu/dma.rb +36 -0
  65. data/lib/badline/reu/ram.rb +15 -0
  66. data/lib/badline/reu/saved_state.rb +52 -0
  67. data/lib/badline/reu.rb +4 -0
  68. data/lib/badline/sdl/events.rb +2 -0
  69. data/lib/badline/sid/envelope/state.rb +68 -0
  70. data/lib/badline/sid/envelope.rb +7 -0
  71. data/lib/badline/sid/filter.rb +38 -0
  72. data/lib/badline/sid/voice.rb +10 -0
  73. data/lib/badline/sid/waveform/state.rb +66 -0
  74. data/lib/badline/sid/waveform.rb +2 -0
  75. data/lib/badline/sid.rb +34 -0
  76. data/lib/badline/snapshot/container.rb +117 -0
  77. data/lib/badline/snapshot/fields.rb +54 -0
  78. data/lib/badline/snapshot/image.rb +70 -0
  79. data/lib/badline/snapshot/machine_state.rb +77 -0
  80. data/lib/badline/snapshot/setup.rb +58 -0
  81. data/lib/badline/snapshot/state.rb +178 -0
  82. data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
  83. data/lib/badline/snapshot/vice/cias.rb +89 -0
  84. data/lib/badline/snapshot/vice/cias_import.rb +70 -0
  85. data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
  86. data/lib/badline/snapshot/vice/peripherals.rb +47 -0
  87. data/lib/badline/snapshot/vice/restore.rb +125 -0
  88. data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
  89. data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
  90. data/lib/badline/snapshot/vice/vicii.rb +119 -0
  91. data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
  92. data/lib/badline/snapshot/vice.rb +74 -0
  93. data/lib/badline/snapshot.rb +52 -0
  94. data/lib/badline/storage/crt_file.rb +9 -3
  95. data/lib/badline/storage/d64_image.rb +2 -0
  96. data/lib/badline/storage/d71_image.rb +2 -0
  97. data/lib/badline/storage/d81_image.rb +2 -0
  98. data/lib/badline/storage/disk_image.rb +11 -2
  99. data/lib/badline/storage/host_directory.rb +5 -0
  100. data/lib/badline/storage/t64.rb +6 -0
  101. data/lib/badline/storage/tap.rb +10 -1
  102. data/lib/badline/storage/unavailable.rb +20 -0
  103. data/lib/badline/storage.rb +34 -0
  104. data/lib/badline/time_of_day.rb +56 -0
  105. data/lib/badline/version.rb +1 -1
  106. data/lib/badline/via/control_lines.rb +15 -0
  107. data/lib/badline/via/interrupt_register.rb +9 -0
  108. data/lib/badline/via/shift_register.rb +13 -0
  109. data/lib/badline/via/state.rb +42 -0
  110. data/lib/badline/via/timer1.rb +13 -0
  111. data/lib/badline/via/timer2.rb +18 -0
  112. data/lib/badline/via.rb +2 -0
  113. data/lib/badline/vic/border_mask.rb +10 -0
  114. data/lib/badline/vic/collisions.rb +11 -0
  115. data/lib/badline/vic/color_patches.rb +11 -0
  116. data/lib/badline/vic/display_state.rb +29 -1
  117. data/lib/badline/vic/graphics_shifter.rb +26 -0
  118. data/lib/badline/vic/register_log.rb +18 -0
  119. data/lib/badline/vic/registers.rb +13 -2
  120. data/lib/badline/vic/sequencer.rb +12 -0
  121. data/lib/badline/vic/sequencer_state.rb +89 -0
  122. data/lib/badline/vic/sprite/internal_bus.rb +11 -0
  123. data/lib/badline/vic/sprite/saved_state.rb +64 -0
  124. data/lib/badline/vic/sprite.rb +2 -0
  125. data/lib/badline/vic/sprites.rb +3 -0
  126. data/lib/badline/vic/sprites_state.rb +57 -0
  127. data/lib/badline/vic.rb +94 -10
  128. data/lib/badline.rb +1 -0
  129. metadata +35 -1
@@ -80,6 +80,33 @@ module Badline
80
80
  data ? new(data) : new([], error: [Drive::ILLEGAL_TRACK_OR_SECTOR, track, sector])
81
81
  end
82
82
 
83
+ # Reads what save_state wrote after the flag that marks a file open
84
+ # for writing (Drive::Channels.channel_from_state). A buffer channel
85
+ # takes its block in `ram`, the drive's RAM, again.
86
+ def self.from_state(input, ram)
87
+ new.tap { |channel| channel.load_state(input, ram) }
88
+ end
89
+
90
+ # A buffer channel's bytes are the drive's RAM, which the drive
91
+ # writes itself.
92
+ def save_state(out)
93
+ out.boolean(false).boolean(@writable).int(@base).int(@length).int(@pointer)
94
+ out.optional_int(@lead).optional_int(@buffer).boolean(!@error.nil?)
95
+ out.ints(@error) if @error
96
+ out.blob(@bytes) unless @writable
97
+ end
98
+
99
+ def load_state(input, ram)
100
+ @writable = input.boolean?
101
+ @base = input.int
102
+ @length = input.int
103
+ @pointer = input.int
104
+ @lead = input.optional_int
105
+ @buffer = input.optional_int
106
+ @error = input.boolean? ? input.ints : nil
107
+ @bytes = @writable ? ram : input.blob
108
+ end
109
+
83
110
  def replace(bytes, length = bytes.length)
84
111
  @bytes = bytes
85
112
  rewind(length)
@@ -22,6 +22,31 @@ module Badline
22
22
 
23
23
  def clear = @channels.clear
24
24
 
25
+ # Each open channel with its secondary address, in the order they
26
+ # opened.
27
+ def save_state(out)
28
+ out.int(@channels.length)
29
+ @channels.each do |secondary, channel|
30
+ out.int(secondary)
31
+ channel.save_state(out)
32
+ end
33
+ end
34
+
35
+ # Buffer channels take their block in the drive's RAM again.
36
+ def load_state(input, memory)
37
+ @channels.clear
38
+ input.int.times do
39
+ secondary = input.int
40
+ @channels[secondary] = Channels.channel_from_state(input, memory.ram)
41
+ end
42
+ end
43
+
44
+ # A channel save_state wrote: a file open for writing, or a Channel
45
+ # whose buffer, if it has one, is in `ram`.
46
+ def self.channel_from_state(input, ram)
47
+ input.boolean? ? WriteFile.from_state(input) : Channel.from_state(input, ram)
48
+ end
49
+
25
50
  # Opens a buffer channel on the buffer a "#n" name asks for, or on
26
51
  # the highest free one. Returns nil when that buffer isn't free,
27
52
  # which leaves a channel asking again for its own buffer open.
@@ -62,6 +62,15 @@ module Badline
62
62
  load_buffer(Channels::BAM_BUFFER, block) if block
63
63
  end
64
64
 
65
+ def save_state(out)
66
+ out.blob(@ram).boolean(@bam)
67
+ end
68
+
69
+ def load_state(input)
70
+ input.blob_into(@ram)
71
+ @bam = input.boolean?
72
+ end
73
+
65
74
  # Whether the BAM has been read since the last reset.
66
75
  def bam? = @bam
67
76
 
@@ -37,6 +37,14 @@ module Badline
37
37
  nil
38
38
  end
39
39
 
40
+ def save_state(out)
41
+ @channel.save_state(out)
42
+ end
43
+
44
+ def load_state(input)
45
+ @channel = Channels.channel_from_state(input, [])
46
+ end
47
+
40
48
  def replace(bytes)
41
49
  @channel.replace(bytes)
42
50
  end
@@ -11,6 +11,17 @@ module Badline
11
11
  attr_accessor :buffer
12
12
  attr_reader :name, :type, :mode
13
13
 
14
+ TYPES = %i[del seq prg usr rel].freeze
15
+ MODES = %i[write replace append].freeze
16
+
17
+ # Reads what save_state wrote after the flag that marks a file
18
+ # open for writing.
19
+ def self.from_state(input)
20
+ name = input.string
21
+ type = TYPES.fetch(input.int).to_sym
22
+ new(name, type, mode: MODES.fetch(input.int).to_sym).tap { |file| file.load_state(input) }
23
+ end
24
+
14
25
  def initialize(name, type, mode: :write)
15
26
  @name = name
16
27
  @type = type
@@ -18,6 +29,16 @@ module Badline
18
29
  @bytes = []
19
30
  end
20
31
 
32
+ def save_state(out)
33
+ out.boolean(true).string(@name).int(TYPES.index(@type)).int(MODES.index(@mode))
34
+ out.blob(@bytes).optional_int(@buffer)
35
+ end
36
+
37
+ def load_state(input)
38
+ @bytes = input.blob
39
+ @buffer = input.optional_int
40
+ end
41
+
21
42
  def write(bytes) = @bytes.concat(bytes)
22
43
 
23
44
  def writable? = true
@@ -143,6 +143,25 @@ module Badline
143
143
  # vector, as U: does.
144
144
  def reset! = reset(cold: true)
145
145
 
146
+ # The mounted storage's setup, for Storage.reopen, then the drive's
147
+ # RAM, its status and its open channels.
148
+ def save_state(out)
149
+ @storage.save_setup(out)
150
+ out.marker("TRAP DRIVE")
151
+ @memory.save_state(out)
152
+ @status.save_state(out)
153
+ @channels.save_state(out)
154
+ end
155
+
156
+ # Takes the state after the storage's setup, into a drive mounted on
157
+ # the reopened storage.
158
+ def load_state(input)
159
+ input.marker("TRAP DRIVE")
160
+ @memory.load_state(input)
161
+ @status.load_state(input)
162
+ @channels.load_state(input, @memory)
163
+ end
164
+
146
165
  # Returns the next byte and whether it is the channel's last, or nil
147
166
  # when there is nothing left to send.
148
167
  def read(secondary)
@@ -37,6 +37,14 @@ module Badline
37
37
  @saving = false
38
38
  end
39
39
 
40
+ def save_state(out)
41
+ out.boolean(@saving)
42
+ end
43
+
44
+ def load_state(input)
45
+ @saving = input.boolean?
46
+ end
47
+
40
48
  def call
41
49
  return unless active?
42
50
  return finish if @saving
@@ -46,6 +46,22 @@ module Badline
46
46
  @buffer = []
47
47
  end
48
48
 
49
+ # The frame under way, with the channels and the bytes it gathered.
50
+ # The device it answers is the machine's to set.
51
+ def save_state(out)
52
+ out.boolean(@listening).boolean(@talking).optional_int(@listen_channel).optional_int(@talk_channel)
53
+ out.int(@frame).blob(@buffer)
54
+ end
55
+
56
+ def load_state(input)
57
+ @listening = input.boolean?
58
+ @talking = input.boolean?
59
+ @listen_channel = input.optional_int
60
+ @talk_channel = input.optional_int
61
+ @frame = input.int
62
+ @buffer = input.blob
63
+ end
64
+
49
65
  def install
50
66
  ROUTINES.each do |address, routine|
51
67
  @cpu.install_trap(address) { call_routine(routine) if kernal? }
@@ -29,6 +29,14 @@ module Badline
29
29
  Array(bytes).each_with_index { |b, i| poke(addr + i, b) }
30
30
  end
31
31
 
32
+ def save_state(out)
33
+ out.blob(@storage)
34
+ end
35
+
36
+ def load_state(input)
37
+ input.blob_into(@storage)
38
+ end
39
+
32
40
  # Refills the whole memory as if newly built with `initial`
33
41
  def clear!(initial = [])
34
42
  @storage.replace(zero_fill(initial))
@@ -23,8 +23,9 @@ module Badline
23
23
 
24
24
  Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
25
25
  .g64 disk image for the true 1541, a .t64 tape archive, a .tap
26
- tape, a .crt cartridge, a .sid tune, or a directory to mount as
27
- device 8. It opens in the emulator window. Device 8 answers through
26
+ tape, a .crt cartridge, a .sid tune, a .vsf snapshot, or a directory
27
+ to mount as device 8. It opens in the emulator window, where F11
28
+ saves a snapshot and F12 restores it. Device 8 answers through
28
29
  traps on the KERNAL's disk routines, unless --true-drive puts an
29
30
  emulated 1541 there, which runs its own DOS and reads .d64 and .g64
30
31
  images only.
@@ -30,6 +30,22 @@ module Badline
30
30
  @locked = false
31
31
  end
32
32
 
33
+ def type = :plus256k
34
+
35
+ def save_state(out)
36
+ [@low_ram, @video_ram, @high_ram].each { |bank| out.int(@banks.index(bank)) }
37
+ out.boolean(@locked)
38
+ @banks.drop(1).each { |bank| bank.save_state(out) }
39
+ end
40
+
41
+ def load_state(input)
42
+ @low_ram = @banks.fetch(input.int)
43
+ @video_ram = @banks.fetch(input.int)
44
+ @high_ram = @banks.fetch(input.int)
45
+ @locked = input.boolean?
46
+ @banks.drop(1).each { |bank| bank.load_state(input) }
47
+ end
48
+
33
49
  def power_on!
34
50
  @banks.drop(1).each(&:clear!)
35
51
  end
@@ -23,6 +23,18 @@ module Badline
23
23
  @high_ram = @banks[0]
24
24
  end
25
25
 
26
+ def type = :plus60k
27
+
28
+ def save_state(out)
29
+ out.int(@banks.index(@high_ram))
30
+ @banks[1].save_state(out)
31
+ end
32
+
33
+ def load_state(input)
34
+ @high_ram = @banks.fetch(input.int)
35
+ @banks[1].load_state(input)
36
+ end
37
+
26
38
  def power_on!
27
39
  @banks[1].clear!
28
40
  end
@@ -12,8 +12,14 @@ module Badline
12
12
  @on_change = nil
13
13
  end
14
14
 
15
+ def type = nil
16
+
15
17
  def map_io(_read_pages, _write_pages); end
16
18
 
19
+ def save_state(_out); end
20
+
21
+ def load_state(_input); end
22
+
17
23
  def reset!; end
18
24
 
19
25
  def power_on!; end
@@ -69,8 +69,44 @@ module Badline
69
69
  @moved_all ? wind_down : move(ba_low)
70
70
  end
71
71
 
72
+ # Everything a transfer holds between cycles. Before the first
73
+ # transfer, the fields it sets on starting are empty.
74
+ def save_state(out)
75
+ out.int(@c64).int(@expansion).int(@length).int(@events)
76
+ [@type, @c64_step, @expansion_step, @last, @ba_low_after_writes, @swap_byte, @held_read].each do |value|
77
+ out.optional_int(value)
78
+ end
79
+ [@holding, @waiting, @stopped_after_write, @swap_write_due, @moved_all, @extra_cycle].each do |flag|
80
+ out.boolean(flag)
81
+ end
82
+ end
83
+
84
+ def load_state(input)
85
+ @c64 = input.int
86
+ @expansion = input.int
87
+ @length = input.int
88
+ @events = input.int
89
+ load_steps(input)
90
+ @holding = input.boolean?
91
+ @waiting = input.boolean?
92
+ @stopped_after_write = input.boolean?
93
+ @swap_write_due = input.boolean?
94
+ @moved_all = input.boolean?
95
+ @extra_cycle = input.boolean?
96
+ end
97
+
72
98
  private
73
99
 
100
+ def load_steps(input)
101
+ @type = input.optional_int
102
+ @c64_step = input.optional_int
103
+ @expansion_step = input.optional_int
104
+ @last = input.optional_int
105
+ @ba_low_after_writes = input.optional_int
106
+ @swap_byte = input.optional_int
107
+ @held_read = input.optional_int
108
+ end
109
+
74
110
  # The second BA-low cycle in a row after writes stops the REC, and so
75
111
  # does any BA-low cycle after a read. It then waits for BA to rise.
76
112
  def note_ba(ba_low)
@@ -37,6 +37,21 @@ module Badline
37
37
  bank(addr >> 16)[addr & 0xffff] = value if addr < @size
38
38
  end
39
39
 
40
+ # The latch, and each 64K bank the REC has touched. A bank it hasn't
41
+ # keeps its power-on contents until it does.
42
+ def save_state(out)
43
+ out.int(@latch)
44
+ @banks.each do |bank|
45
+ out.boolean(!bank.empty?)
46
+ out.blob(bank) unless bank.empty?
47
+ end
48
+ end
49
+
50
+ def load_state(input)
51
+ @latch = input.int
52
+ @banks.each_index { |number| @banks[number] = input.boolean? ? input.blob : [] }
53
+ end
54
+
40
55
  private
41
56
 
42
57
  # A bank takes its power-on contents the first time it is touched,
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class REU
5
+ # The REU's state for a snapshot: the REC's registers and the values
6
+ # its counters start from, an armed or running transfer, the IRQ line,
7
+ # the transfer's own state and the RAM. The size is the machine's
8
+ # setup (Snapshot::Setup), so a restore finds an REU of the same size.
9
+ module SavedState
10
+ def size_kb = @size_kb
11
+
12
+ def save_state(out)
13
+ out.marker("REU")
14
+ [@status, @command, @c64, @c64_start, @expansion, @expansion_start, @bank, @bank_start, @length,
15
+ @length_start, @interrupt_mask, @address_control].each { |value| out.int(value) }
16
+ out.boolean(@armed).boolean(@requested).boolean(@running).boolean(@irq)
17
+ @dma.save_state(out)
18
+ @ram.save_state(out)
19
+ end
20
+
21
+ # The IRQ line comes back as it was without calling back into the
22
+ # machine, which reads it again afterwards.
23
+ def load_state(input)
24
+ input.marker("REU")
25
+ load_registers(input)
26
+ @armed = input.boolean?
27
+ @requested = input.boolean?
28
+ @running = input.boolean?
29
+ @irq = input.boolean?
30
+ @dma.load_state(input)
31
+ @ram.load_state(input)
32
+ end
33
+
34
+ private
35
+
36
+ def load_registers(input)
37
+ @status = input.int
38
+ @command = input.int
39
+ @c64 = input.int
40
+ @c64_start = input.int
41
+ @expansion = input.int
42
+ @expansion_start = input.int
43
+ @bank = input.int
44
+ @bank_start = input.int
45
+ @length = input.int
46
+ @length_start = input.int
47
+ @interrupt_mask = input.int
48
+ @address_control = input.int
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/badline/reu.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "badline/reu/ram"
4
4
  require "badline/reu/dma"
5
5
  require "badline/reu/trigger"
6
+ require "badline/reu/saved_state"
6
7
 
7
8
  module Badline
8
9
  # A RAM Expansion Unit: the Commodore 1700 (128K), 1764 (256K) and 1750
@@ -18,6 +19,8 @@ module Badline
18
19
  # waits for the CPU's next read cycle with BA high, and Computer then
19
20
  # clocks it through #dma_cycle! until it hands the bus back.
20
21
  class REU
22
+ include SavedState
23
+
21
24
  SIZES_KB = [128, 256, 512, 1024, 2048, 4096, 8192, 16_384].freeze
22
25
 
23
26
  # The registers, by offset.
@@ -74,6 +77,7 @@ module Badline
74
77
 
75
78
  # The 1700's REC counts 17 address bits, the others' 19.
76
79
  span = size_kb == 128 ? 0x20000 : 0x80000
80
+ @size_kb = size_kb
77
81
  size = size_kb * 1024
78
82
  @address_mask = [size, span].max - 1
79
83
  @bank_bits = size > span ? 0xff : 0x07
@@ -4,6 +4,8 @@ module Badline
4
4
  module SDL
5
5
  KEY_TAB = 0x09
6
6
  KEY_F10 = 0x4000_0043
7
+ KEY_F11 = 0x4000_0044
8
+ KEY_F12 = 0x4000_0045
7
9
  KMOD_SHIFT = 0x0003
8
10
 
9
11
  # The events the front end handles. SDL_Event is a 56-byte union, read
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class SID
5
+ class Envelope
6
+ # A snapshot of an envelope: its rates, the gate, the state and the
7
+ # counters and pipelines between them, and the same fields as reSID
8
+ # keeps them, for VICE's SIDEXTENDED module.
9
+ module State
10
+ # The fields reSID keeps for an envelope between cycles: the rate and
11
+ # exponential counters, the level, the state as an index into STATES,
12
+ # whether it holds at zero, the two periods and the pipeline.
13
+ def resid_fields
14
+ [@rate_counter, @exponential_counter, @counter, STATES.index(@state).to_i, @hold_zero ? 1 : 0,
15
+ @rate_period, @exponential_period, @envelope_pipeline]
16
+ end
17
+
18
+ # Takes the fields resid_fields gives from `fields`, from `at` on.
19
+ def restore_resid_fields(fields, at)
20
+ @rate_counter = fields[at]
21
+ @exponential_counter = fields[at + 1]
22
+ @counter = @env3 = fields[at + 2]
23
+ state = fields[at + 3]
24
+ @state = @next_state = STATES.fetch(state.clamp(0, STATES.length - 1)).to_sym
25
+ @hold_zero = fields[at + 4] != 0
26
+ @rate_period = fields[at + 5]
27
+ @exponential_period = fields[at + 6]
28
+ @envelope_pipeline = fields[at + 7]
29
+ end
30
+
31
+ def save_state(out)
32
+ [@attack, @decay, @sustain, @release].each { |rate| out.int(rate) }
33
+ out.boolean(@gate).int(STATES.index(@state)).int(STATES.index(@next_state))
34
+ out.int(@counter).int(@env3).int(@rate_counter).int(@rate_period).boolean(@reset_rate_counter)
35
+ out.int(@exponential_counter).int(@exponential_period)
36
+ out.int(@state_pipeline).int(@envelope_pipeline).int(@exponential_pipeline).boolean(@hold_zero)
37
+ end
38
+
39
+ def load_state(input)
40
+ @attack = input.int
41
+ @decay = input.int
42
+ @sustain = input.int
43
+ @release = input.int
44
+ @gate = input.boolean?
45
+ @state = STATES.fetch(input.int).to_sym
46
+ @next_state = STATES.fetch(input.int).to_sym
47
+ load_counters(input)
48
+ end
49
+
50
+ private
51
+
52
+ def load_counters(input)
53
+ @counter = input.int
54
+ @env3 = input.int
55
+ @rate_counter = input.int
56
+ @rate_period = input.int
57
+ @reset_rate_counter = input.boolean?
58
+ @exponential_counter = input.int
59
+ @exponential_period = input.int
60
+ @state_pipeline = input.int
61
+ @envelope_pipeline = input.int
62
+ @exponential_pipeline = input.int
63
+ @hold_zero = input.boolean?
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/sid/envelope/state"
4
+
3
5
  module Badline
4
6
  class SID
5
7
  # ADSR envelope generator.
@@ -25,6 +27,11 @@ module Badline
25
27
  EXPONENTIAL_PERIODS = { 0xff => 1, 0x5d => 2, 0x36 => 4, 0x1a => 8,
26
28
  0x0e => 16, 0x06 => 30, 0x00 => 1 }.freeze
27
29
 
30
+ # The states a snapshot names by index.
31
+ STATES = %i[attack decay_sustain release].freeze
32
+
33
+ include State
34
+
28
35
  attr_reader :state, :counter, :env3
29
36
 
30
37
  def initialize
@@ -90,6 +90,16 @@ module Badline
90
90
  @output = 0
91
91
  end
92
92
 
93
+ def save_state(out)
94
+ out.int(@lowpass).int(@highpass).int(@output)
95
+ end
96
+
97
+ def load_state(input)
98
+ @lowpass = input.int
99
+ @highpass = input.int
100
+ @output = input.int
101
+ end
102
+
93
103
  # Takes the input in 1/2^Filter::FRACTION units, and reads the
94
104
  # output out in whole ones.
95
105
  def cycle!(input, cycles = 1)
@@ -124,6 +134,25 @@ module Badline
124
134
  @input = @unfiltered = 0
125
135
  end
126
136
 
137
+ # The registers as the filter decoded them and its integrators. The
138
+ # W0 table is the model's.
139
+ def save_state(out)
140
+ out.int(@cutoff).int(@routing).int(@mode).int(@volume).boolean(@voice3_off).int(@w0).int(@resonance)
141
+ [@highpass, @bandpass, @lowpass, @unfiltered, @input].each { |value| out.int(value) }
142
+ @external.save_state(out)
143
+ end
144
+
145
+ def load_state(input)
146
+ @cutoff = input.int
147
+ @routing = input.int
148
+ @mode = input.int
149
+ @volume = input.int
150
+ @voice3_off = input.boolean?
151
+ @w0 = input.int
152
+ @resonance = input.int
153
+ load_integrators(input)
154
+ end
155
+
127
156
  def write(reg, value)
128
157
  case reg
129
158
  when 0x15 then self.cutoff = (@cutoff & 0x7f8) | (value & 0x07)
@@ -163,6 +192,15 @@ module Badline
163
192
 
164
193
  private
165
194
 
195
+ def load_integrators(input)
196
+ @highpass = input.int
197
+ @bandpass = input.int
198
+ @lowpass = input.int
199
+ @unfiltered = input.int
200
+ @input = input.int
201
+ @external.load_state(input)
202
+ end
203
+
166
204
  def write_resonance(value)
167
205
  @routing = value & 0x0f
168
206
  @resonance = RESONANCE[(value >> 4) & 0x0f]
@@ -38,6 +38,16 @@ module Badline
38
38
  @envelope = Envelope.new
39
39
  end
40
40
 
41
+ def save_state(out)
42
+ @waveform.save_state(out)
43
+ @envelope.save_state(out)
44
+ end
45
+
46
+ def load_state(input)
47
+ @waveform.load_state(input)
48
+ @envelope.load_state(input)
49
+ end
50
+
41
51
  def control=(value)
42
52
  @waveform.control = value
43
53
  @envelope.control = value
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class SID
5
+ class Waveform
6
+ # A snapshot of an oscillator: its phase, the noise register and the
7
+ # output latch, and the same fields as reSID keeps them, for VICE's
8
+ # SIDEXTENDED module. The model's tables and the voices it syncs with
9
+ # are the SID's wiring.
10
+ module State
11
+ # The fields reSID keeps for an oscillator between cycles: the phase,
12
+ # the noise register, its shift pipeline and reset countdown, the
13
+ # floating output's countdown and the pulse level.
14
+ def resid_fields
15
+ [@accumulator, @shift_register, @shift_pipeline, @shift_register_reset, @floating_ttl, @pulse]
16
+ end
17
+
18
+ # Takes the fields resid_fields gives from `fields`, from `at` on.
19
+ def restore_resid_fields(fields, at)
20
+ @accumulator = fields[at]
21
+ @shift_register = fields[at + 1]
22
+ @shift_pipeline = fields[at + 2]
23
+ @shift_register_reset = fields[at + 3]
24
+ @floating_ttl = fields[at + 4]
25
+ @pulse = fields[at + 5]
26
+ @stale = true
27
+ end
28
+
29
+ def save_state(out)
30
+ out.int(@accumulator).int(@delayed_triangle).int(@delayed_sawtooth)
31
+ out.int(@shift_register).int(@shift_register_reset).int(@shift_pipeline)
32
+ out.int(@frequency).int(@pulse_width).int(@selected)
33
+ [@test, @ring, @sync, @msb_rising].each { |flag| out.boolean(flag) }
34
+ out.int(@floating).int(@floating_ttl).int(@pulse).int(@output).boolean(@stale)
35
+ end
36
+
37
+ def load_state(input)
38
+ @accumulator = input.int
39
+ @delayed_triangle = input.int
40
+ @delayed_sawtooth = input.int
41
+ @shift_register = input.int
42
+ @shift_register_reset = input.int
43
+ @shift_pipeline = input.int
44
+ @frequency = input.int
45
+ @pulse_width = input.int
46
+ @selected = input.int
47
+ load_latches(input)
48
+ end
49
+
50
+ private
51
+
52
+ def load_latches(input)
53
+ @test = input.boolean?
54
+ @ring = input.boolean?
55
+ @sync = input.boolean?
56
+ @msb_rising = input.boolean?
57
+ @floating = input.int
58
+ @floating_ttl = input.int
59
+ @pulse = input.int
60
+ @output = input.int
61
+ @stale = input.boolean?
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end