badline 0.1.0 → 0.2.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 +185 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +96 -25
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +132 -16
- data/lib/badline/cia/interrupt_register.rb +102 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +138 -88
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +97 -12
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +169 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +109 -168
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +51 -0
- data/lib/badline/kernal_trap/drive/status.rb +49 -0
- data/lib/badline/kernal_trap/drive.rb +277 -0
- data/lib/badline/kernal_trap/file.rb +5 -25
- data/lib/badline/kernal_trap/load.rb +86 -24
- data/lib/badline/kernal_trap/routine.rb +51 -0
- data/lib/badline/kernal_trap/save.rb +46 -14
- data/lib/badline/kernal_trap/serial.rb +171 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/memory.rb +5 -0
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +251 -23
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/d64_image.rb +6 -0
- data/lib/badline/storage/d71_image.rb +4 -0
- data/lib/badline/storage/d81_image.rb +4 -0
- data/lib/badline/storage/disk_image.rb +99 -13
- data/lib/badline/storage/host_directory.rb +30 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +389 -55
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +104 -4
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
# PC traps on the KERNAL's serial bus primitives. Frames addressed to
|
|
6
|
+
# device 8 go to a virtual drive instead of out over the IEC lines, so
|
|
7
|
+
# OPEN/CHKIN/CHRIN reach a CBM DOS, and so do the block reads that
|
|
8
|
+
# loaders make through them. Other devices fall through to the ROM.
|
|
9
|
+
class Serial < Routine
|
|
10
|
+
ROUTINES = {
|
|
11
|
+
0xed09 => :talk,
|
|
12
|
+
0xed0c => :listen,
|
|
13
|
+
0xedb9 => :second,
|
|
14
|
+
0xedc7 => :tksa,
|
|
15
|
+
0xeddd => :ciout,
|
|
16
|
+
0xedef => :untalk,
|
|
17
|
+
0xedfe => :unlisten,
|
|
18
|
+
0xee13 => :acptr
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Frame type in the high nibble of the secondary address
|
|
22
|
+
OPEN = 0xf0
|
|
23
|
+
CLOSE = 0xe0
|
|
24
|
+
|
|
25
|
+
# ST bits at $90
|
|
26
|
+
DEVICE_NOT_PRESENT = 0x80
|
|
27
|
+
EOI = 0x40
|
|
28
|
+
READ_TIMEOUT = 0x02
|
|
29
|
+
|
|
30
|
+
NO_DATA = [0x0d, EOI | READ_TIMEOUT].freeze
|
|
31
|
+
|
|
32
|
+
def initialize(cpu:, bus:, drive:)
|
|
33
|
+
super(cpu:, bus:)
|
|
34
|
+
@drive = drive
|
|
35
|
+
@listening = false
|
|
36
|
+
@talking = false
|
|
37
|
+
@listen_channel = nil
|
|
38
|
+
@talk_channel = nil
|
|
39
|
+
@frame = OPEN
|
|
40
|
+
@buffer = []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def install
|
|
44
|
+
ROUTINES.each do |address, routine|
|
|
45
|
+
@cpu.install_trap(address) { call_routine(routine) if kernal? }
|
|
46
|
+
end
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def call_routine(routine)
|
|
53
|
+
case routine
|
|
54
|
+
when :talk then talk
|
|
55
|
+
when :listen then listen
|
|
56
|
+
when :second then second
|
|
57
|
+
when :tksa then tksa
|
|
58
|
+
when :ciout then ciout
|
|
59
|
+
when :untalk then untalk
|
|
60
|
+
when :unlisten then unlisten
|
|
61
|
+
when :acptr then acptr
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Addressing the bus starts a new frame. The channel number arrives
|
|
66
|
+
# in the secondary address that follows.
|
|
67
|
+
def talk
|
|
68
|
+
@talk_channel = nil
|
|
69
|
+
@talking = @cpu.a == DEVICE
|
|
70
|
+
return_to_caller if @talking
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def listen
|
|
74
|
+
@listen_channel = nil
|
|
75
|
+
@listening = @cpu.a == DEVICE
|
|
76
|
+
return_to_caller if @listening
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def second
|
|
80
|
+
return unless @listening
|
|
81
|
+
|
|
82
|
+
@frame = @cpu.a & 0xf0
|
|
83
|
+
@listen_channel = @cpu.a & 0x0f
|
|
84
|
+
@buffer = []
|
|
85
|
+
return_to_caller
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def tksa
|
|
89
|
+
return unless @talking
|
|
90
|
+
|
|
91
|
+
@talk_channel = @cpu.a & 0x0f
|
|
92
|
+
return_to_caller
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ciout
|
|
96
|
+
return unless @listening
|
|
97
|
+
|
|
98
|
+
@buffer << @cpu.a
|
|
99
|
+
update_status(DEVICE_NOT_PRESENT) unless accepted?
|
|
100
|
+
@cpu.status.carry = false
|
|
101
|
+
return_to_caller
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The drive acknowledges every byte of an open or close frame, but
|
|
105
|
+
# takes data only on a channel it has open. Otherwise nothing holds
|
|
106
|
+
# the data line when a byte starts, so the KERNAL finds no device.
|
|
107
|
+
def accepted?
|
|
108
|
+
[OPEN, CLOSE].include?(@frame) || @drive.listening?(@listen_channel)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def unlisten
|
|
112
|
+
return unless @listening
|
|
113
|
+
|
|
114
|
+
@listening = false
|
|
115
|
+
deliver_frame
|
|
116
|
+
release_bus
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def untalk
|
|
120
|
+
return unless @talking
|
|
121
|
+
|
|
122
|
+
@talking = false
|
|
123
|
+
release_bus
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# UNLSN and UNTLK end by releasing ATN, the clock and the data line on
|
|
127
|
+
# CIA 2's port A, and leave the port's last read in the accumulator.
|
|
128
|
+
def release_bus
|
|
129
|
+
@bus.poke(0xdd00, @bus.peek(0xdd00) & 0xc7)
|
|
130
|
+
@cpu.a = @bus.peek(0xdd00) & 0xdf
|
|
131
|
+
@cpu.status.negative = @cpu.a.anybits?(0x80)
|
|
132
|
+
@cpu.status.zero = @cpu.a.zero?
|
|
133
|
+
return_to_caller
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def acptr
|
|
137
|
+
return unless @talking
|
|
138
|
+
|
|
139
|
+
byte, status = received
|
|
140
|
+
@cpu.a = byte
|
|
141
|
+
update_status(status)
|
|
142
|
+
@cpu.status.carry = false
|
|
143
|
+
return_to_caller
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def deliver_frame
|
|
147
|
+
return unless @listen_channel
|
|
148
|
+
|
|
149
|
+
case @frame
|
|
150
|
+
when OPEN then @drive.open(@listen_channel, @buffer.pack("C*"))
|
|
151
|
+
when CLOSE then @drive.close(@listen_channel)
|
|
152
|
+
else @drive.write(@listen_channel, @buffer)
|
|
153
|
+
end
|
|
154
|
+
@buffer = []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def received
|
|
158
|
+
byte, eoi = @drive.read(@talk_channel)
|
|
159
|
+
return NO_DATA unless byte
|
|
160
|
+
|
|
161
|
+
[byte, eoi ? EOI : 0]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def update_status(bits)
|
|
165
|
+
return if bits.zero?
|
|
166
|
+
|
|
167
|
+
@bus.poke(0x90, @bus.peek(0x90) | bits)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/badline/kernal_trap.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/kernal_trap/routine"
|
|
4
|
+
require "badline/kernal_trap/channel"
|
|
5
|
+
require "badline/kernal_trap/drive"
|
|
3
6
|
require "badline/kernal_trap/file"
|
|
4
7
|
require "badline/kernal_trap/load"
|
|
5
8
|
require "badline/kernal_trap/save"
|
|
9
|
+
require "badline/kernal_trap/serial"
|
data/lib/badline/keyboard.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
|
+
# The 8x8 key matrix spanning CIA 1's two ports: rows on port A, columns on
|
|
5
|
+
# port B.
|
|
6
|
+
#
|
|
7
|
+
# A pressed key shorts its row line to its column line, so whichever side is
|
|
8
|
+
# pulled low drags the other down with it. Scanning works in either
|
|
9
|
+
# direction, and chains of keys sharing a line propagate the pulldown -- the
|
|
10
|
+
# ghost keys real hardware reports.
|
|
4
11
|
class Keyboard
|
|
5
12
|
include IntegerHelper
|
|
6
13
|
|
|
@@ -8,9 +15,10 @@ module Badline
|
|
|
8
15
|
|
|
9
16
|
def initialize
|
|
10
17
|
@keys = []
|
|
18
|
+
@row_masks = nil
|
|
11
19
|
|
|
12
20
|
@matrix = [
|
|
13
|
-
%i[delete return cursor_h f7 f1 f3 cursor_v],
|
|
21
|
+
%i[delete return cursor_h f7 f1 f3 f5 cursor_v],
|
|
14
22
|
%i[3 w a 4 z s e lshift],
|
|
15
23
|
%i[5 r d 6 c f t x],
|
|
16
24
|
%i[7 y g 8 b h u v],
|
|
@@ -22,35 +30,51 @@ module Badline
|
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def press(key)
|
|
25
|
-
|
|
33
|
+
return unless valid_key?(key)
|
|
34
|
+
|
|
35
|
+
@keys << key
|
|
36
|
+
@row_masks = nil
|
|
26
37
|
end
|
|
27
38
|
|
|
28
|
-
def
|
|
29
|
-
|
|
39
|
+
def release(key)
|
|
40
|
+
@keys.reject! { |k| k == key }
|
|
41
|
+
@row_masks = nil
|
|
30
42
|
end
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
# Settles the row (port A) and column (port B) lines against each other
|
|
45
|
+
# until no key contact pulls a line low any more, and returns both.
|
|
46
|
+
def scan(rows, cols)
|
|
47
|
+
return [rows, cols] if keys.empty?
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
loop do
|
|
50
|
+
settled_rows = rows
|
|
51
|
+
settled_cols = cols
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
row_masks.each_with_index do |mask, row|
|
|
54
|
+
bit = 1 << row
|
|
55
|
+
next if mask.zero? || (rows.allbits?(bit) && cols.allbits?(mask))
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
rows &= ~bit
|
|
58
|
+
cols &= ~mask
|
|
42
59
|
end
|
|
43
|
-
end
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
return [rows, cols] if rows == settled_rows && cols == settled_cols
|
|
62
|
+
end
|
|
46
63
|
end
|
|
47
64
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
def read_a(port_a, port_b) = scan(port_a, port_b).first
|
|
66
|
+
|
|
67
|
+
def read_b(port_a, port_b) = scan(port_a, port_b).last
|
|
51
68
|
|
|
52
69
|
private
|
|
53
70
|
|
|
71
|
+
# Columns held down per row, as a bit mask of port B lines.
|
|
72
|
+
def row_masks
|
|
73
|
+
@row_masks ||= matrix.map do |row|
|
|
74
|
+
row.each_with_index.sum { |key, column| keys.include?(key) ? 1 << column : 0 }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
54
78
|
def valid_key?(key)
|
|
55
79
|
matrix.flatten.include?(key)
|
|
56
80
|
end
|
data/lib/badline/media.rb
CHANGED
|
@@ -3,37 +3,75 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
5
|
AUTOSTART = %(lO"*",8,1\rrun\r)
|
|
6
|
+
TAPE_AUTOSTART = %(lO\rrun\r)
|
|
6
7
|
BASIC_START = 0x0801
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
MOUNT_TYPES = {
|
|
9
10
|
".d64" => Storage::D64Image,
|
|
10
11
|
".d71" => Storage::D71Image,
|
|
11
|
-
".d81" => Storage::D81Image
|
|
12
|
+
".d81" => Storage::D81Image,
|
|
13
|
+
".t64" => Storage::T64
|
|
12
14
|
}.freeze
|
|
13
15
|
|
|
14
16
|
class << self
|
|
15
|
-
|
|
17
|
+
# `cartridge` sets the jumpers of a .crt cartridge that has them, such
|
|
18
|
+
# as `{ flash_jumper: true }` for the Retro Replay's flash mode.
|
|
19
|
+
def attach(computer, path, autostart: true, song: nil, cartridge: {})
|
|
16
20
|
if File.directory?(path)
|
|
17
21
|
computer.mount(Storage::HostDirectory.new(path))
|
|
18
22
|
"Mounted #{path} as device 8"
|
|
19
23
|
elsif File.extname(path).downcase == ".crt"
|
|
20
|
-
attach_cartridge(computer, path)
|
|
21
|
-
elsif
|
|
22
|
-
|
|
24
|
+
attach_cartridge(computer, path, cartridge)
|
|
25
|
+
elsif File.extname(path).downcase == ".sid"
|
|
26
|
+
attach_sid(computer, path, autostart:, song:)
|
|
27
|
+
elsif File.extname(path).downcase == ".tap"
|
|
28
|
+
attach_tape(computer, path, autostart:)
|
|
29
|
+
elsif (storage = MOUNT_TYPES[File.extname(path).downcase])
|
|
30
|
+
attach_storage(computer, storage.new(path), path, autostart:)
|
|
23
31
|
else
|
|
24
32
|
attach_prg(computer, path, autostart:)
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
|
|
36
|
+
# The SID a machine for `path` should be built with. A .sid tune names
|
|
37
|
+
# its own; everything else gets the 6581.
|
|
38
|
+
def sid_model(path)
|
|
39
|
+
return :mos6581 unless path && File.extname(path).downcase == ".sid"
|
|
40
|
+
|
|
41
|
+
Storage::SIDFile.new(path).sid_model
|
|
42
|
+
end
|
|
43
|
+
|
|
28
44
|
private
|
|
29
45
|
|
|
30
|
-
def attach_cartridge(computer, path)
|
|
31
|
-
computer.attach_cartridge(Cartridge.from_file(path))
|
|
46
|
+
def attach_cartridge(computer, path, options)
|
|
47
|
+
computer.attach_cartridge(Cartridge.from_file(path, **options))
|
|
32
48
|
"Attached cartridge #{path}"
|
|
33
49
|
end
|
|
34
50
|
|
|
35
|
-
def
|
|
36
|
-
|
|
51
|
+
def attach_sid(computer, path, autostart:, song:)
|
|
52
|
+
tune = Storage::SIDFile.new(path)
|
|
53
|
+
song = (song || tune.start_song).clamp(1, tune.songs)
|
|
54
|
+
computer.on_init { start_tune(computer, tune, autostart:, song:) }
|
|
55
|
+
title = tune.name.empty? ? path : tune.name
|
|
56
|
+
title += " (song #{song})" if tune.songs > 1
|
|
57
|
+
autostart ? "Playing #{title}" : "Loaded #{title}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def start_tune(computer, tune, autostart:, song:)
|
|
61
|
+
computer.ram.write(tune.load_address, tune.data)
|
|
62
|
+
computer.ram.write(tune.driver_address, tune.driver(song:))
|
|
63
|
+
computer.type_text("sys#{tune.driver_address}\r") if autostart
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def attach_tape(computer, path, autostart:)
|
|
67
|
+
computer.datasette.insert(Storage::TAP.new(path))
|
|
68
|
+
computer.datasette.play!
|
|
69
|
+
computer.type_text(TAPE_AUTOSTART) if autostart
|
|
70
|
+
"Inserted #{path} in the datasette"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def attach_storage(computer, storage, path, autostart:)
|
|
74
|
+
computer.mount(storage)
|
|
37
75
|
computer.type_text(AUTOSTART) if autostart
|
|
38
76
|
"Mounted #{path} as device 8"
|
|
39
77
|
end
|
data/lib/badline/memory.rb
CHANGED
|
@@ -26,6 +26,11 @@ module Badline
|
|
|
26
26
|
Array(bytes).each_with_index { |b, i| poke(addr + i, b) }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Refills the whole memory as if newly built with `initial`
|
|
30
|
+
def clear!(initial = [])
|
|
31
|
+
@storage.replace(zero_fill(initial))
|
|
32
|
+
end
|
|
33
|
+
|
|
29
34
|
private
|
|
30
35
|
|
|
31
36
|
def blank_value
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
This software is provided 'as-is', without any express or implied
|
|
2
|
+
warranty. In no event will the authors be held liable for any damages
|
|
3
|
+
arising from the use of this software.
|
|
4
|
+
|
|
5
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
6
|
+
including commercial applications, and to alter it and redistribute it
|
|
7
|
+
freely, subject to the following restrictions:
|
|
8
|
+
|
|
9
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
10
|
+
claim that you wrote the original software. If you use this software
|
|
11
|
+
in a product, an acknowledgment in the product documentation would be
|
|
12
|
+
appreciated but is not required.
|
|
13
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
14
|
+
misrepresented as being the original software.
|
|
15
|
+
3. This notice may not be removed or altered from any source distribution.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
EasyFlash EAPI for the Am29F040
|
|
2
|
+
===============================
|
|
3
|
+
|
|
4
|
+
eapi-am29f040-14 is the EasyFlash flash driver (EAPI) for the AMD Am29F040,
|
|
5
|
+
version 1.4, by Thomas 'skoe' Giesel. It is third-party software, not part
|
|
6
|
+
of badline, and is distributed under the zlib licence in LICENSE.md.
|
|
7
|
+
|
|
8
|
+
It is the unaltered output of assembling the upstream source, a C64 PRG
|
|
9
|
+
(a two-byte load address, $C000, then 768 bytes):
|
|
10
|
+
|
|
11
|
+
Source: https://gitlab.com/easyflash/eapi
|
|
12
|
+
Revision: 1b4ea15d44974d18f6f82156b935f59d2f85596c
|
|
13
|
+
File: eapi-am29f040.s (includes eapi_defs.s)
|
|
14
|
+
Built: acme -l out/eapi-am29f040-14.labels -o out/eapi-am29f040-14 eapi-am29f040.s
|
|
15
|
+
(the repository's Makefile rule, with ACME 0.96.4)
|
|
16
|
+
SHA-256: 4c32d425d7b0a1f659e007bccad5c43603a93236974ef19743b2eefb812ab9ed
|
|
17
|
+
|
|
18
|
+
The source file's notice:
|
|
19
|
+
|
|
20
|
+
(c) 2009-2010 Thomas 'skoe' Giesel
|
|
21
|
+
|
|
22
|
+
This software is provided 'as-is', without any express or implied
|
|
23
|
+
warranty. In no event will the authors be held liable for any damages
|
|
24
|
+
arising from the use of this software.
|
|
25
|
+
|
|
26
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
27
|
+
including commercial applications, and to alter it and redistribute it
|
|
28
|
+
freely, subject to the following restrictions:
|
|
29
|
+
|
|
30
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
31
|
+
claim that you wrote the original software. If you use this software
|
|
32
|
+
in a product, an acknowledgment in the product documentation would be
|
|
33
|
+
appreciated but is not required.
|
|
34
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
35
|
+
misrepresented as being the original software.
|
|
36
|
+
3. This notice may not be removed or altered from any source distribution.
|
|
37
|
+
|
|
38
|
+
When an EasyFlash image carries an EAPI, as its "eapi" signature at $B800
|
|
39
|
+
in bank 0 of ROMH shows, badline replaces those 768 bytes with this driver
|
|
40
|
+
on attach, the way VICE does, so the image drives the flash chip badline
|
|
41
|
+
emulates.
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class SID
|
|
5
|
+
# Reduces the SID's one-sample-per-cycle stream to an audio rate by
|
|
6
|
+
# averaging each window. Point sampling would be cheaper, but the pulse
|
|
7
|
+
# and noise waveforms carry enough energy above the output Nyquist to
|
|
8
|
+
# alias audibly.
|
|
9
|
+
#
|
|
10
|
+
# A sample can stand for several cycles at once, when the DSP has run
|
|
11
|
+
# them as one step.
|
|
12
|
+
class Decimator
|
|
13
|
+
def initialize(clock_hz:, rate:)
|
|
14
|
+
@clock_hz = clock_hz
|
|
15
|
+
@rate = rate
|
|
16
|
+
@phase = 0
|
|
17
|
+
@sum = 0
|
|
18
|
+
@count = 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Cycles until the current window closes, counting the next one as 1.
|
|
22
|
+
def cycles_to_close = (@clock_hz - @phase + @rate - 1) / @rate
|
|
23
|
+
|
|
24
|
+
# Returns the averaged sample when these cycles close a window, and
|
|
25
|
+
# nil otherwise. They must not run past the window's end.
|
|
26
|
+
def push(sample, cycles = 1)
|
|
27
|
+
@sum += sample * cycles
|
|
28
|
+
@count += cycles
|
|
29
|
+
@phase += @rate * cycles
|
|
30
|
+
return unless @phase >= @clock_hz
|
|
31
|
+
|
|
32
|
+
@phase -= @clock_hz
|
|
33
|
+
average
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def average
|
|
39
|
+
value = @sum.fdiv(@count).round
|
|
40
|
+
@sum = 0
|
|
41
|
+
@count = 0
|
|
42
|
+
value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|