badline 0.11.1 → 0.12.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 +26 -0
- data/CLAUDE.md +8 -7
- data/CONTRIBUTING.md +7 -6
- data/README.md +12 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/address_bus/saved_state.rb +4 -10
- data/lib/badline/address_bus.rb +8 -27
- data/lib/badline/audio/bare_player.rb +1 -1
- data/lib/badline/c128/bus.rb +17 -52
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128/drives.rb +35 -22
- data/lib/badline/c128/fast_serial.rb +1 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +9 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +22 -0
- data/lib/badline/c128/z80.rb +49 -0
- data/lib/badline/c128/z80_pages.rb +118 -0
- data/lib/badline/c128/z80_turns.rb +106 -0
- data/lib/badline/c128.rb +21 -83
- data/lib/badline/cartridge/action_replay.rb +2 -2
- data/lib/badline/cartridge/comal80.rb +1 -7
- data/lib/badline/cartridge/dinamic.rb +0 -8
- data/lib/badline/cartridge/final_cartridge3.rb +6 -6
- data/lib/badline/cartridge/fun_play.rb +0 -8
- data/lib/badline/cartridge/game_system.rb +0 -8
- data/lib/badline/cartridge/kcs_power.rb +10 -7
- data/lib/badline/cartridge/mach5.rb +2 -4
- data/lib/badline/cartridge/magic_desk.rb +0 -8
- data/lib/badline/cartridge/ocean.rb +0 -8
- data/lib/badline/cartridge/pagefox.rb +2 -2
- data/lib/badline/cartridge/retro_replay.rb +7 -6
- data/lib/badline/cartridge/rex_utility.rb +2 -6
- data/lib/badline/cartridge/rgcd.rb +2 -2
- data/lib/badline/cartridge/simons_basic.rb +0 -5
- data/lib/badline/cartridge/super_games.rb +4 -2
- data/lib/badline/cartridge/westermann.rb +2 -6
- data/lib/badline/cartridge.rb +37 -9
- data/lib/badline/cia/fast_forward.rb +21 -3
- data/lib/badline/cia.rb +8 -5
- data/lib/badline/computer/attachments.rb +14 -95
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +69 -0
- data/lib/badline/computer.rb +9 -68
- data/lib/badline/core.rb +4 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive/core.rb +8 -33
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/drive/idle.rb +1 -11
- data/lib/badline/drive/orbit.rb +2 -5
- data/lib/badline/drive/sleep.rb +0 -2
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive.rb +2 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/drive1571/wd1770.rb +94 -8
- data/lib/badline/drive1571.rb +8 -58
- data/lib/badline/drive1581/bus.rb +137 -0
- data/lib/badline/drive1581/cpu.rb +11 -0
- data/lib/badline/drive1581/disk.rb +135 -0
- data/lib/badline/drive1581/disk_state.rb +87 -0
- data/lib/badline/drive1581/mechanism.rb +117 -0
- data/lib/badline/drive1581/serial_port.rb +52 -0
- data/lib/badline/drive1581/slot.rb +78 -0
- data/lib/badline/drive1581/track.rb +100 -0
- data/lib/badline/drive1581/track_bytes.rb +75 -0
- data/lib/badline/drive1581/wd1772/registers.rb +89 -0
- data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
- data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
- data/lib/badline/drive1581/wd1772/state.rb +66 -0
- data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
- data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
- data/lib/badline/drive1581/wd1772/turn.rb +73 -0
- data/lib/badline/drive1581/wd1772.rb +216 -0
- data/lib/badline/drive1581.rb +233 -0
- data/lib/badline/frontend/app.rb +1 -3
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +24 -67
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/keys.rb +33 -8
- data/lib/badline/frontend/media_slots.rb +42 -0
- data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
- data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
- data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
- data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
- data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
- data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
- data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
- data/lib/badline/frontend/menu/theme.rb +18 -0
- data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
- data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
- data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
- data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
- data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
- data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
- data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
- data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
- data/lib/badline/frontend/player/theme.rb +18 -0
- data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
- data/lib/badline/frontend/sound.rb +19 -38
- data/lib/badline/frontend/timeline.rb +3 -14
- data/lib/badline/frontend.rb +24 -20
- data/lib/badline/iec_bus.rb +14 -2
- data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap.rb +1 -1
- data/lib/badline/machine/clocking.rb +50 -0
- data/lib/badline/machine.rb +63 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/disk_list.rb +3 -3
- data/lib/badline/media/disk_set.rb +1 -1
- data/lib/badline/media/extensions.rb +33 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +38 -17
- data/lib/badline/media/tune.rb +35 -0
- data/lib/badline/media/vic20_cartridge.rb +1 -7
- data/lib/badline/media/vic20_media.rb +38 -80
- data/lib/badline/media.rb +90 -81
- data/lib/badline/options/event.rb +4 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +6 -4
- data/lib/badline/snapshot.rb +11 -9
- data/lib/badline/storage/d64_image.rb +15 -7
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/disk_image.rb +1 -1
- data/lib/badline/storage/host_directory.rb +1 -1
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/time_of_day.rb +3 -3
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +1 -1
- data/lib/badline/vic/border_mask.rb +1 -1
- data/lib/badline/vic/collisions.rb +1 -1
- data/lib/badline/vic/color_patches.rb +1 -1
- data/lib/badline/vic/display_state.rb +1 -1
- data/lib/badline/vic/graphics_mode.rb +1 -1
- data/lib/badline/vic/graphics_shifter.rb +1 -1
- data/lib/badline/vic/register_log.rb +1 -1
- data/lib/badline/vic/registers.rb +1 -1
- data/lib/badline/vic/sequencer.rb +1 -1
- data/lib/badline/vic/sequencer_output.rb +1 -1
- data/lib/badline/vic/sequencer_state.rb +1 -1
- data/lib/badline/vic/sprite/internal_bus.rb +1 -1
- data/lib/badline/vic/sprite/saved_state.rb +1 -1
- data/lib/badline/vic/sprite/shifter.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +1 -1
- data/lib/badline/vic/sprite.rb +1 -1
- data/lib/badline/vic/sprites.rb +1 -1
- data/lib/badline/vic/sprites_state.rb +1 -1
- data/lib/badline/vic.rb +6 -5
- data/lib/badline/vic20/attachments.rb +15 -71
- data/lib/badline/vic20/saved_state.rb +2 -30
- data/lib/badline/vic20.rb +4 -37
- data/lib/badline/z80/core.rb +2 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- metadata +61 -23
- data/lib/badline/cycleable.rb +0 -38
- /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
- /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
- /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
- /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
|
@@ -14,33 +14,33 @@ module Badline
|
|
|
14
14
|
# frame that would take the queue past LIMIT is dropped whole, which
|
|
15
15
|
# only happens unpaced.
|
|
16
16
|
#
|
|
17
|
-
# Each frame's samples go to
|
|
18
|
-
#
|
|
19
|
-
#
|
|
17
|
+
# Each frame's samples go to a mono AudioDevice. The chip's cycles are
|
|
18
|
+
# converted at `clock_hz`, the machine's clock, so an NTSC or Drean
|
|
19
|
+
# machine plays at its own pitch.
|
|
20
20
|
class Sound
|
|
21
21
|
RATE = 44_100
|
|
22
22
|
AHEAD = 0.08
|
|
23
23
|
LIMIT = 0.25
|
|
24
24
|
|
|
25
|
-
attr_reader :
|
|
25
|
+
attr_reader :underruns, :dropped, :queued, :low, :high
|
|
26
26
|
|
|
27
27
|
def initialize(source, clock_hz, wanted, verbose)
|
|
28
28
|
@source = source
|
|
29
29
|
@clock_hz = clock_hz
|
|
30
30
|
@verbose = verbose
|
|
31
|
-
@device =
|
|
32
|
-
@rate = RATE
|
|
31
|
+
@device = AudioDevice.new(channels: 1, rate: RATE, samples: 512)
|
|
33
32
|
@started = false
|
|
34
33
|
@muted = false
|
|
35
34
|
@underruns = 0
|
|
36
35
|
@dropped = 0
|
|
37
36
|
@queued = 0
|
|
38
|
-
@buffer = IO::Buffer.new(4096)
|
|
39
37
|
reset_levels
|
|
40
38
|
open_device if wanted
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
def
|
|
41
|
+
def rate = @device.rate
|
|
42
|
+
|
|
43
|
+
def on? = @device.open?
|
|
44
44
|
|
|
45
45
|
# Plays another machine's sound from here on, at that machine's clock.
|
|
46
46
|
def switch(source, clock_hz)
|
|
@@ -60,7 +60,7 @@ module Badline
|
|
|
60
60
|
return if @muted || samples.empty?
|
|
61
61
|
|
|
62
62
|
level = queued_seconds
|
|
63
|
-
if level + (samples.size.to_f /
|
|
63
|
+
if level + (samples.size.to_f / rate) > LIMIT
|
|
64
64
|
@dropped += samples.size
|
|
65
65
|
else
|
|
66
66
|
play(samples, level)
|
|
@@ -80,7 +80,7 @@ module Badline
|
|
|
80
80
|
return unless @muted
|
|
81
81
|
|
|
82
82
|
halt
|
|
83
|
-
|
|
83
|
+
@device.clear
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def reset_levels
|
|
@@ -89,32 +89,22 @@ module Badline
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def close
|
|
92
|
-
|
|
92
|
+
@device.close
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
private
|
|
96
96
|
|
|
97
97
|
def open_device
|
|
98
|
-
|
|
99
|
-
puts "No sound: #{SDL.SDL_GetError}"
|
|
100
|
-
return
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
SDL.spec_freq(SDL.wanted, RATE)
|
|
104
|
-
SDL.spec_format(SDL.wanted, SDL::AUDIO_S16LSB)
|
|
105
|
-
SDL.spec_channels(SDL.wanted, 1)
|
|
106
|
-
SDL.spec_samples(SDL.wanted, 512)
|
|
107
|
-
@device = SDL.SDL_OpenAudioDevice(nil, 0, SDL.wanted, SDL.obtained, SDL::ALLOW_FREQUENCY_CHANGE)
|
|
98
|
+
@device.open(false)
|
|
108
99
|
return puts "No sound: #{SDL.SDL_GetError}" unless on?
|
|
109
100
|
|
|
110
|
-
@rate = SDL.read_i32(SDL.obtained)
|
|
111
101
|
record
|
|
112
|
-
puts "Sound at #{
|
|
102
|
+
puts "Sound at #{rate} Hz" if @verbose
|
|
113
103
|
end
|
|
114
104
|
|
|
115
|
-
def record = @source.record(rate: @rate, clock_hz: @clock_hz)
|
|
105
|
+
def record = @source.record(rate: @device.rate, clock_hz: @clock_hz)
|
|
116
106
|
|
|
117
|
-
def queued_seconds =
|
|
107
|
+
def queued_seconds = @device.queued_seconds
|
|
118
108
|
|
|
119
109
|
def play(samples, level)
|
|
120
110
|
@low = level if level < @low
|
|
@@ -126,29 +116,20 @@ module Badline
|
|
|
126
116
|
end
|
|
127
117
|
|
|
128
118
|
def queue(samples)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@buffer.resize(bytes) if bytes > @buffer.size
|
|
132
|
-
buffer = @buffer
|
|
133
|
-
i = 0
|
|
134
|
-
while i < count
|
|
135
|
-
buffer.set_value(:s16, i * 2, samples[i])
|
|
136
|
-
i += 1
|
|
137
|
-
end
|
|
138
|
-
SDL.SDL_QueueAudio(@device, buffer, bytes)
|
|
139
|
-
@queued += count
|
|
119
|
+
@device.queue(samples)
|
|
120
|
+
@queued += samples.size
|
|
140
121
|
end
|
|
141
122
|
|
|
142
123
|
def start
|
|
143
124
|
return if @started
|
|
144
125
|
|
|
145
126
|
@started = true
|
|
146
|
-
|
|
127
|
+
@device.start
|
|
147
128
|
end
|
|
148
129
|
|
|
149
130
|
def halt
|
|
150
131
|
@started = false
|
|
151
|
-
|
|
132
|
+
@device.pause
|
|
152
133
|
end
|
|
153
134
|
|
|
154
135
|
def underrun!
|
|
@@ -183,12 +183,11 @@ module Badline
|
|
|
183
183
|
return puts "No #{what} to eject" unless ejectable?(computer, what)
|
|
184
184
|
|
|
185
185
|
if what == "disk"
|
|
186
|
-
eject_disk(computer)
|
|
186
|
+
MediaSlots.eject_disk(computer)
|
|
187
187
|
elsif what == "tape"
|
|
188
188
|
computer.datasette.eject
|
|
189
189
|
else
|
|
190
|
-
computer
|
|
191
|
-
computer.power_cycle!
|
|
190
|
+
MediaSlots.remove_cartridge(computer)
|
|
192
191
|
end
|
|
193
192
|
puts "Ejected the #{what}"
|
|
194
193
|
end
|
|
@@ -200,17 +199,7 @@ module Badline
|
|
|
200
199
|
return !computer.datasette.tape.nil? if what == "tape"
|
|
201
200
|
return !computer.address_bus.cartridge.nil? if what == "cartridge"
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
drive.nil? ? computer.mounted? : !drive.disk.nil?
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def eject_disk(computer)
|
|
208
|
-
drive = Media::TrueDrive.drive(computer)
|
|
209
|
-
if drive.nil?
|
|
210
|
-
computer.unmount
|
|
211
|
-
else
|
|
212
|
-
drive.insert(nil)
|
|
213
|
-
end
|
|
202
|
+
MediaSlots.disk?(computer)
|
|
214
203
|
end
|
|
215
204
|
end
|
|
216
205
|
end
|
data/lib/badline/frontend.rb
CHANGED
|
@@ -9,7 +9,9 @@ require "badline/frontend/drive_led"
|
|
|
9
9
|
require "badline/frontend/keys"
|
|
10
10
|
require "badline/frontend/snapshots"
|
|
11
11
|
require "badline/frontend/screenshot"
|
|
12
|
+
require "badline/frontend/media_slots"
|
|
12
13
|
require "badline/frontend/timeline"
|
|
14
|
+
require "badline/frontend/audio_device"
|
|
13
15
|
require "badline/frontend/sound"
|
|
14
16
|
require "badline/frontend/pacer"
|
|
15
17
|
require "badline/frontend/frame_report"
|
|
@@ -20,24 +22,26 @@ require "badline/frontend/machine_window"
|
|
|
20
22
|
require "badline/frontend/app"
|
|
21
23
|
require "badline/frontend/audio_sink"
|
|
22
24
|
require "badline/frontend/painter"
|
|
23
|
-
require "badline/frontend/
|
|
24
|
-
require "badline/frontend/
|
|
25
|
-
require "badline/frontend/
|
|
26
|
-
require "badline/frontend/
|
|
27
|
-
require "badline/frontend/
|
|
25
|
+
require "badline/frontend/player/theme"
|
|
26
|
+
require "badline/frontend/player/sid_history"
|
|
27
|
+
require "badline/frontend/player/sid_view"
|
|
28
|
+
require "badline/frontend/player/filter_view"
|
|
29
|
+
require "badline/frontend/player/scope"
|
|
30
|
+
require "badline/frontend/player/visualizer_view"
|
|
28
31
|
require "badline/frontend/buttons"
|
|
29
|
-
require "badline/frontend/player_state"
|
|
30
|
-
require "badline/frontend/player_header"
|
|
31
|
-
require "badline/frontend/player_footer"
|
|
32
|
-
require "badline/frontend/info_view"
|
|
33
|
-
require "badline/frontend/player_commands"
|
|
34
|
-
require "badline/frontend/player_screen"
|
|
35
|
-
require "badline/frontend/
|
|
36
|
-
require "badline/frontend/
|
|
37
|
-
require "badline/frontend/
|
|
38
|
-
require "badline/frontend/
|
|
39
|
-
require "badline/frontend/
|
|
40
|
-
require "badline/frontend/
|
|
41
|
-
require "badline/frontend/
|
|
42
|
-
require "badline/frontend/
|
|
43
|
-
require "badline/frontend/
|
|
32
|
+
require "badline/frontend/player/player_state"
|
|
33
|
+
require "badline/frontend/player/player_header"
|
|
34
|
+
require "badline/frontend/player/player_footer"
|
|
35
|
+
require "badline/frontend/player/info_view"
|
|
36
|
+
require "badline/frontend/player/player_commands"
|
|
37
|
+
require "badline/frontend/player/player_screen"
|
|
38
|
+
require "badline/frontend/player/player_controller"
|
|
39
|
+
require "badline/frontend/menu/theme"
|
|
40
|
+
require "badline/frontend/menu/pause_menu"
|
|
41
|
+
require "badline/frontend/menu/file_browser"
|
|
42
|
+
require "badline/frontend/menu/confirmation"
|
|
43
|
+
require "badline/frontend/menu/name_field"
|
|
44
|
+
require "badline/frontend/menu/menu_dialogs"
|
|
45
|
+
require "badline/frontend/menu/menu_media"
|
|
46
|
+
require "badline/frontend/menu/menu_ports"
|
|
47
|
+
require "badline/frontend/menu/menu_pages"
|
data/lib/badline/iec_bus.rb
CHANGED
|
@@ -15,7 +15,9 @@ module Badline
|
|
|
15
15
|
# A drive drives CLK and DATA from VIA 1's PB3 and PB1, the same way. It
|
|
16
16
|
# also pulls DATA while ATN IN differs from its ATN acknowledge on PB4
|
|
17
17
|
# (an XOR gate), which answers ATN in hardware before the DOS gets to it.
|
|
18
|
-
# SerialPort reads the lines back into VIA 1.
|
|
18
|
+
# SerialPort reads the lines back into VIA 1. A drive whose
|
|
19
|
+
# serial_output sets DRIVE_ATN_GATED pulls DATA that way only while
|
|
20
|
+
# ATN is low, as the 1581's NAND does.
|
|
19
21
|
#
|
|
20
22
|
# Fast serial adds SRQ, which the C64 leaves alone. A C128 and a 1571
|
|
21
23
|
# clock bytes over it from a CIA's CNT, with the bits on DATA from its
|
|
@@ -48,6 +50,10 @@ module Badline
|
|
|
48
50
|
DRIVE_FAST_DATA = 0x100
|
|
49
51
|
DRIVE_FAST_SRQ = 0x200
|
|
50
52
|
|
|
53
|
+
# A drive's serial_output bit whose ATN acknowledge pulls DATA only
|
|
54
|
+
# while ATN is low.
|
|
55
|
+
DRIVE_ATN_GATED = 0x400
|
|
56
|
+
|
|
51
57
|
attr_reader :drives, :host_lines
|
|
52
58
|
|
|
53
59
|
# The lines the host's fast serial pins pull, as DATA and SRQ bits.
|
|
@@ -119,7 +125,7 @@ module Badline
|
|
|
119
125
|
while i < @drives.length
|
|
120
126
|
drive = @drives[i].serial_output
|
|
121
127
|
low |= CLK if drive.anybits?(DRIVE_CLK_OUT)
|
|
122
|
-
low |= DATA if drive.anybits?(DRIVE_DATA_OUT) ||
|
|
128
|
+
low |= DATA if drive.anybits?(DRIVE_DATA_OUT) || acknowledging?(drive, atn)
|
|
123
129
|
i += 1
|
|
124
130
|
end
|
|
125
131
|
low
|
|
@@ -127,6 +133,12 @@ module Badline
|
|
|
127
133
|
|
|
128
134
|
def atn_low?(host = @host_lines) = host.anybits?(HOST_ATN_OUT)
|
|
129
135
|
|
|
136
|
+
# Whether a drive's ATN acknowledge pulls DATA, with its serial_output
|
|
137
|
+
# +drive+, while ATN is +atn+.
|
|
138
|
+
def acknowledging?(drive, atn)
|
|
139
|
+
atn != drive.anybits?(DRIVE_ATNA) && (atn || drive.nobits?(DRIVE_ATN_GATED))
|
|
140
|
+
end
|
|
141
|
+
|
|
130
142
|
def clk_low? = low_lines.anybits?(CLK)
|
|
131
143
|
|
|
132
144
|
def data_low? = low_lines.anybits?(DATA)
|
|
@@ -12,7 +12,7 @@ module Badline
|
|
|
12
12
|
# below $0334, into the zero page, the stack or the KERNAL vectors: the
|
|
13
13
|
# ROM's byte loop then loads it through the serial traps, and a loader
|
|
14
14
|
# that overwrites ISTOP takes over mid-load as it does on a real drive.
|
|
15
|
-
class Load <
|
|
15
|
+
class Load < FileRoutine
|
|
16
16
|
# ST bits at $90
|
|
17
17
|
EOI = 0x40
|
|
18
18
|
VERIFY_MISMATCH = 0x10
|
|
@@ -15,7 +15,7 @@ module Badline
|
|
|
15
15
|
# write: the drive stops listening, so ST reads DEVICE NOT PRESENT,
|
|
16
16
|
# and the ROM returns without an error. A "@" before the drive prefix
|
|
17
17
|
# writes over a file of the same name.
|
|
18
|
-
class Save <
|
|
18
|
+
class Save < FileRoutine
|
|
19
19
|
SECONDARY = 0x61
|
|
20
20
|
|
|
21
21
|
# ST bit at $90
|
data/lib/badline/kernal_trap.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "badline/kernal_trap/layout"
|
|
|
4
4
|
require "badline/kernal_trap/routine"
|
|
5
5
|
require "badline/kernal_trap/channel"
|
|
6
6
|
require "badline/kernal_trap/drive"
|
|
7
|
-
require "badline/kernal_trap/
|
|
7
|
+
require "badline/kernal_trap/file_routine"
|
|
8
8
|
require "badline/kernal_trap/load"
|
|
9
9
|
require "badline/kernal_trap/save"
|
|
10
10
|
require "badline/kernal_trap/serial"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Machine
|
|
5
|
+
# Running a machine from outside its own #cycle!, and the handlers that
|
|
6
|
+
# wait for its KERNAL to boot. Computer, Vic20 and C128 each include it
|
|
7
|
+
# and answer #cycle!, #cycles and #init_threshold, the cycle at which
|
|
8
|
+
# #on_init's handlers run, and keep the handlers in @init_handlers.
|
|
9
|
+
module Clocking
|
|
10
|
+
# Runs `count` cycles, one #cycle! after another.
|
|
11
|
+
def run_cycles(count)
|
|
12
|
+
i = 0
|
|
13
|
+
while i < count
|
|
14
|
+
cycle!
|
|
15
|
+
i += 1
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Runs cycles until the block returns true or the cycle count passes
|
|
20
|
+
# `limit`, checking before each cycle.
|
|
21
|
+
def run_until(limit)
|
|
22
|
+
cycle! until yield || @cycles > limit
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Runs the block once the KERNAL has booted (see init_threshold), or
|
|
26
|
+
# now if it has.
|
|
27
|
+
def on_init(&block)
|
|
28
|
+
if booting?
|
|
29
|
+
@init_handlers << block
|
|
30
|
+
else
|
|
31
|
+
block.call
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def booting?
|
|
42
|
+
@cycles < init_threshold
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_init
|
|
46
|
+
@init_handlers.each(&:call)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/badline/machine.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/machine/clocking"
|
|
4
|
+
|
|
3
5
|
module Badline
|
|
4
6
|
# = Machine
|
|
5
7
|
#
|
|
@@ -25,8 +27,9 @@ module Badline
|
|
|
25
27
|
# +mounted?+, +datasette+, +attach_cartridge+,
|
|
26
28
|
# +press_cartridge_button+, +release_cartridge_button+,
|
|
27
29
|
# +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
|
|
28
|
-
# for the drive Media::TrueDrive plugs in,
|
|
29
|
-
# +
|
|
30
|
+
# for the drive Media::TrueDrive plugs in, +attach_drive1581+,
|
|
31
|
+
# +drive1581+ and +plug_drive1581+ for a 1581 (Drive1581::Slot), and
|
|
32
|
+
# +address_bus+ for its +cartridge+ and +detach_cartridge+.
|
|
30
33
|
# - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
|
|
31
34
|
# +install_debug_register+ and +capture_output+.
|
|
32
35
|
#
|
|
@@ -34,8 +37,8 @@ module Badline
|
|
|
34
37
|
# input parts, with its one joystick as both +joystick1+ and +joystick2+
|
|
35
38
|
# and nil for +control_ports+, and of the rest +ram+, +load_prg+,
|
|
36
39
|
# +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
|
|
37
|
-
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+,
|
|
38
|
-
# +install_debug_register+ and +capture_output+.
|
|
40
|
+
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+, the
|
|
41
|
+
# 1581's, +install_debug_register+ and +capture_output+.
|
|
39
42
|
#
|
|
40
43
|
# The C128 answers all but the cartridge button, and besides +mode+,
|
|
41
44
|
# :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
|
|
@@ -43,16 +46,22 @@ module Badline
|
|
|
43
46
|
# +press_caps_lock+, +release_caps_lock+, +press_display_key+,
|
|
44
47
|
# +release_display_key+, and +attach_drive1571+ and +drive1571+ for the
|
|
45
48
|
# C128D's 1571, its true drive.
|
|
49
|
+
#
|
|
50
|
+
# Each machine includes Machine::Clocking for the clocking part.
|
|
46
51
|
module Machine
|
|
52
|
+
# Media the machine named doesn't take.
|
|
53
|
+
class Unsupported < ArgumentError; end
|
|
54
|
+
|
|
47
55
|
# A new machine of `family`, :c64, :vic20 or :c128, the model of it
|
|
48
56
|
# `model` names. A C64's model is one of Model::ALL, with its SID
|
|
49
57
|
# `sid_model` when not nil and an REU of `reu` K when not nil. A
|
|
50
58
|
# VIC-20's is "pal", with the RAM expansion `ram` names
|
|
51
59
|
# (Vic20::Bus::RAM_CONFIGURATIONS), unexpanded when nil. A C128's is
|
|
52
|
-
# one of C128::Model::ALL, with its SID `sid_model` when not nil
|
|
60
|
+
# one of C128::Model::ALL, with its SID `sid_model` when not nil, built
|
|
61
|
+
# for C64 mode.
|
|
53
62
|
def self.build(family, model:, sid_model: nil, reu: nil, ram: nil)
|
|
54
63
|
return vic20(model, ram) if family == :vic20
|
|
55
|
-
return
|
|
64
|
+
return c128(model, sid_model, :c64) if family == :c128
|
|
56
65
|
raise ArgumentError, "no machine family named #{family}" unless family == :c64
|
|
57
66
|
|
|
58
67
|
profile = Model.named(model)
|
|
@@ -61,11 +70,59 @@ module Badline
|
|
|
61
70
|
kernal: profile.kernal, datasette: profile.datasette, board: profile.board)
|
|
62
71
|
end
|
|
63
72
|
|
|
73
|
+
# The machine the command line's `options` name, to start `media`, a
|
|
74
|
+
# path or nil. A C64 or a C128 without a SID named takes the one a .sid
|
|
75
|
+
# tune names. The C128 is built for C128 mode, with C= held through the
|
|
76
|
+
# reset, so its KERNAL starts C64 mode, for --c64 (+c64_mode?+) or media
|
|
77
|
+
# only C64 mode runs: a .sid tune or a program that loads at the C64's
|
|
78
|
+
# BASIC start. A VIC-20 without RAM named gets the expansion the media
|
|
79
|
+
# needs (Media::Vic20Media.ram_for) and yields a line saying so. Raises
|
|
80
|
+
# Unsupported for media a VIC-20 doesn't take.
|
|
81
|
+
def self.for_options(options, media, &)
|
|
82
|
+
family = options.family
|
|
83
|
+
return vic20_for(media, options.model, options.ram, &) if family == :vic20
|
|
84
|
+
|
|
85
|
+
sid_model = options.sid_model || Media.sid_model(media, otherwise: nil)
|
|
86
|
+
return build(:c64, model: options.model, sid_model:, reu: options.reu) unless family == :c128
|
|
87
|
+
|
|
88
|
+
machine = c128(options.model, sid_model, :c128)
|
|
89
|
+
machine.hold_commodore_key if options.c64_mode? || c64_media?(media)
|
|
90
|
+
machine
|
|
91
|
+
end
|
|
92
|
+
|
|
64
93
|
def self.vic20(model, ram)
|
|
65
94
|
raise ArgumentError, "no VIC-20 model named #{model}" unless model == "pal"
|
|
66
95
|
|
|
67
96
|
Vic20.new(ram: ram || :unexpanded)
|
|
68
97
|
end
|
|
69
98
|
private_class_method :vic20
|
|
99
|
+
|
|
100
|
+
def self.c128(model, sid_model, mode) = C128.new(model:, sid_model:, mode:)
|
|
101
|
+
private_class_method :c128
|
|
102
|
+
|
|
103
|
+
def self.vic20_for(media, model, ram)
|
|
104
|
+
unless media.nil?
|
|
105
|
+
unsupported = Media::Vic20Media::UNSUPPORTED.include?(File.extname(media).downcase)
|
|
106
|
+
raise Unsupported, "doesn't go in a VIC-20" if unsupported
|
|
107
|
+
|
|
108
|
+
unless ram
|
|
109
|
+
ram = Media::Vic20Media.ram_for(media)
|
|
110
|
+
yield "RAM expansion: #{ram}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
build(:vic20, model:, ram:)
|
|
114
|
+
end
|
|
115
|
+
private_class_method :vic20_for
|
|
116
|
+
|
|
117
|
+
def self.c64_media?(media)
|
|
118
|
+
return false if media.nil? || !File.file?(media)
|
|
119
|
+
|
|
120
|
+
extension = File.extname(media).downcase
|
|
121
|
+
return true if extension == ".sid"
|
|
122
|
+
return false unless %w[.prg .p00].include?(extension)
|
|
123
|
+
|
|
124
|
+
[Media::BASIC_START, Media::BASIC_START - 1].include?(Storage::P00.load_address(media))
|
|
125
|
+
end
|
|
126
|
+
private_class_method :c64_media?
|
|
70
127
|
end
|
|
71
128
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Media
|
|
5
|
+
# A C128 boot disk. At power-on the C128 KERNAL reads track 1, sector 0
|
|
6
|
+
# of the disk in device 8 and runs it when it starts with "CBM", as boot
|
|
7
|
+
# games have it. The 1581's DOS first loads and runs a USR file named
|
|
8
|
+
# COPYRIGHT CBM 86 when the disk has one, which is how CP/M's 1581 disks
|
|
9
|
+
# hand the KERNAL their boot sector, so a .d81 with that file boots too.
|
|
10
|
+
#
|
|
11
|
+
# On a C128 in C128 mode a boot disk goes in a true drive, a 1581 for a
|
|
12
|
+
# .d81 and the C128D's 1571 otherwise, and nothing is typed, so the
|
|
13
|
+
# KERNAL boots it. Other disks, and other machines, keep the traps and
|
|
14
|
+
# the autostart.
|
|
15
|
+
module BootDisk
|
|
16
|
+
SIGNATURE = "CBM".bytes.freeze
|
|
17
|
+
AUTO_BOOT = "copyright cbm 86"
|
|
18
|
+
TYPES = Extensions.of(%i[disk])
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def takes?(computer, path)
|
|
22
|
+
computer.family == :c128 && computer.mode == :c128 && boot?(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Puts a disk that boots on the computer in a true drive as device
|
|
26
|
+
# 8, write-protected with `disk: { read_only: true }`, for the KERNAL
|
|
27
|
+
# to boot at power-on, and says so. Nil for any other disk.
|
|
28
|
+
def attach(computer, path, disk: {}, **)
|
|
29
|
+
return unless takes?(computer, path)
|
|
30
|
+
|
|
31
|
+
read_only = disk.fetch(:read_only, false)
|
|
32
|
+
"#{TrueDrive.insert(computer, path, read_only:)}, to boot at power-on"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Whether the disk image at path boots: its boot sector, or on a
|
|
36
|
+
# .d81 the 1581's auto-boot file.
|
|
37
|
+
def boot?(path)
|
|
38
|
+
type = File.extname(path).downcase
|
|
39
|
+
return false unless TYPES.include?(type) && File.file?(path)
|
|
40
|
+
|
|
41
|
+
image = Media.open_storage(path, { read_only: true })
|
|
42
|
+
block = image.read_block(1, 0)
|
|
43
|
+
return true if block && block[0, 3] == SIGNATURE
|
|
44
|
+
|
|
45
|
+
type == ".d81" && !image.read_file(AUTO_BOOT, type: :usr).nil?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -11,9 +11,9 @@ module Badline
|
|
|
11
11
|
# A list naming no disk image that's there.
|
|
12
12
|
class Error < ArgumentError; end
|
|
13
13
|
|
|
14
|
-
EXTENSIONS = %
|
|
14
|
+
EXTENSIONS = Extensions.of(%i[disk_list])
|
|
15
15
|
|
|
16
|
-
def self.list?(path) =
|
|
16
|
+
def self.list?(path) = Extensions.kind(path) == :disk_list
|
|
17
17
|
|
|
18
18
|
# The disk `path` puts in the drive: a list's first disk, or the
|
|
19
19
|
# path itself.
|
|
@@ -32,7 +32,7 @@ module Badline
|
|
|
32
32
|
found = []
|
|
33
33
|
entries(list).each do |entry|
|
|
34
34
|
disk = entry.start_with?("/") ? entry : File.join(directory, entry)
|
|
35
|
-
found << disk if
|
|
35
|
+
found << disk if Extensions::DISKS.include?(File.extname(disk).downcase) && File.file?(disk)
|
|
36
36
|
end
|
|
37
37
|
found
|
|
38
38
|
end
|
|
@@ -13,7 +13,7 @@ module Badline
|
|
|
13
13
|
# ahead of the names.
|
|
14
14
|
module DiskSet
|
|
15
15
|
KINDS = %w[disk disc side part].freeze
|
|
16
|
-
EXTENSIONS =
|
|
16
|
+
EXTENSIONS = Extensions::DISKS
|
|
17
17
|
|
|
18
18
|
# The paths of the set's disks in order, or just `path` when it
|
|
19
19
|
# belongs to none. A list's set is the disks it lists.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Media
|
|
5
|
+
# The kind of medium each file extension names, which every check of a
|
|
6
|
+
# path's extension reads. A file with any other extension is a program.
|
|
7
|
+
# It loads on its own, for Options.
|
|
8
|
+
#
|
|
9
|
+
# - :disk, a disk image that the traps mount and a true drive reads
|
|
10
|
+
# - :gcr, a disk's raw GCR, which only a true drive reads
|
|
11
|
+
# - :archive, a .t64, which the traps mount read-only
|
|
12
|
+
# - :tape, :cartridge and :tune
|
|
13
|
+
# - :disk_list, a list of the disks of a set (DiskList)
|
|
14
|
+
module Extensions
|
|
15
|
+
KINDS = {
|
|
16
|
+
".d64" => :disk, ".d71" => :disk, ".d81" => :disk, ".g64" => :gcr, ".g71" => :gcr, ".t64" => :archive,
|
|
17
|
+
".tap" => :tape, ".crt" => :cartridge, ".sid" => :tune, ".m3u" => :disk_list, ".vfl" => :disk_list
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# The kind of the medium at `path`, by its extension.
|
|
21
|
+
def self.kind(path) = KINDS.fetch(File.extname(path).downcase, :program)
|
|
22
|
+
|
|
23
|
+
# The extensions of the kinds given.
|
|
24
|
+
def self.of(kinds) = KINDS.keys.select { |extension| kinds.include?(KINDS[extension]) }.freeze
|
|
25
|
+
|
|
26
|
+
# Disk images, which a drive reads.
|
|
27
|
+
DISKS = of(%i[disk gcr])
|
|
28
|
+
|
|
29
|
+
# What goes in device 8 through the traps.
|
|
30
|
+
MOUNTABLE = of(%i[disk archive])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Media
|
|
5
|
+
# A program loading into a C64 or a C128 once it has booted
|
|
6
|
+
# (Media.attach). The VIC-20 has its own (Vic20Basic).
|
|
7
|
+
module Program
|
|
8
|
+
class << self
|
|
9
|
+
# Loads the program where it says, and RUNs one that loads at the
|
|
10
|
+
# start of BASIC.
|
|
11
|
+
def load(computer, data, autostart:)
|
|
12
|
+
load_addr = computer.load_prg(data)
|
|
13
|
+
# Run only makes sense for programs at BASIC start, or a byte ahead
|
|
14
|
+
# of it, where BASIC keeps the zero before its first line. BASIC 7.0
|
|
15
|
+
# starts at $1C01 and keeps the end of its text at $1210.
|
|
16
|
+
start, text_end = computer.family == :c128 && computer.mode == :c128 ? [0x1c01, 0x1210] : [BASIC_START, 0x2d]
|
|
17
|
+
return unless autostart && (load_addr == start || load_addr == start - 1)
|
|
18
|
+
|
|
19
|
+
# The end of the program, where BASIC's variables start, and where
|
|
20
|
+
# the KERNAL's LOAD leaves its end address.
|
|
21
|
+
end_addr = load_addr + data.length - 2
|
|
22
|
+
[text_end, 0xae].each { |pointer| computer.ram.write(pointer, [end_addr & 0xff, end_addr >> 8]) }
|
|
23
|
+
computer.type_text("run\r")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|