badline 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/CLAUDE.md +10 -9
- data/README.md +82 -10
- data/doc/pinned-behaviour.md +131 -30
- data/exe/badline-ruby +20 -23
- data/lib/badline/address_bus/saved_state.rb +72 -0
- data/lib/badline/address_bus.rb +11 -1
- data/lib/badline/audio/cli.rb +18 -5
- data/lib/badline/audio/console.rb +2 -1
- data/lib/badline/audio.rb +0 -3
- data/lib/badline/cartridge/action_replay.rb +15 -0
- data/lib/badline/cartridge/comal80.rb +8 -0
- data/lib/badline/cartridge/dinamic.rb +8 -0
- data/lib/badline/cartridge/easy_flash.rb +20 -1
- data/lib/badline/cartridge/epyx_fastload.rb +8 -0
- data/lib/badline/cartridge/final_cartridge3.rb +12 -0
- data/lib/badline/cartridge/flash.rb +41 -0
- data/lib/badline/cartridge/fun_play.rb +8 -0
- data/lib/badline/cartridge/g_mod2.rb +12 -0
- data/lib/badline/cartridge/game_system.rb +8 -0
- data/lib/badline/cartridge/geo_ram.rb +16 -0
- data/lib/badline/cartridge/magic_desk.rb +8 -0
- data/lib/badline/cartridge/ocean.rb +8 -0
- data/lib/badline/cartridge/pagefox.rb +12 -0
- data/lib/badline/cartridge/retro_replay.rb +44 -1
- data/lib/badline/cartridge/rgcd.rb +10 -0
- data/lib/badline/cartridge/super_games.rb +10 -0
- data/lib/badline/cartridge/zaxxon.rb +16 -0
- data/lib/badline/cartridge.rb +72 -0
- data/lib/badline/cia/interrupt_register.rb +17 -0
- data/lib/badline/cia/serial.rb +44 -0
- data/lib/badline/cia/state.rb +47 -0
- data/lib/badline/cia/timer.rb +33 -0
- data/lib/badline/cia.rb +8 -0
- data/lib/badline/computer/attachments.rb +132 -0
- data/lib/badline/computer/saved_state.rb +116 -0
- data/lib/badline/computer.rb +13 -33
- data/lib/badline/cpu/boundary.rb +54 -0
- data/lib/badline/cpu/core.rb +165 -0
- data/lib/badline/cpu/microcode.rb +8 -0
- data/lib/badline/cpu/saved_state.rb +115 -0
- data/lib/badline/cpu.rb +3 -212
- data/lib/badline/datasette.rb +27 -0
- data/lib/badline/drive1541/bus.rb +13 -0
- data/lib/badline/drive1541/cpu.rb +29 -0
- data/lib/badline/drive1541/disk.rb +7 -2
- data/lib/badline/drive1541/disk_state.rb +80 -0
- data/lib/badline/drive1541/mechanism.rb +2 -0
- data/lib/badline/drive1541/mechanism_state.rb +67 -0
- data/lib/badline/drive1541/rotation.rb +10 -0
- data/lib/badline/drive1541/serial_port.rb +10 -0
- data/lib/badline/drive1541.rb +35 -0
- data/lib/badline/ffi.rb +157 -0
- data/lib/badline/frontend/app.rb +225 -0
- data/lib/badline/frontend/audio_sink.rb +72 -0
- data/lib/badline/frontend/boot.rb +52 -0
- data/lib/badline/frontend/controls.rb +163 -0
- data/lib/badline/frontend/drive_led.rb +51 -0
- data/lib/badline/frontend/frame_rate.rb +83 -0
- data/lib/badline/frontend/gamepads.rb +76 -0
- data/lib/badline/frontend/keys.rb +48 -0
- data/lib/badline/frontend/pacer.rb +100 -0
- data/lib/badline/frontend/pad_port.rb +61 -0
- data/lib/badline/frontend/screen.rb +65 -0
- data/lib/badline/frontend/screenshot.rb +22 -0
- data/lib/badline/frontend/snapshots.rb +60 -0
- data/lib/badline/frontend/sound.rb +153 -0
- data/lib/badline/frontend/timeline.rb +171 -0
- data/lib/badline/frontend.rb +18 -0
- data/lib/badline/kernal_trap/channel.rb +27 -0
- data/lib/badline/kernal_trap/drive/channels.rb +25 -0
- data/lib/badline/kernal_trap/drive/memory.rb +9 -0
- data/lib/badline/kernal_trap/drive/status.rb +8 -0
- data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
- data/lib/badline/kernal_trap/drive.rb +19 -0
- data/lib/badline/kernal_trap/save.rb +8 -0
- data/lib/badline/kernal_trap/serial.rb +16 -0
- data/lib/badline/memory.rb +8 -0
- data/lib/badline/options/event.rb +94 -0
- data/lib/badline/options/help.rb +78 -0
- data/lib/badline/options/option.rb +29 -0
- data/lib/badline/options/table.rb +62 -0
- data/lib/badline/options.rb +181 -144
- data/lib/badline/ram_expansion/plus256k.rb +16 -0
- data/lib/badline/ram_expansion/plus60k.rb +12 -0
- data/lib/badline/ram_expansion/unexpanded.rb +6 -0
- data/lib/badline/reu/dma/saved_state.rb +50 -0
- data/lib/badline/reu/dma.rb +56 -11
- data/lib/badline/reu/ram.rb +15 -0
- data/lib/badline/reu/saved_state.rb +53 -0
- data/lib/badline/reu.rb +23 -10
- data/lib/badline/sdl.rb +123 -51
- data/lib/badline/sid/envelope/state.rb +68 -0
- data/lib/badline/sid/envelope.rb +7 -0
- data/lib/badline/sid/filter.rb +38 -0
- data/lib/badline/sid/voice.rb +10 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
- data/lib/badline/sid/waveform/state.rb +66 -0
- data/lib/badline/sid/waveform.rb +4 -3
- data/lib/badline/sid.rb +34 -0
- data/lib/badline/snapshot/container.rb +117 -0
- data/lib/badline/snapshot/fields.rb +54 -0
- data/lib/badline/snapshot/image.rb +70 -0
- data/lib/badline/snapshot/machine_state.rb +77 -0
- data/lib/badline/snapshot/setup.rb +58 -0
- data/lib/badline/snapshot/state.rb +178 -0
- data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
- data/lib/badline/snapshot/vice/cias.rb +89 -0
- data/lib/badline/snapshot/vice/cias_import.rb +70 -0
- data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
- data/lib/badline/snapshot/vice/peripherals.rb +47 -0
- data/lib/badline/snapshot/vice/restore.rb +125 -0
- data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
- data/lib/badline/snapshot/vice/vicii.rb +119 -0
- data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
- data/lib/badline/snapshot/vice.rb +74 -0
- data/lib/badline/snapshot.rb +52 -0
- data/lib/badline/storage/crt_file.rb +9 -3
- data/lib/badline/storage/d64_image.rb +2 -0
- data/lib/badline/storage/d71_image.rb +2 -0
- data/lib/badline/storage/d81_image.rb +2 -0
- data/lib/badline/storage/disk_image.rb +11 -2
- data/lib/badline/storage/host_directory.rb +5 -0
- data/lib/badline/storage/t64.rb +6 -0
- data/lib/badline/storage/tap.rb +10 -1
- data/lib/badline/storage/unavailable.rb +20 -0
- data/lib/badline/storage.rb +34 -0
- data/lib/badline/time_of_day.rb +56 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +15 -0
- data/lib/badline/via/interrupt_register.rb +9 -0
- data/lib/badline/via/shift_register.rb +13 -0
- data/lib/badline/via/state.rb +42 -0
- data/lib/badline/via/timer1.rb +13 -0
- data/lib/badline/via/timer2.rb +18 -0
- data/lib/badline/via.rb +2 -0
- data/lib/badline/vic/bank.rb +21 -0
- data/lib/badline/vic/border_mask.rb +10 -0
- data/lib/badline/vic/collisions.rb +11 -0
- data/lib/badline/vic/color_patches.rb +11 -0
- data/lib/badline/vic/display_state.rb +29 -1
- data/lib/badline/vic/graphics_shifter.rb +26 -0
- data/lib/badline/vic/register_log.rb +18 -0
- data/lib/badline/vic/registers.rb +13 -2
- data/lib/badline/vic/sequencer.rb +12 -0
- data/lib/badline/vic/sequencer_state.rb +89 -0
- data/lib/badline/vic/sprite/internal_bus.rb +11 -0
- data/lib/badline/vic/sprite/saved_state.rb +64 -0
- data/lib/badline/vic/sprite.rb +2 -0
- data/lib/badline/vic/sprites.rb +3 -0
- data/lib/badline/vic/sprites_state.rb +57 -0
- data/lib/badline/vic.rb +109 -16
- data/lib/badline.rb +1 -0
- metadata +59 -18
- data/.claude/hooks/session-start.sh +0 -57
- data/.claude/settings.json +0 -14
- data/lib/badline/audio/sdl_sink.rb +0 -70
- data/lib/badline/audio/stream.rb +0 -117
- data/lib/badline/gui/application.rb +0 -216
- data/lib/badline/gui/drive_led_pane.rb +0 -29
- data/lib/badline/gui/gamepads.rb +0 -97
- data/lib/badline/gui/joy_map.rb +0 -27
- data/lib/badline/gui/key_map.rb +0 -39
- data/lib/badline/gui/palette.rb +0 -35
- data/lib/badline/gui/pane.rb +0 -36
- data/lib/badline/gui/screen_pane.rb +0 -46
- data/lib/badline/gui/window.rb +0 -61
- data/lib/badline/gui.rb +0 -13
- data/lib/badline/sdl/events.rb +0 -47
- data/lib/badline/sdl/functions.rb +0 -74
- data/vendor/.gitkeep +0 -0
data/lib/badline/options.rb
CHANGED
|
@@ -1,101 +1,106 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "badline/options/option"
|
|
4
|
+
require "badline/options/event"
|
|
5
|
+
require "badline/options/table"
|
|
6
|
+
require "badline/options/help"
|
|
4
7
|
|
|
5
8
|
module Badline
|
|
6
|
-
# The command line of
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
9
|
+
# The command line of both builds, badline-ruby and the native badline.
|
|
10
|
+
# Either opens the window for any media, or with --headless or
|
|
11
|
+
# --audio-out plays or renders a .sid tune without one. The native build
|
|
12
|
+
# adds --no-sound and --version, and badline-ruby adds --disable-jit.
|
|
13
|
+
#
|
|
14
|
+
# TABLE lists the options, and both the parser and the help read it. It
|
|
15
|
+
# parses by hand, inside the subset of Ruby Spinel compiles. Values come
|
|
16
|
+
# as `--song 2` or `--song=2`, and `--` ends the options. It loads
|
|
17
|
+
# nothing else from badline, so badline-ruby can parse and reject its
|
|
18
|
+
# arguments before paying for the emulator.
|
|
10
19
|
class Options
|
|
11
20
|
class Error < StandardError; end
|
|
12
21
|
|
|
13
22
|
FALLBACK_SECONDS = 60.0
|
|
14
23
|
|
|
15
|
-
DEFAULT_RATE = 44_100
|
|
16
|
-
|
|
17
24
|
SID_MODELS = { "6581" => :mos6581, "8580" => :mos8580 }.freeze
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
Usage: badline-ruby [options] [media]
|
|
21
|
-
badline-ruby --headless [options] tune.sid
|
|
22
|
-
badline-ruby [options] tune.sid --audio-out FILE
|
|
23
|
-
|
|
24
|
-
Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
|
|
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
|
|
28
|
-
traps on the KERNAL's disk routines, unless --true-drive puts an
|
|
29
|
-
emulated 1541 there, which runs its own DOS and reads .d64 and .g64
|
|
30
|
-
images only.
|
|
31
|
-
|
|
32
|
-
--headless plays a .sid tune on the host's audio device without the
|
|
33
|
-
window, and --audio-out renders it to 16-bit PCM instead. The
|
|
34
|
-
container follows the file's extension, .wav or .aiff. Played on a
|
|
35
|
-
terminal, n or → skips to the next song, p or ← to the previous one,
|
|
36
|
-
space pauses and q quits.
|
|
37
|
-
|
|
38
|
-
A .sid file carries no length of its own. Without --seconds the tune is
|
|
39
|
-
looked up by MD5 in HVSC's Songlengths.md5, taken from --songlengths, from
|
|
40
|
-
the DOCUMENTS directory of an HVSC collection above the tune, or from
|
|
41
|
-
$HVSC_BASE. Failing all of those it runs for #{FALLBACK_SECONDS.to_i} seconds.
|
|
42
|
-
|
|
43
|
-
Without the window, PSID tunes run on a bare CPU and SID, faster than
|
|
44
|
-
real time. RSID tunes boot the whole machine and run at about half
|
|
45
|
-
real time, so they stutter when played. --filter-chunk 1 runs the
|
|
46
|
-
filter cycle by cycle, which is exact but takes about twice as long.
|
|
26
|
+
REU_SIZES = %w[128 256 512 1024 2048 4096 8192 16384].freeze
|
|
47
27
|
|
|
48
|
-
|
|
28
|
+
# A Float as OptionParser took one: digits with an optional sign,
|
|
29
|
+
# fraction and exponent.
|
|
30
|
+
DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
|
|
49
31
|
|
|
50
|
-
attr_reader :
|
|
32
|
+
attr_reader :program, :media_path, :song, :sid_model, :reu, :frames, :screenshot, :save_snapshot, :audio_out,
|
|
33
|
+
:seconds, :songlengths, :filter_chunk, :timeline
|
|
51
34
|
|
|
52
|
-
|
|
35
|
+
def self.parse(argv, native: false) = new(native:).parse(argv)
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
# The native build plays the SID in the window unless --no-sound says
|
|
38
|
+
# otherwise, and badline-ruby, which runs below real time, only with
|
|
39
|
+
# --sound.
|
|
40
|
+
def initialize(native: false)
|
|
41
|
+
@native = native
|
|
42
|
+
@program = native ? "badline" : "badline-ruby"
|
|
43
|
+
@media_path = nil
|
|
44
|
+
@song = nil
|
|
45
|
+
@sid_model = nil
|
|
46
|
+
@reu = nil
|
|
57
47
|
@autostart = true
|
|
58
48
|
@read_only = false
|
|
59
|
-
@sound =
|
|
49
|
+
@sound = native
|
|
50
|
+
@frames = 0
|
|
51
|
+
@paced = true
|
|
52
|
+
@vsync = true
|
|
60
53
|
@verbose = false
|
|
61
54
|
@true_drive = false
|
|
62
55
|
@ntsc = false
|
|
63
|
-
@
|
|
56
|
+
@screenshot = ""
|
|
57
|
+
@save_snapshot = ""
|
|
58
|
+
@timeline = []
|
|
64
59
|
@jit = true
|
|
60
|
+
@help = false
|
|
61
|
+
@version = false
|
|
62
|
+
@headless = false
|
|
63
|
+
@audio_out = nil
|
|
64
|
+
@seconds = nil
|
|
65
|
+
@songlengths = nil
|
|
66
|
+
@rate = nil
|
|
67
|
+
@filter_chunk = nil
|
|
65
68
|
@quiet = false
|
|
66
69
|
@tui = true
|
|
67
|
-
@help = false
|
|
68
70
|
@window_only = []
|
|
69
71
|
@headless_only = []
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
def parse(argv)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
validate unless help?
|
|
75
|
+
args = argv.dup
|
|
76
|
+
argument(args.shift, args) until args.empty?
|
|
77
|
+
validate unless help? || version?
|
|
77
78
|
self
|
|
78
|
-
rescue OptionParser::ParseError => e
|
|
79
|
-
raise Error, e.message
|
|
80
79
|
end
|
|
81
80
|
|
|
81
|
+
# Whether the media is a .vsf snapshot to restore.
|
|
82
|
+
def snapshot? = !@media_path.nil? && File.extname(@media_path).casecmp?(".vsf")
|
|
83
|
+
|
|
82
84
|
# Plays or renders a .sid tune without the window.
|
|
83
85
|
def headless? = @headless || render?
|
|
84
86
|
|
|
85
87
|
def window? = !headless?
|
|
86
88
|
|
|
87
|
-
def render? =
|
|
89
|
+
def render? = !@audio_out.nil?
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
# The tune --headless and --audio-out play, for Audio::CLI.
|
|
92
|
+
def tune_path = @media_path
|
|
90
93
|
|
|
91
94
|
# How long to play a tune whose length nothing gives.
|
|
92
95
|
def fallback_seconds = FALLBACK_SECONDS
|
|
93
96
|
|
|
97
|
+
def rate = @rate.nil? ? DEFAULT_RATE : @rate
|
|
98
|
+
|
|
94
99
|
def rate_given? = !@rate.nil?
|
|
95
100
|
|
|
96
|
-
def
|
|
101
|
+
def quiet? = @quiet
|
|
97
102
|
|
|
98
|
-
def
|
|
103
|
+
def tui? = @tui
|
|
99
104
|
|
|
100
105
|
def autostart? = @autostart
|
|
101
106
|
|
|
@@ -103,133 +108,165 @@ module Badline
|
|
|
103
108
|
|
|
104
109
|
def sound? = @sound
|
|
105
110
|
|
|
111
|
+
def paced? = @paced
|
|
112
|
+
|
|
113
|
+
def vsync? = @vsync
|
|
114
|
+
|
|
106
115
|
def verbose? = @verbose
|
|
107
116
|
|
|
108
117
|
def true_drive? = @true_drive
|
|
109
118
|
|
|
110
119
|
def ntsc? = @ntsc
|
|
111
120
|
|
|
112
|
-
def
|
|
121
|
+
def jit? = @jit
|
|
113
122
|
|
|
114
|
-
def
|
|
123
|
+
def help? = @help
|
|
115
124
|
|
|
116
|
-
def
|
|
125
|
+
def version? = @version
|
|
117
126
|
|
|
118
127
|
private
|
|
119
128
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
def takes?(option) = [:both, @native ? :native : :ruby].include?(option.build)
|
|
130
|
+
|
|
131
|
+
def argument(arg, args)
|
|
132
|
+
if arg == "--"
|
|
133
|
+
media_argument(args.shift) until args.empty?
|
|
134
|
+
elsif arg.start_with?("-") && arg != "-"
|
|
135
|
+
option(arg, args)
|
|
136
|
+
else
|
|
137
|
+
media_argument(arg)
|
|
138
|
+
end
|
|
124
139
|
end
|
|
125
140
|
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
def option(arg, args)
|
|
142
|
+
flag = arg.start_with?("--") && arg.include?("=") ? arg[0, arg.index("=")] : arg
|
|
143
|
+
option = find(flag)
|
|
144
|
+
restrict(option, flag)
|
|
145
|
+
if flag == arg
|
|
146
|
+
option.valued? ? valued_option(option.name, flag, take_value(flag, args)) : switch(option.name)
|
|
147
|
+
elsif option.valued?
|
|
148
|
+
valued_option(option.name, flag, arg[flag.size + 1, arg.size - flag.size - 1])
|
|
149
|
+
else
|
|
150
|
+
raise Error, "needless argument: #{arg}"
|
|
133
151
|
end
|
|
134
152
|
end
|
|
135
153
|
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
raise Error, "invalid
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
def find(flag)
|
|
155
|
+
option = TABLE.find { |candidate| candidate.named?(flag) && takes?(candidate) }
|
|
156
|
+
raise Error, "invalid option: #{flag}" if option.nil?
|
|
157
|
+
|
|
158
|
+
option
|
|
141
159
|
end
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
# Notes an option only the window or only the headless player takes.
|
|
162
|
+
def restrict(option, flag)
|
|
163
|
+
@window_only << flag if option.needs == :window
|
|
164
|
+
@headless_only << flag if option.needs == :headless
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def take_value(flag, args)
|
|
168
|
+
raise Error, "missing argument: #{flag}" if args.empty?
|
|
169
|
+
|
|
170
|
+
args.shift
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def valued_option(name, flag, value)
|
|
174
|
+
case name
|
|
175
|
+
when "--song" then @song = number(flag, value)
|
|
176
|
+
when "--sid" then @sid_model = sid_model_for(value)
|
|
177
|
+
when "--reu" then @reu = reu_size(value)
|
|
178
|
+
when "--frames" then @frames = number(flag, value)
|
|
179
|
+
when "--screenshot" then @screenshot = value
|
|
180
|
+
when "--save-snapshot" then @save_snapshot = value
|
|
181
|
+
when "--at", "--script" then @timeline.concat(Event.given(name, value))
|
|
182
|
+
when "--audio-out" then @audio_out = value
|
|
183
|
+
when "--seconds" then @seconds = decimal(flag, value)
|
|
184
|
+
when "--songlengths" then @songlengths = value
|
|
185
|
+
when "--rate" then @rate = number(flag, value)
|
|
186
|
+
else @filter_chunk = number(flag, value)
|
|
146
187
|
end
|
|
147
188
|
end
|
|
148
189
|
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
190
|
+
def switch(name)
|
|
191
|
+
case name
|
|
192
|
+
when "--no-autostart" then @autostart = false
|
|
193
|
+
when "--read-only" then @read_only = true
|
|
194
|
+
when "--sound" then @sound = true
|
|
195
|
+
when "--no-sound" then @sound = false
|
|
196
|
+
when "--no-vsync" then @vsync = false
|
|
197
|
+
when "--verbose" then @verbose = true
|
|
198
|
+
when "--true-drive" then @true_drive = true
|
|
199
|
+
when "--ntsc" then @ntsc = true
|
|
200
|
+
when "--unpaced" then @paced = false
|
|
201
|
+
when "--disable-jit" then @jit = false
|
|
202
|
+
when "--help" then @help = true
|
|
203
|
+
when "--version" then @version = true
|
|
204
|
+
when "--headless" then @headless = true
|
|
205
|
+
when "--quiet" then @quiet = true
|
|
206
|
+
else @tui = false
|
|
160
207
|
end
|
|
161
208
|
end
|
|
162
209
|
|
|
163
|
-
def
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
opts.on("--disable-jit", "Run without enabling YJIT") { @jit = false }
|
|
168
|
-
opts.on("-h", "--help", "Show this help") { @help = true }
|
|
210
|
+
def sid_model_for(value)
|
|
211
|
+
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
212
|
+
|
|
213
|
+
SID_MODELS[value]
|
|
169
214
|
end
|
|
170
215
|
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
opts.on("--read-only", "Mount a disk image write-protected, leaving its file unchanged") do
|
|
176
|
-
window_only("--read-only") { @read_only = true }
|
|
177
|
-
end
|
|
178
|
-
opts.on("--sound", "Play the SID through the host's audio device (F10 mutes)") do
|
|
179
|
-
window_only("--sound") { @sound = true }
|
|
180
|
-
end
|
|
181
|
-
opts.on("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps") do
|
|
182
|
-
window_only("--true-drive") { @true_drive = true }
|
|
183
|
-
end
|
|
184
|
-
opts.on("--reu SIZE", %w[128 256 512 1024 2048 4096 8192 16384],
|
|
185
|
-
"Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384") do |size|
|
|
186
|
-
window_only("--reu") { @reu = size.to_i }
|
|
187
|
-
end
|
|
188
|
-
opts.on("--ntsc", "Run an NTSC C64, with the 6567R8 VIC-II, instead of a PAL one") do
|
|
189
|
-
window_only("--ntsc") { @ntsc = true }
|
|
190
|
-
end
|
|
191
|
-
opts.on("--verbose", "Print the display, sound and gamepad setup and the frame timing") do
|
|
192
|
-
window_only("--verbose") { @verbose = true }
|
|
193
|
-
end
|
|
216
|
+
def reu_size(value)
|
|
217
|
+
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
218
|
+
|
|
219
|
+
value.to_i
|
|
194
220
|
end
|
|
195
221
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
define_rendering_options(opts)
|
|
201
|
-
opts.on("--quiet", "Don't report progress") { headless_only("--quiet") { @quiet = true } }
|
|
202
|
-
opts.on("--no-tui", "Play without the interactive display, even on a terminal") do
|
|
203
|
-
headless_only("--no-tui") { @tui = false }
|
|
204
|
-
end
|
|
222
|
+
def number(flag, value)
|
|
223
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(/\A\d+\z/)
|
|
224
|
+
|
|
225
|
+
value.to_i
|
|
205
226
|
end
|
|
206
227
|
|
|
207
|
-
def
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
opts.on("--songlengths PATH", "HVSC Songlengths.md5 to take the length from") do |path|
|
|
212
|
-
headless_only("--songlengths") { @songlengths = path }
|
|
213
|
-
end
|
|
228
|
+
def decimal(flag, value)
|
|
229
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(DECIMAL)
|
|
230
|
+
|
|
231
|
+
value.to_f
|
|
214
232
|
end
|
|
215
233
|
|
|
216
|
-
def
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
def media_argument(arg)
|
|
235
|
+
raise Error, "unexpected argument: #{arg}" unless @media_path.nil?
|
|
236
|
+
|
|
237
|
+
@media_path = arg
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def validate
|
|
241
|
+
validate_mode
|
|
242
|
+
validate_numbers
|
|
243
|
+
validate_paths
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def validate_mode
|
|
247
|
+
if headless?
|
|
248
|
+
raise Error, "#{@window_only.first} needs the window" unless @window_only.empty?
|
|
249
|
+
raise Error, "no tune given" if @media_path.nil?
|
|
250
|
+
raise Error, "not a .sid tune: #{@media_path}" unless File.extname(@media_path).casecmp?(".sid")
|
|
251
|
+
elsif !@headless_only.empty?
|
|
252
|
+
raise Error, "#{@headless_only.first} needs --headless or --audio-out"
|
|
222
253
|
end
|
|
223
254
|
end
|
|
224
255
|
|
|
225
|
-
def
|
|
226
|
-
@
|
|
227
|
-
|
|
256
|
+
def validate_numbers
|
|
257
|
+
positive("--song", @song)
|
|
258
|
+
positive("--seconds", @seconds)
|
|
259
|
+
positive("--rate", @rate)
|
|
260
|
+
positive("--filter-chunk", @filter_chunk)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def positive(name, value)
|
|
264
|
+
raise Error, "invalid argument: #{name} #{value}" if !value.nil? && value <= 0
|
|
228
265
|
end
|
|
229
266
|
|
|
230
|
-
def
|
|
231
|
-
@
|
|
232
|
-
|
|
267
|
+
def validate_paths
|
|
268
|
+
raise Error, "no such file or directory: #{@media_path}" unless @media_path.nil? || File.exist?(@media_path)
|
|
269
|
+
raise Error, "no such file or directory: #{@songlengths}" unless @songlengths.nil? || File.exist?(@songlengths)
|
|
233
270
|
end
|
|
234
271
|
end
|
|
235
272
|
end
|
|
@@ -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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class REU
|
|
5
|
+
class DMA
|
|
6
|
+
# A transfer's state for a snapshot (REU::SavedState).
|
|
7
|
+
module SavedState
|
|
8
|
+
# Everything a transfer holds between cycles. Before the first
|
|
9
|
+
# transfer, the fields it sets on starting are empty.
|
|
10
|
+
def save_state(out)
|
|
11
|
+
out.int(@c64).int(@expansion).int(@length).int(@events)
|
|
12
|
+
[@type, @c64_step, @expansion_step, @last, @ba_low_after_writes, @swap_byte, @held_read].each do |value|
|
|
13
|
+
out.optional_int(value)
|
|
14
|
+
end
|
|
15
|
+
[@holding, @waiting, @stopped_after_write, @swap_write_due, @swap_read_again, @moved_all,
|
|
16
|
+
@extra_cycle].each do |flag|
|
|
17
|
+
out.boolean(flag)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_state(input)
|
|
22
|
+
@c64 = input.int
|
|
23
|
+
@expansion = input.int
|
|
24
|
+
@length = input.int
|
|
25
|
+
@events = input.int
|
|
26
|
+
load_steps(input)
|
|
27
|
+
@holding = input.boolean?
|
|
28
|
+
@waiting = input.boolean?
|
|
29
|
+
@stopped_after_write = input.boolean?
|
|
30
|
+
@swap_write_due = input.boolean?
|
|
31
|
+
@swap_read_again = input.boolean?
|
|
32
|
+
@moved_all = input.boolean?
|
|
33
|
+
@extra_cycle = input.boolean?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def load_steps(input)
|
|
39
|
+
@type = input.optional_int
|
|
40
|
+
@c64_step = input.optional_int
|
|
41
|
+
@expansion_step = input.optional_int
|
|
42
|
+
@last = input.optional_int
|
|
43
|
+
@ba_low_after_writes = input.optional_int
|
|
44
|
+
@swap_byte = input.optional_int
|
|
45
|
+
@held_read = input.optional_int
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/badline/reu/dma.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/reu/dma/saved_state"
|
|
4
|
+
|
|
3
5
|
module Badline
|
|
4
6
|
class REU
|
|
5
7
|
# One transfer between the C64 and the REU's RAM, clocked a cycle at a
|
|
@@ -8,6 +10,8 @@ module Badline
|
|
|
8
10
|
# After a write it goes on through the first BA-low cycle and stops at
|
|
9
11
|
# the second. See doc/pinned-behaviour.md, REU DMA.
|
|
10
12
|
class DMA
|
|
13
|
+
include SavedState
|
|
14
|
+
|
|
11
15
|
# Transfer types, as the command register's bottom two bits give
|
|
12
16
|
# them.
|
|
13
17
|
STASH = 0
|
|
@@ -52,16 +56,32 @@ module Badline
|
|
|
52
56
|
@ba_low_after_writes = 0
|
|
53
57
|
@stopped_after_write = false
|
|
54
58
|
@swap_write_due = false
|
|
59
|
+
@swap_read_again = false
|
|
55
60
|
@swap_byte = 0
|
|
56
61
|
@held_read = 0
|
|
57
62
|
@moved_all = false
|
|
58
63
|
@extra_cycle = false
|
|
59
64
|
end
|
|
60
65
|
|
|
66
|
+
# Writes the byte a fetch from these addresses starts with, without
|
|
67
|
+
# counting it. Other transfers start with a read.
|
|
68
|
+
def write_ahead(type, c64, expansion)
|
|
69
|
+
return unless type == FETCH
|
|
70
|
+
|
|
71
|
+
@c64 = c64
|
|
72
|
+
byte = @ram.peek(expansion)
|
|
73
|
+
@ram.latch = byte
|
|
74
|
+
write_c64(byte)
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
# One cycle of the transfer. The cycle the REC lets go of the bus on
|
|
62
|
-
# is the CPU's.
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
# is the CPU's. late says BA fell on this cycle but a REC that moved a
|
|
79
|
+
# byte on the last one doesn't see it yet (VIC#reu_ba_late?), and
|
|
80
|
+
# handed_on that BA runs on from a bad line's DMA into a sprite's
|
|
81
|
+
# here (VIC#reu_ba_handed_on?).
|
|
82
|
+
def cycle!(ba_low, late, handed_on)
|
|
83
|
+
note_ba(ba_low, late)
|
|
84
|
+
@swap_read_again ||= handed_on && @swap_write_due
|
|
65
85
|
settle_held_read(ba_low) if @held_read.positive?
|
|
66
86
|
return if @waiting && ba_low
|
|
67
87
|
|
|
@@ -73,20 +93,34 @@ module Badline
|
|
|
73
93
|
|
|
74
94
|
# The second BA-low cycle in a row after writes stops the REC, and so
|
|
75
95
|
# does any BA-low cycle after a read. It then waits for BA to rise.
|
|
76
|
-
def note_ba(ba_low)
|
|
96
|
+
def note_ba(ba_low, late)
|
|
97
|
+
seen = ba_low && !late
|
|
77
98
|
if @last == READ
|
|
78
|
-
|
|
99
|
+
note_ba_after_read(seen)
|
|
79
100
|
elsif @last == WRITE
|
|
80
|
-
|
|
81
|
-
@stopped_after_write = @ba_low_after_writes == 2
|
|
82
|
-
if @stopped_after_write
|
|
83
|
-
@waiting = true
|
|
84
|
-
@ba_low_after_writes = 0
|
|
85
|
-
end
|
|
101
|
+
note_ba_after_write(seen)
|
|
86
102
|
end
|
|
87
103
|
@last = IDLE
|
|
88
104
|
end
|
|
89
105
|
|
|
106
|
+
# A swap's read that BA stops before its write is made again.
|
|
107
|
+
def note_ba_after_read(ba_low)
|
|
108
|
+
@waiting = ba_low
|
|
109
|
+
@ba_low_after_writes = 0
|
|
110
|
+
@swap_read_again = ba_low && @swap_write_due && @held_read.zero?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A fetch's last write doesn't go through the first BA-low cycle.
|
|
114
|
+
# Pinned by REU/reutiming2/d and d2.
|
|
115
|
+
def note_ba_after_write(ba_low)
|
|
116
|
+
@ba_low_after_writes = ba_low ? @ba_low_after_writes + 1 : 0
|
|
117
|
+
@stopped_after_write = @ba_low_after_writes == 2 || (ba_low && @type == FETCH && @length == 1)
|
|
118
|
+
return unless @stopped_after_write
|
|
119
|
+
|
|
120
|
+
@waiting = true
|
|
121
|
+
@ba_low_after_writes = 0
|
|
122
|
+
end
|
|
123
|
+
|
|
90
124
|
# A fetch or a swap whose last write ran into BA, or a verify that
|
|
91
125
|
# found a difference before its last byte, takes one more cycle before
|
|
92
126
|
# the REC lets go of the bus.
|
|
@@ -130,6 +164,8 @@ module Badline
|
|
|
130
164
|
# Two cycles a byte: the C64's byte is read and stored while the
|
|
131
165
|
# REU's is fetched, and the REU's is written to the C64 on the next.
|
|
132
166
|
def swap(ba_low)
|
|
167
|
+
return read_swap_again if @swap_read_again
|
|
168
|
+
|
|
133
169
|
if @swap_write_due
|
|
134
170
|
@swap_write_due = false
|
|
135
171
|
write_c64(@swap_byte)
|
|
@@ -160,6 +196,15 @@ module Badline
|
|
|
160
196
|
@ba_low_after_writes = 0
|
|
161
197
|
end
|
|
162
198
|
|
|
199
|
+
# A swap's read that BA cut off from its write is made again when BA
|
|
200
|
+
# rises, and the write follows it. Pinned by REU/reutiming2/e4-m2,
|
|
201
|
+
# e6-m2 and g3-m2.
|
|
202
|
+
def read_swap_again
|
|
203
|
+
@swap_read_again = false
|
|
204
|
+
@ram.poke(@expansion, read_c64)
|
|
205
|
+
@last = READ
|
|
206
|
+
end
|
|
207
|
+
|
|
163
208
|
def settle_held_read(ba_low)
|
|
164
209
|
@held_read -= 1
|
|
165
210
|
return if @held_read.positive? && ba_low
|
data/lib/badline/reu/ram.rb
CHANGED
|
@@ -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,
|