badline 0.8.0 → 0.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/CLAUDE.md +6 -4
- data/README.md +60 -19
- data/doc/library.md +10 -3
- data/doc/media.md +11 -2
- data/doc/pinned-behaviour.md +199 -9
- data/doc/sid-player.md +8 -4
- data/doc/snapshots.md +79 -33
- data/lib/badline/address_bus/pla.rb +130 -0
- data/lib/badline/address_bus/roms.rb +32 -0
- data/lib/badline/address_bus.rb +13 -92
- data/lib/badline/audio/bare_player.rb +24 -0
- data/lib/badline/audio/checkpoints.rb +42 -0
- data/lib/badline/audio/jukebox.rb +11 -4
- data/lib/badline/audio/machine_player.rb +16 -0
- data/lib/badline/audio/renderer.rb +80 -9
- data/lib/badline/audio/stereo.rb +3 -5
- data/lib/badline/audio.rb +1 -0
- data/lib/badline/chrout_trap.rb +4 -3
- data/lib/badline/computer/attachments.rb +52 -12
- data/lib/badline/computer/saved_state.rb +1 -0
- data/lib/badline/computer.rb +50 -33
- data/lib/badline/control_ports.rb +10 -3
- data/lib/badline/core.rb +51 -0
- data/lib/badline/data_path.rb +37 -0
- data/lib/badline/datasette.rb +63 -10
- data/lib/badline/drive1541/disk.rb +3 -3
- data/lib/badline/drive1541/disk_state.rb +14 -5
- data/lib/badline/drive1541/serial_port.rb +5 -8
- data/lib/badline/drive1541/sleep.rb +5 -6
- data/lib/badline/drive1541.rb +3 -3
- data/lib/badline/frontend/app.rb +63 -64
- data/lib/badline/frontend/boot.rb +34 -4
- data/lib/badline/frontend/confirmation.rb +17 -11
- data/lib/badline/frontend/controls.rb +12 -6
- data/lib/badline/frontend/drive_led.rb +9 -10
- data/lib/badline/frontend/frame_rate.rb +4 -4
- data/lib/badline/frontend/machine_window.rb +87 -0
- data/lib/badline/frontend/menu_dialogs.rb +52 -14
- data/lib/badline/frontend/menu_media.rb +70 -16
- data/lib/badline/frontend/menu_pages.rb +47 -42
- data/lib/badline/frontend/menu_ports.rb +69 -0
- data/lib/badline/frontend/name_field.rb +88 -0
- data/lib/badline/frontend/pacer.rb +6 -5
- data/lib/badline/frontend/pause_menu.rb +52 -37
- data/lib/badline/frontend/player_screen.rb +1 -1
- data/lib/badline/frontend/player_window.rb +4 -4
- data/lib/badline/frontend/screen.rb +39 -37
- data/lib/badline/frontend/sid_view.rb +1 -1
- data/lib/badline/frontend/snapshots.rb +145 -17
- data/lib/badline/frontend/sound.rb +17 -9
- data/lib/badline/frontend/timeline.rb +36 -5
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/iec_bus.rb +16 -28
- data/lib/badline/kernal_trap/drive.rb +3 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +112 -0
- data/lib/badline/kernal_trap/load.rb +24 -25
- data/lib/badline/kernal_trap/routine.rb +17 -5
- data/lib/badline/kernal_trap/save.rb +13 -23
- data/lib/badline/kernal_trap/serial.rb +38 -24
- data/lib/badline/kernal_trap.rb +1 -0
- data/lib/badline/keyboard.rb +17 -14
- data/lib/badline/machine.rb +60 -0
- data/lib/badline/media/disk_list.rb +75 -0
- data/lib/badline/media/disk_set.rb +13 -3
- data/lib/badline/media/vic20_basic.rb +54 -0
- data/lib/badline/media/vic20_cartridge.rb +74 -0
- data/lib/badline/media/vic20_media.rb +132 -0
- data/lib/badline/media.rb +39 -20
- data/lib/badline/model.rb +62 -0
- data/lib/badline/options/event.rb +10 -6
- data/lib/badline/options/help.rb +25 -13
- data/lib/badline/options/table.rb +15 -1
- data/lib/badline/options/validation.rb +63 -3
- data/lib/badline/options.rb +37 -34
- data/lib/badline/region.rb +16 -4
- data/lib/badline/reu/ram.rb +27 -0
- data/lib/badline/reu/saved_state.rb +27 -0
- data/lib/badline/roms/README +37 -0
- data/lib/badline/roms/kernal-sx64.rom +0 -0
- data/lib/badline/roms/vic20/basic.rom +0 -0
- data/lib/badline/roms/vic20/character.rom +0 -0
- data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
- data/lib/badline/sid/decimator.rb +13 -0
- data/lib/badline/sid/filter.rb +6 -3
- data/lib/badline/sid.rb +19 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/fields.rb +1 -0
- data/lib/badline/snapshot/image.rb +15 -0
- data/lib/badline/snapshot/setup.rb +20 -8
- data/lib/badline/snapshot/state.rb +15 -7
- data/lib/badline/snapshot/vice/main_cpu.rb +1 -1
- data/lib/badline/snapshot/vice/peripherals.rb +12 -2
- data/lib/badline/snapshot/vice/restore.rb +3 -1
- data/lib/badline/snapshot/vice/reu1764.rb +55 -0
- data/lib/badline/snapshot/vice/vicii.rb +59 -13
- data/lib/badline/snapshot/vice/vicii_import.rb +14 -3
- data/lib/badline/snapshot/vice.rb +25 -23
- data/lib/badline/snapshot.rb +10 -0
- data/lib/badline/storage/crt_file.rb +16 -5
- data/lib/badline/storage/disk_image.rb +3 -0
- data/lib/badline/storage/host_directory.rb +3 -0
- data/lib/badline/storage/t64.rb +10 -4
- data/lib/badline/storage/tap.rb +44 -8
- data/lib/badline/storage/unavailable.rb +3 -1
- data/lib/badline/storage.rb +5 -5
- data/lib/badline/timing.rb +12 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/shift_register.rb +43 -16
- data/lib/badline/vic/bank.rb +53 -16
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic/sprite/timing.rb +5 -9
- data/lib/badline/vic/sprite.rb +4 -3
- data/lib/badline/vic.rb +34 -15
- data/lib/badline/vic20/attachments.rb +103 -0
- data/lib/badline/vic20/bus.rb +308 -0
- data/lib/badline/vic20/cpu.rb +10 -0
- data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
- data/lib/badline/vic20/port_wiring.rb +50 -0
- data/lib/badline/vic20/saved_state.rb +152 -0
- data/lib/badline/vic20/sound/output.rb +83 -0
- data/lib/badline/vic20/sound.rb +233 -0
- data/lib/badline/vic20/user_via_ports.rb +47 -0
- data/lib/badline/vic20/vic/painter.rb +239 -0
- data/lib/badline/vic20/vic/saved_state.rb +55 -0
- data/lib/badline/vic20/vic/text_window.rb +71 -0
- data/lib/badline/vic20/vic.rb +267 -0
- data/lib/badline/vic20.rb +220 -0
- data/lib/badline.rb +2 -38
- metadata +37 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
data/lib/badline/media.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
require "badline/media/true_drive"
|
|
4
4
|
require "badline/media/queue"
|
|
5
5
|
require "badline/media/disk_set"
|
|
6
|
+
require "badline/media/disk_list"
|
|
7
|
+
require "badline/media/vic20_basic"
|
|
8
|
+
require "badline/media/vic20_cartridge"
|
|
9
|
+
require "badline/media/vic20_media"
|
|
6
10
|
|
|
7
11
|
module Badline
|
|
8
12
|
module Media
|
|
@@ -27,23 +31,15 @@ module Badline
|
|
|
27
31
|
# A .g64 plugs in a true drive as device 8 (TrueDrive), and with one
|
|
28
32
|
# there, a .d64 goes into it instead of the KERNAL traps too. The
|
|
29
33
|
# autostart then loads through it.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
attach_sid(computer, path, autostart:, subtune:)
|
|
40
|
-
elsif File.extname(path).downcase == ".tap"
|
|
41
|
-
attach_tape(computer, path, autostart:)
|
|
42
|
-
elsif MOUNT_TYPES.key?(File.extname(path).downcase)
|
|
43
|
-
attach_storage(computer, path, options.fetch(:disk, {}), autostart:)
|
|
44
|
-
else
|
|
45
|
-
attach_prg(computer, path, autostart:)
|
|
46
|
-
end
|
|
34
|
+
#
|
|
35
|
+
# An .m3u or .vfl list of disks attaches the first disk it lists
|
|
36
|
+
# (DiskList). A machine other than the C64 takes its own media
|
|
37
|
+
# (Vic20Media).
|
|
38
|
+
def attach(computer, path, autostart: true, subtune: nil, **)
|
|
39
|
+
path = DiskList.disk(path)
|
|
40
|
+
return Vic20Media.attach(computer, path, autostart:, **) unless computer.is_a?(Computer)
|
|
41
|
+
|
|
42
|
+
attach_c64(computer, path, autostart:, subtune:, **)
|
|
47
43
|
end
|
|
48
44
|
|
|
49
45
|
# Swaps the disk in device 8 for a disk image or a host directory,
|
|
@@ -64,16 +60,39 @@ module Badline
|
|
|
64
60
|
"Inserted #{path} in device 8"
|
|
65
61
|
end
|
|
66
62
|
|
|
63
|
+
# The RAM expansion a VIC-20 for `path` should be built with
|
|
64
|
+
# (Vic20Media.ram_for), a key of Vic20::Bus::RAM_CONFIGURATIONS.
|
|
65
|
+
def vic20_ram_for(path) = Vic20Media.ram_for(path)
|
|
66
|
+
|
|
67
67
|
# The SID a machine for `path` should be built with. A .sid tune names
|
|
68
|
-
# its own; everything else gets
|
|
69
|
-
def sid_model(path)
|
|
70
|
-
return
|
|
68
|
+
# its own; everything else gets `otherwise`.
|
|
69
|
+
def sid_model(path, otherwise: :mos6581)
|
|
70
|
+
return otherwise unless path && File.extname(path).downcase == ".sid"
|
|
71
71
|
|
|
72
72
|
Storage::SIDFile.new(path).sid_model
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
private
|
|
76
76
|
|
|
77
|
+
def attach_c64(computer, path, autostart:, subtune:, **options)
|
|
78
|
+
if TrueDrive.takes?(computer, path)
|
|
79
|
+
attach_true_drive(computer, path, options.fetch(:disk, {}), autostart:)
|
|
80
|
+
elsif File.directory?(path)
|
|
81
|
+
computer.mount(Storage::HostDirectory.new(path))
|
|
82
|
+
"Mounted #{path} as device 8"
|
|
83
|
+
elsif File.extname(path).downcase == ".crt"
|
|
84
|
+
attach_cartridge(computer, path, options.fetch(:cartridge, {}))
|
|
85
|
+
elsif File.extname(path).downcase == ".sid"
|
|
86
|
+
attach_sid(computer, path, autostart:, subtune:)
|
|
87
|
+
elsif File.extname(path).downcase == ".tap"
|
|
88
|
+
attach_tape(computer, path, autostart:)
|
|
89
|
+
elsif MOUNT_TYPES.key?(File.extname(path).downcase)
|
|
90
|
+
attach_storage(computer, path, options.fetch(:disk, {}), autostart:)
|
|
91
|
+
else
|
|
92
|
+
attach_prg(computer, path, autostart:)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
77
96
|
def disk?(path)
|
|
78
97
|
storage = MOUNT_TYPES[File.extname(path).downcase]
|
|
79
98
|
File.directory?(path) || (!storage.nil? && storage < Storage::DiskImage)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# = Model
|
|
5
|
+
#
|
|
6
|
+
# The C64s badline builds by name, as `--model` selects them: the chips
|
|
7
|
+
# each was sold with, its video standard, its KERNAL and whether it has
|
|
8
|
+
# a datasette. The names are x64sc's.
|
|
9
|
+
module Model
|
|
10
|
+
# Each field but the name is a keyword Computer.new takes.
|
|
11
|
+
Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :kernal, :datasette)
|
|
12
|
+
|
|
13
|
+
# The breadbin PAL C64: the 6569 VIC-II, 6526 CIAs and the 6581 SID.
|
|
14
|
+
C64 = Profile.new(name: "c64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
15
|
+
region: Region::PAL, kernal: :c64, datasette: true)
|
|
16
|
+
|
|
17
|
+
# The PAL C64C: the HMOS 8565 VIC-II, 6526A CIAs and the 8580 SID.
|
|
18
|
+
C64C = Profile.new(name: "c64c", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
|
|
19
|
+
region: Region::PAL, kernal: :c64, datasette: true)
|
|
20
|
+
|
|
21
|
+
# The NTSC C64 with the 6567R8 VIC-II.
|
|
22
|
+
NTSC = Profile.new(name: "ntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
23
|
+
region: Region::NTSC, kernal: :c64, datasette: true)
|
|
24
|
+
|
|
25
|
+
# The NTSC C64C: the 8562, the 6567R8's HMOS successor, with the
|
|
26
|
+
# C64C's CIAs and SID.
|
|
27
|
+
NEW_NTSC = Profile.new(name: "newntsc", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
|
|
28
|
+
region: Region::NTSC, kernal: :c64, datasette: true)
|
|
29
|
+
|
|
30
|
+
# The first NTSC C64s, with the 6567R56A VIC-II.
|
|
31
|
+
OLD_NTSC = Profile.new(name: "oldntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
32
|
+
region: Region::NTSC_OLD, kernal: :c64, datasette: true)
|
|
33
|
+
|
|
34
|
+
# The Drean C64 of Argentina, on PAL-N with the 6572 VIC-II.
|
|
35
|
+
DREAN = Profile.new(name: "drean", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
36
|
+
region: Region::DREAN, kernal: :c64, datasette: true)
|
|
37
|
+
|
|
38
|
+
# The portable SX-64: a PAL C64's chips with its own KERNAL, a 1541
|
|
39
|
+
# built in as device 8 and no cassette port.
|
|
40
|
+
SX64 = Profile.new(name: "sx64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
41
|
+
region: Region::PAL, kernal: :sx64, datasette: false)
|
|
42
|
+
|
|
43
|
+
ALL = [C64, C64C, NTSC, NEW_NTSC, OLD_NTSC, DREAN, SX64].freeze
|
|
44
|
+
|
|
45
|
+
def self.named(name)
|
|
46
|
+
model = ALL.find { |candidate| candidate.name == name }
|
|
47
|
+
raise ArgumentError, "no C64 model named #{name}" if model.nil?
|
|
48
|
+
|
|
49
|
+
model
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The model whose chips, region, KERNAL and datasette `machine` has, or
|
|
53
|
+
# nil for none.
|
|
54
|
+
def self.of(machine)
|
|
55
|
+
ALL.find do |model|
|
|
56
|
+
model.vic_model == machine.vic_model && model.cia_model == machine.cia_model &&
|
|
57
|
+
model.sid_model == machine.sid_model && model.region == machine.region &&
|
|
58
|
+
model.kernal == machine.kernal && model.datasette == machine.datasette
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -6,10 +6,11 @@ module Badline
|
|
|
6
6
|
# `frame` frames have run. An event given at several frames becomes
|
|
7
7
|
# one Event per frame.
|
|
8
8
|
class Event
|
|
9
|
-
# The actions, and whether each takes an argument.
|
|
9
|
+
# The actions, and whether each takes an argument. menu's page may be
|
|
10
|
+
# left out.
|
|
10
11
|
ACTIONS = {
|
|
11
|
-
"key" => true, "type" => true, "insert" => true, "eject" => true, "screenshot" => true,
|
|
12
|
-
"reset" => false, "freeze" => false, "quit" => false
|
|
12
|
+
"key" => true, "type" => true, "insert" => true, "eject" => true, "screenshot" => true, "menu" => true,
|
|
13
|
+
"reset" => false, "freeze" => false, "resume" => false, "quit" => false
|
|
13
14
|
}.freeze
|
|
14
15
|
|
|
15
16
|
# The frames a key or the freeze button is held for.
|
|
@@ -17,8 +18,8 @@ module Badline
|
|
|
17
18
|
|
|
18
19
|
EJECTABLE = %w[disk tape cartridge].freeze
|
|
19
20
|
|
|
20
|
-
# What insert takes: disk images, tapes and cartridges, or
|
|
21
|
-
# directory to mount as device 8.
|
|
21
|
+
# What insert takes: disk images, tapes and cartridges, or an .m3u
|
|
22
|
+
# or .vfl list of disks or a directory to mount as device 8.
|
|
22
23
|
INSERTABLE = %w[.d64 .d71 .d81 .g64 .t64 .tap .crt].freeze
|
|
23
24
|
|
|
24
25
|
attr_reader :frame, :action, :argument
|
|
@@ -79,6 +80,7 @@ module Badline
|
|
|
79
80
|
|
|
80
81
|
def self.valid?(action, argument)
|
|
81
82
|
return false unless ACTIONS.key?(action)
|
|
83
|
+
return true if action == "menu"
|
|
82
84
|
return argument.empty? unless ACTIONS[action]
|
|
83
85
|
return EJECTABLE.include?(argument) if action == "eject"
|
|
84
86
|
return insertable?(argument) if action == "insert"
|
|
@@ -87,7 +89,9 @@ module Badline
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def self.insertable?(path)
|
|
90
|
-
|
|
92
|
+
return true if File.directory?(path)
|
|
93
|
+
|
|
94
|
+
File.exist?(path) && (INSERTABLE.include?(File.extname(path).downcase) || Media::DiskList.list?(path))
|
|
91
95
|
end
|
|
92
96
|
end
|
|
93
97
|
end
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -37,20 +37,30 @@ module Badline
|
|
|
37
37
|
|
|
38
38
|
def banner
|
|
39
39
|
<<~BANNER
|
|
40
|
-
Usage: #{@program} [options] [media]
|
|
40
|
+
Usage: #{@program} [c64] [options] [media]
|
|
41
|
+
#{@program} vic20 [options] [media]
|
|
41
42
|
#{@program} --headless [options] tune.sid
|
|
42
43
|
#{@program} [options] tune.sid --audio-out FILE
|
|
43
44
|
#{@program} sid [options] FILE|DIR...
|
|
44
45
|
|
|
46
|
+
Commands:
|
|
47
|
+
c64 Run a C64, the one --model names (the default)
|
|
48
|
+
vic20 Run a PAL VIC-20, with the RAM --ram names
|
|
49
|
+
sid Play .sid tunes and directories of them
|
|
50
|
+
|
|
45
51
|
Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
|
|
46
|
-
.g64 disk image for the true 1541,
|
|
47
|
-
|
|
48
|
-
to mount as device 8. It
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
.g64 disk image for the true 1541, an .m3u or .vfl list of disk
|
|
53
|
+
images, a .t64 tape archive, a .tap tape, a .crt cartridge, a .sid
|
|
54
|
+
tune, a .vsf snapshot, or a directory to mount as device 8. It
|
|
55
|
+
opens in the emulator window. There F11 quicksaves the machine to
|
|
56
|
+
the next of five slots in badline's data folder, replacing the
|
|
57
|
+
oldest, and F12 restores the newest quicksave or named save. The
|
|
58
|
+
window also autosaves every two minutes of play, which only the
|
|
59
|
+
pause menu loads. Device 8 answers through traps on the KERNAL's
|
|
60
|
+
disk routines, unless --true-drive puts an emulated 1541 there,
|
|
61
|
+
which runs its own DOS and reads .d64 and .g64 images only. A .sid
|
|
62
|
+
tune opens in the SID player, as `sid` plays it, unless an option
|
|
63
|
+
of the emulator's window asks for the machine.
|
|
54
64
|
|
|
55
65
|
--headless plays a .sid tune on the host's audio device without the
|
|
56
66
|
window, and --audio-out renders it to 16-bit PCM instead. The
|
|
@@ -74,10 +84,12 @@ module Badline
|
|
|
74
84
|
key=NAME presses a C64 key (space, return, a, f1, run_stop, restore)
|
|
75
85
|
or a joystick's direction or fire (joy1-up, joy2-fire) for #{Event::HOLD}
|
|
76
86
|
frames. type=TEXT types it, with \\n for RETURN. insert=FILE swaps
|
|
77
|
-
in a disk,
|
|
78
|
-
takes one out. screenshot=FILE saves
|
|
79
|
-
frame's number in place of %d.
|
|
80
|
-
|
|
87
|
+
in a disk, a list's first disk, a tape or a cartridge, and
|
|
88
|
+
eject=disk, tape or cartridge takes one out. screenshot=FILE saves
|
|
89
|
+
the frame as a .bmp, with the frame's number in place of %d. menu
|
|
90
|
+
opens the pause menu, and menu=PAGE opens it on snapshots, drive,
|
|
91
|
+
datasette, expansion, ports, sound or power. Its frames still count,
|
|
92
|
+
and resume closes it. reset, freeze and quit take no argument.
|
|
81
93
|
BANNER
|
|
82
94
|
end
|
|
83
95
|
|
|
@@ -4,6 +4,17 @@ module Badline
|
|
|
4
4
|
class Options
|
|
5
5
|
DEFAULT_RATE = 44_100
|
|
6
6
|
|
|
7
|
+
# The names Badline::Model builds, in the order the help lists them.
|
|
8
|
+
MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64].freeze
|
|
9
|
+
|
|
10
|
+
# The VIC-20's models, as xvic names its video standards. Only PAL is
|
|
11
|
+
# emulated.
|
|
12
|
+
VIC20_MODELS = %w[pal ntsc].freeze
|
|
13
|
+
|
|
14
|
+
# The VIC-20 RAM expansions --ram takes, in the order the help lists
|
|
15
|
+
# them.
|
|
16
|
+
RAM_NAMES = %w[unexpanded 3k 8k 16k 24k 32k all].freeze
|
|
17
|
+
|
|
7
18
|
SECTIONS = ["Options", "Window options", "Options without the window", "Testing options"].freeze
|
|
8
19
|
|
|
9
20
|
# Every option of both builds, in the order the help lists them.
|
|
@@ -25,7 +36,10 @@ module Badline
|
|
|
25
36
|
section: "Window options", needs: :window),
|
|
26
37
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
27
38
|
section: "Window options", needs: :window),
|
|
28
|
-
Option.new("--
|
|
39
|
+
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal",
|
|
40
|
+
section: "Window options", needs: :window),
|
|
41
|
+
Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
|
|
42
|
+
Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded)",
|
|
29
43
|
section: "Window options", needs: :window),
|
|
30
44
|
Option.new("--no-vsync", "Pace frames by the timer or the sound instead of the display",
|
|
31
45
|
section: "Window options", needs: :window),
|
|
@@ -2,14 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Options
|
|
5
|
-
# The checks Options#parse makes
|
|
6
|
-
#
|
|
7
|
-
# exist.
|
|
5
|
+
# The checks Options#parse makes: that each value reads as its option
|
|
6
|
+
# takes it, and once every argument has been read, that the options
|
|
7
|
+
# fit the mode, the numbers are positive and the files exist.
|
|
8
8
|
module Validation
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
def sid_model_for(value)
|
|
12
|
+
return if value == "auto"
|
|
13
|
+
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
14
|
+
|
|
15
|
+
SID_MODELS[value]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ram_configuration(value)
|
|
19
|
+
raise Error, "invalid argument: --ram #{value}" unless RAM_CONFIGURATIONS.key?(value)
|
|
20
|
+
|
|
21
|
+
RAM_CONFIGURATIONS[value]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reu_size(value)
|
|
25
|
+
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
26
|
+
|
|
27
|
+
value.to_i
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def number(flag, value)
|
|
31
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(/\A\d+\z/)
|
|
32
|
+
|
|
33
|
+
value.to_i
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def decimal(flag, value)
|
|
37
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(DECIMAL)
|
|
38
|
+
|
|
39
|
+
value.to_f
|
|
40
|
+
end
|
|
41
|
+
|
|
11
42
|
def validate
|
|
43
|
+
validate_family
|
|
12
44
|
validate_mode
|
|
45
|
+
validate_models
|
|
13
46
|
validate_numbers
|
|
14
47
|
validate_paths
|
|
15
48
|
end
|
|
@@ -25,6 +58,24 @@ module Badline
|
|
|
25
58
|
end
|
|
26
59
|
end
|
|
27
60
|
|
|
61
|
+
def validate_family
|
|
62
|
+
if @family == :vic20
|
|
63
|
+
validate_vic20
|
|
64
|
+
elsif !@ram.nil?
|
|
65
|
+
raise Error, "--ram needs vic20"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The VIC-20 is a PAL one, in the window, without the C64's SID and
|
|
70
|
+
# REU.
|
|
71
|
+
def validate_vic20
|
|
72
|
+
raise Error, "the NTSC VIC-20 isn't emulated yet" if @models.include?("ntsc")
|
|
73
|
+
|
|
74
|
+
raise Error, "--sid needs the C64" unless @sid_model.nil?
|
|
75
|
+
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
76
|
+
raise Error, "the VIC-20 needs the window" if headless?
|
|
77
|
+
end
|
|
78
|
+
|
|
28
79
|
# `sid` takes directories as well as tunes.
|
|
29
80
|
def validate_tune(path)
|
|
30
81
|
return if !File.exist?(path) || File.extname(path).casecmp?(".sid")
|
|
@@ -33,6 +84,15 @@ module Badline
|
|
|
33
84
|
raise Error, "not a .sid tune: #{path}"
|
|
34
85
|
end
|
|
35
86
|
|
|
87
|
+
# --model and --ntsc may be given more than once, but only for one
|
|
88
|
+
# model.
|
|
89
|
+
def validate_models
|
|
90
|
+
names = @family == :vic20 ? VIC20_MODELS : MODELS
|
|
91
|
+
unknown = @models.find { |name| !names.include?(name) }
|
|
92
|
+
raise Error, "invalid argument: --model #{unknown}" unless unknown.nil?
|
|
93
|
+
raise Error, "conflicting models: #{@models.uniq.join(' and ')}" unless @models.uniq.size <= 1
|
|
94
|
+
end
|
|
95
|
+
|
|
36
96
|
def validate_numbers
|
|
37
97
|
positive("--subtune", @subtune)
|
|
38
98
|
positive("--seconds", @seconds)
|
data/lib/badline/options.rb
CHANGED
|
@@ -10,7 +10,9 @@ module Badline
|
|
|
10
10
|
# The command line of both builds, badline-ruby and the native badline.
|
|
11
11
|
# Either opens the window for any media, or with --headless or
|
|
12
12
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
13
|
-
# first, plays .sid tunes and directories of them in the terminal.
|
|
13
|
+
# first, plays .sid tunes and directories of them in the terminal.
|
|
14
|
+
# `c64` or `vic20`, given first, names the machine family, and without
|
|
15
|
+
# either the family is the C64. The
|
|
14
16
|
# native build adds --no-sound and --version, and badline-ruby adds
|
|
15
17
|
# --disable-jit.
|
|
16
18
|
#
|
|
@@ -37,6 +39,13 @@ module Badline
|
|
|
37
39
|
|
|
38
40
|
REU_SIZES = %w[128 256 512 1024 2048 4096 8192 16384].freeze
|
|
39
41
|
|
|
42
|
+
# The VIC-20's RAM expansions as --ram names them (RAM_NAMES), and the
|
|
43
|
+
# names Vic20::Bus::RAM_CONFIGURATIONS gives them.
|
|
44
|
+
RAM_CONFIGURATIONS = {
|
|
45
|
+
"unexpanded" => :unexpanded, "3k" => :"3k", "8k" => :"8k", "16k" => :"16k", "24k" => :"24k",
|
|
46
|
+
"32k" => :"32k", "all" => :all
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
40
49
|
# A Float as OptionParser took one: digits with an optional sign,
|
|
41
50
|
# fraction and exponent.
|
|
42
51
|
DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
|
|
@@ -46,6 +55,13 @@ module Badline
|
|
|
46
55
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
47
56
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
48
57
|
|
|
58
|
+
# The machine family to build (Badline::Machine), :c64 or :vic20.
|
|
59
|
+
attr_reader :family
|
|
60
|
+
|
|
61
|
+
# The VIC-20's RAM expansion --ram names, a key of
|
|
62
|
+
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
63
|
+
attr_reader :ram
|
|
64
|
+
|
|
49
65
|
def self.parse(argv, native: false) = new(native:).parse(argv)
|
|
50
66
|
|
|
51
67
|
# The native build plays the SID in the window unless --no-sound says
|
|
@@ -55,11 +71,14 @@ module Badline
|
|
|
55
71
|
@native = native
|
|
56
72
|
@program = native ? "badline" : "badline-ruby"
|
|
57
73
|
@media_path = nil
|
|
74
|
+
@family = :c64
|
|
58
75
|
@sid_command = false
|
|
59
76
|
@tune_paths = []
|
|
60
77
|
@subtune = nil
|
|
61
78
|
@sid_model = nil
|
|
79
|
+
@models = []
|
|
62
80
|
@reu = nil
|
|
81
|
+
@ram = nil
|
|
63
82
|
@autostart = true
|
|
64
83
|
@writable = false
|
|
65
84
|
@sound = native
|
|
@@ -68,7 +87,6 @@ module Badline
|
|
|
68
87
|
@vsync = true
|
|
69
88
|
@verbose = false
|
|
70
89
|
@true_drive = false
|
|
71
|
-
@ntsc = false
|
|
72
90
|
@screenshot = ""
|
|
73
91
|
@save_snapshot = ""
|
|
74
92
|
@timeline = []
|
|
@@ -90,7 +108,7 @@ module Badline
|
|
|
90
108
|
|
|
91
109
|
def parse(argv)
|
|
92
110
|
args = argv.dup
|
|
93
|
-
|
|
111
|
+
subcommand(args)
|
|
94
112
|
argument(args.shift, args) until args.empty?
|
|
95
113
|
play_lone_tune
|
|
96
114
|
validate unless help? || version?
|
|
@@ -147,10 +165,12 @@ module Badline
|
|
|
147
165
|
|
|
148
166
|
def true_drive? = @true_drive
|
|
149
167
|
|
|
150
|
-
def ntsc? = @ntsc
|
|
151
|
-
|
|
152
168
|
def jit? = @jit
|
|
153
169
|
|
|
170
|
+
# The name of the model to build: a C64 Badline::Model names, or a
|
|
171
|
+
# VIC-20 of VIC20_MODELS. --ntsc is short for --model ntsc.
|
|
172
|
+
def model = @models.first || (@family == :vic20 ? VIC20_MODELS.first : MODELS.first)
|
|
173
|
+
|
|
154
174
|
def help? = @help
|
|
155
175
|
|
|
156
176
|
def version? = @version
|
|
@@ -163,15 +183,21 @@ module Badline
|
|
|
163
183
|
[:both, @native ? :native : :ruby].include?(option.build)
|
|
164
184
|
end
|
|
165
185
|
|
|
166
|
-
|
|
186
|
+
# Takes `sid`, `c64` or `vic20` when given first.
|
|
187
|
+
def subcommand(args)
|
|
188
|
+
case args.first
|
|
189
|
+
when "sid" then @sid_command = true
|
|
190
|
+
when "c64" then @family = :c64
|
|
191
|
+
when "vic20" then @family = :vic20
|
|
192
|
+
else return
|
|
193
|
+
end
|
|
167
194
|
args.shift
|
|
168
|
-
@sid_command = true
|
|
169
195
|
end
|
|
170
196
|
|
|
171
197
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
172
198
|
# option of the emulator's window asks for the machine.
|
|
173
199
|
def play_lone_tune
|
|
174
|
-
return if @sid_command || headless? || !@window_only.empty? || @media_path.nil?
|
|
200
|
+
return if @sid_command || @family == :vic20 || headless? || !@window_only.empty? || @media_path.nil?
|
|
175
201
|
|
|
176
202
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
177
203
|
end
|
|
@@ -222,7 +248,9 @@ module Badline
|
|
|
222
248
|
case name
|
|
223
249
|
when "--subtune" then @subtune = number(flag, value)
|
|
224
250
|
when "--sid" then @sid_model = sid_model_for(value)
|
|
251
|
+
when "--model" then @models << value
|
|
225
252
|
when "--reu" then @reu = reu_size(value)
|
|
253
|
+
when "--ram" then @ram = ram_configuration(value)
|
|
226
254
|
when "--frames" then @frames = number(flag, value)
|
|
227
255
|
when "--screenshot" then @screenshot = value
|
|
228
256
|
when "--save-snapshot" then @save_snapshot = value
|
|
@@ -244,7 +272,7 @@ module Badline
|
|
|
244
272
|
when "--no-vsync" then @vsync = false
|
|
245
273
|
when "--verbose" then @verbose = true
|
|
246
274
|
when "--true-drive" then @true_drive = true
|
|
247
|
-
when "--ntsc" then @
|
|
275
|
+
when "--ntsc" then @models << "ntsc"
|
|
248
276
|
when "--unpaced" then @paced = false
|
|
249
277
|
when "--disable-jit" then @jit = false
|
|
250
278
|
when "--help" then @help = true
|
|
@@ -256,31 +284,6 @@ module Badline
|
|
|
256
284
|
end
|
|
257
285
|
end
|
|
258
286
|
|
|
259
|
-
def sid_model_for(value)
|
|
260
|
-
return if value == "auto"
|
|
261
|
-
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
262
|
-
|
|
263
|
-
SID_MODELS[value]
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def reu_size(value)
|
|
267
|
-
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
268
|
-
|
|
269
|
-
value.to_i
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
def number(flag, value)
|
|
273
|
-
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(/\A\d+\z/)
|
|
274
|
-
|
|
275
|
-
value.to_i
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def decimal(flag, value)
|
|
279
|
-
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(DECIMAL)
|
|
280
|
-
|
|
281
|
-
value.to_f
|
|
282
|
-
end
|
|
283
|
-
|
|
284
287
|
def media_argument(arg)
|
|
285
288
|
raise Error, "unexpected argument: #{arg}" unless @sid_command || @media_path.nil?
|
|
286
289
|
|
data/lib/badline/region.rb
CHANGED
|
@@ -21,8 +21,8 @@ module Badline
|
|
|
21
21
|
# `sprite_cycle` is Bauer's cycle for sprite 0's p-access, and each
|
|
22
22
|
# sprite after it takes two more. `sprite_display_cycle` is the cycle
|
|
23
23
|
# that turns a sprite's display on or off. `x_hold` is how many pixels
|
|
24
|
-
# the X counter spends past its run,
|
|
25
|
-
# of more than 512 pixels needs them.
|
|
24
|
+
# the X counter spends past its run, going over $180-$187 a second
|
|
25
|
+
# time, and a raster of more than 512 pixels needs them.
|
|
26
26
|
Profile = Data.define(:name, :clock_hz, :cycles_per_line, :lines_per_frame, :hblank, :vblank,
|
|
27
27
|
:display_x_bounds, :crop, :mains_hz, :sprite_cycle, :sprite_display_cycle,
|
|
28
28
|
:x_hold)
|
|
@@ -40,8 +40,8 @@ module Badline
|
|
|
40
40
|
# The 6567R8 of an NTSC C64: 65 cycles by 263 lines at 1,022,727 Hz,
|
|
41
41
|
# with 60 Hz mains. The sprite fetches and the compares ahead of them
|
|
42
42
|
# run a cycle later than on the 6569 (the spritesteal, spritex and
|
|
43
|
-
# phi1timing testprogs), and the X counter
|
|
44
|
-
#
|
|
43
|
+
# phi1timing testprogs), and the X counter runs over $180-$187 twice,
|
|
44
|
+
# 8 pixels more, as the 6567R8's lightpen dump shows. The
|
|
45
45
|
# blanked lines are the ones VICE's NTSC view leaves out, 12-27. It shows lines 0-11 below
|
|
46
46
|
# line 262, and the crop leaves those out.
|
|
47
47
|
NTSC = Profile.new(
|
|
@@ -65,6 +65,18 @@ module Badline
|
|
|
65
65
|
sprite_cycle: 59, sprite_display_cycle: 58, x_hold: 0
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
+
# The 6572 of the Drean C64, sold in Argentina on PAL-N: 65 cycles by
|
|
69
|
+
# 312 lines at 1,023,440 Hz, with 50 Hz mains. It lays out the line
|
|
70
|
+
# as the 6567R8 does, sprite fetches and X counter hold included
|
|
71
|
+
# (spritescan_drean), and blanks and crops lines as PAL does.
|
|
72
|
+
DREAN = Profile.new(
|
|
73
|
+
name: :drean, clock_hz: 1_023_440, cycles_per_line: 65, lines_per_frame: 312,
|
|
74
|
+
hblank: [63, 9].freeze, vblank: [300, 15].freeze,
|
|
75
|
+
display_x_bounds: [[135, 438].freeze, [128, 447].freeze].freeze,
|
|
76
|
+
crop: [96, 20, 384, 272].freeze, mains_hz: 50,
|
|
77
|
+
sprite_cycle: 59, sprite_display_cycle: 59, x_hold: 8
|
|
78
|
+
)
|
|
79
|
+
|
|
68
80
|
# Whether a column or line lies in a [first, last] blanking span.
|
|
69
81
|
def self.blanked?(position, span)
|
|
70
82
|
first, last = span
|
data/lib/badline/reu/ram.rb
CHANGED
|
@@ -52,8 +52,35 @@ module Badline
|
|
|
52
52
|
@banks.each_index { |number| @banks[number] = input.boolean? ? input.blob : [] }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Every byte, as one string, with a bank the REC hasn't touched at
|
|
56
|
+
# its power-on contents.
|
|
57
|
+
def contents
|
|
58
|
+
power_on = power_on_strings
|
|
59
|
+
Array.new(@banks.length) do |number|
|
|
60
|
+
bank = @banks[number]
|
|
61
|
+
bank.empty? ? power_on[(number >> 1) & 1] : bank.pack("C*")
|
|
62
|
+
end.join
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Takes every byte from a string as long as the RAM. A bank that
|
|
66
|
+
# holds its power-on contents stays untouched.
|
|
67
|
+
def restore(bytes)
|
|
68
|
+
power_on = power_on_strings
|
|
69
|
+
@banks.each_index do |number|
|
|
70
|
+
slice = bytes.byteslice(number * BANK_SIZE, BANK_SIZE).to_s
|
|
71
|
+
@banks[number] = slice == power_on[(number >> 1) & 1] ? [] : slice.bytes
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
55
75
|
private
|
|
56
76
|
|
|
77
|
+
def power_on_strings
|
|
78
|
+
[0, 1].map do |half|
|
|
79
|
+
@power_on[half] = power_on_bank(half == 1) if @power_on[half].empty?
|
|
80
|
+
@power_on[half].pack("C*")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
57
84
|
# A bank takes its power-on contents the first time it is touched,
|
|
58
85
|
# so an REU costs only the memory a program uses.
|
|
59
86
|
def bank(number)
|
|
@@ -32,6 +32,33 @@ module Badline
|
|
|
32
32
|
@ram.load_state(input)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# The sixteen bytes the REC's registers read from $DF00, without
|
|
36
|
+
# what reading the status does, as VICE's REU1764 module holds them.
|
|
37
|
+
def register_file = Array.new(16) { |register| register_value(register) }
|
|
38
|
+
|
|
39
|
+
# Sets the registers from a register file, as writing them would but
|
|
40
|
+
# without starting a transfer: each address and the length set both
|
|
41
|
+
# the counter and the value it starts from. A command with the
|
|
42
|
+
# execute bit set and the $FF00 trigger on arms the REC.
|
|
43
|
+
def restore_registers(values)
|
|
44
|
+
@status = (values[STATUS] & (INTERRUPT_PENDING | END_OF_BLOCK | FAULT)) | @chips
|
|
45
|
+
@command = values[COMMAND]
|
|
46
|
+
@c64 = @c64_start = values[C64_LOW] | (values[C64_HIGH] << 8)
|
|
47
|
+
@expansion = @expansion_start = values[EXPANSION_LOW] | (values[EXPANSION_HIGH] << 8)
|
|
48
|
+
@bank = @bank_start = values[EXPANSION_BANK] & @bank_bits
|
|
49
|
+
@length = @length_start = values[LENGTH_LOW] | (values[LENGTH_HIGH] << 8)
|
|
50
|
+
@interrupt_mask = values[INTERRUPT_MASK] | 0x1f
|
|
51
|
+
@address_control = values[ADDRESS_CONTROL] | 0x3f
|
|
52
|
+
@armed = @command.anybits?(EXECUTE) && @command.nobits?(NO_FF00_TRIGGER)
|
|
53
|
+
@requested = @running = @ba_was_low = false
|
|
54
|
+
interrupt(@status.anybits?(INTERRUPT_PENDING))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The RAM, every byte of it, as one string.
|
|
58
|
+
def ram_contents = @ram.contents
|
|
59
|
+
|
|
60
|
+
def restore_ram(bytes) = @ram.restore(bytes)
|
|
61
|
+
|
|
35
62
|
private
|
|
36
63
|
|
|
37
64
|
def load_registers(input)
|