badline 0.11.1 → 0.12.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 +18 -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/c128/bus.rb +4 -17
- 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 +16 -19
- 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 +4 -56
- data/lib/badline/computer/true_drives.rb +80 -0
- data/lib/badline/computer.rb +4 -0
- data/lib/badline/core.rb +2 -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.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 +18 -10
- 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/machine.rb +5 -4
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/true_drive.rb +35 -15
- data/lib/badline/media.rb +9 -3
- data/lib/badline/options/event.rb +2 -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/snapshot/state.rb +5 -4
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/time_of_day.rb +3 -3
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +21 -7
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -0
- data/lib/badline/z80/core.rb +2 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- metadata +54 -21
- /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
|
@@ -2,38 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Frontend
|
|
5
|
-
# The SID player
|
|
6
|
-
# a header with the tune, its STIL credit, the views and
|
|
7
|
-
# and a footer with the time and the buttons that step
|
|
8
|
-
# queue, framing one of three views: the visualizer, with
|
|
9
|
-
# note and output and the mix, the SID view, with
|
|
10
|
-
# is doing, and the tune's STIL entry. The window's
|
|
11
|
-
# view, and .sid files and folders dropped on it
|
|
5
|
+
# The SID player that `sid` plays in, which runs its window,
|
|
6
|
+
# PlayerScreen: a header with the tune, its STIL credit, the views and
|
|
7
|
+
# the SID model, and a footer with the time and the buttons that step
|
|
8
|
+
# through the queue, framing one of three views: the visualizer, with
|
|
9
|
+
# each voice's note and output and the mix, the SID view, with
|
|
10
|
+
# everything the chip is doing, and the tune's STIL entry. The window's
|
|
11
|
+
# height follows the view, and .sid files and folders dropped on it
|
|
12
|
+
# join the queue.
|
|
12
13
|
#
|
|
13
14
|
# It stands in for the terminal, so Audio::Jukebox drives it the same
|
|
14
15
|
# way: #wait handles the window's clicks, keys and drops between frames
|
|
15
16
|
# and redraws it at the display's pace. The SID model buttons switch
|
|
16
17
|
# the chips playing on the spot, and each tune after it starts on the
|
|
17
18
|
# same choice. AUTO gives each of a tune's SIDs its own model.
|
|
18
|
-
class
|
|
19
|
+
class PlayerController < Audio::Terminal
|
|
19
20
|
FRAME = 1.0 / 50
|
|
20
21
|
|
|
21
|
-
MOUSEWHEEL = 0x403
|
|
22
|
-
DROPFILE = 0x1000
|
|
23
|
-
UP = 82
|
|
24
|
-
DOWN = 81
|
|
25
|
-
|
|
26
|
-
WARNING = VIC::PALETTE[10]
|
|
27
|
-
|
|
28
22
|
CHIPS = PlayerHeader::CHIPS
|
|
29
23
|
|
|
30
24
|
KEYS = {
|
|
31
25
|
17 => :next, 19 => :previous,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
20 => :quit,
|
|
26
|
+
Keys::RIGHT => :next_subtune, Keys::LEFT => :previous_subtune,
|
|
27
|
+
Keys::SPACE => :pause, 22 => :shuffle, 15 => :loop, 4 => :all_subtunes,
|
|
28
|
+
20 => :quit, Keys::ESCAPE => :quit,
|
|
35
29
|
54 => :back, 55 => :forward,
|
|
36
|
-
|
|
30
|
+
Keys::TAB => :view, 6 => :chip
|
|
37
31
|
}.freeze
|
|
38
32
|
|
|
39
33
|
def initialize(input:, output:)
|
|
@@ -143,17 +137,15 @@ module Badline
|
|
|
143
137
|
when SDL::MOUSEMOTION then point
|
|
144
138
|
when SDL::MOUSEBUTTONDOWN then click(actions) if SDL.event_button(SDL.event) == 1
|
|
145
139
|
when SDL::MOUSEBUTTONUP then @scrolling = false
|
|
146
|
-
when MOUSEWHEEL then @screen.info.scroll(-3 * SDL.event_x(SDL.event))
|
|
147
|
-
when DROPFILE then drop(actions)
|
|
140
|
+
when SDL::MOUSEWHEEL then @screen.info.scroll(-3 * SDL.event_x(SDL.event))
|
|
141
|
+
when SDL::DROPFILE then drop(actions)
|
|
148
142
|
end
|
|
149
143
|
end
|
|
150
144
|
actions
|
|
151
145
|
end
|
|
152
146
|
|
|
153
147
|
def drop(actions)
|
|
154
|
-
|
|
155
|
-
@dropped << LibC.strstr(file, "")
|
|
156
|
-
LibC.free(file)
|
|
148
|
+
@dropped << SDL.dropped_file
|
|
157
149
|
actions << :drop unless actions.include?(:drop)
|
|
158
150
|
end
|
|
159
151
|
|
|
@@ -164,7 +156,7 @@ module Badline
|
|
|
164
156
|
end
|
|
165
157
|
|
|
166
158
|
def key(scancode, actions)
|
|
167
|
-
return @screen.info.scroll(scancode == UP ? -1 : 1) if [UP, DOWN].include?(scancode)
|
|
159
|
+
return @screen.info.scroll(scancode == Keys::UP ? -1 : 1) if [Keys::UP, Keys::DOWN].include?(scancode)
|
|
168
160
|
|
|
169
161
|
action = KEYS[scancode]
|
|
170
162
|
command(action, actions) unless action.nil?
|
|
@@ -29,7 +29,7 @@ module Badline
|
|
|
29
29
|
x += @buttons.icon(x, row, state.paused? ? :play : :pause, :pause, scale: 2) + 4
|
|
30
30
|
x += @buttons.icon(x, row, :next, :next, scale: 2) + 8
|
|
31
31
|
unless state.tunes.zero?
|
|
32
|
-
x += @painter.text(x, row + 6, "TUNE #{state.tune}/#{state.tunes}",
|
|
32
|
+
x += @painter.text(x, row + 6, "TUNE #{state.tune}/#{state.tunes}", PlayerTheme::BRIGHT) + 24
|
|
33
33
|
draw_subtunes(state, x, row + 4)
|
|
34
34
|
end
|
|
35
35
|
draw_toggles(state, row + 4)
|
|
@@ -41,9 +41,9 @@ module Badline
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
43
|
def draw_subtunes(state, left, row)
|
|
44
|
-
x = left + @painter.text(left, row + 2, "SUBTUNE",
|
|
44
|
+
x = left + @painter.text(left, row + 2, "SUBTUNE", PlayerTheme::TEXT) + 4
|
|
45
45
|
x += @buttons.icon(x, row, :left, :previous_subtune)
|
|
46
|
-
x += @painter.text(x + 2, row + 2, "#{state.subtune}/#{state.subtunes}",
|
|
46
|
+
x += @painter.text(x + 2, row + 2, "#{state.subtune}/#{state.subtunes}", PlayerTheme::BRIGHT) + 4
|
|
47
47
|
@buttons.icon(x, row, :right, :next_subtune)
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -51,17 +51,17 @@ module Badline
|
|
|
51
51
|
played = state.played
|
|
52
52
|
width = BAR_WIDTH
|
|
53
53
|
@buttons.area([BAR_LEFT, top - 2, width, 12], :seek)
|
|
54
|
-
@painter.box(BAR_LEFT, top + 2, width, 4,
|
|
54
|
+
@painter.box(BAR_LEFT, top + 2, width, 4, PlayerTheme::BOX)
|
|
55
55
|
draw_target(state, top) if state.seeking?
|
|
56
|
-
@painter.box(BAR_LEFT, top + 2, along(state, played), 4,
|
|
56
|
+
@painter.box(BAR_LEFT, top + 2, along(state, played), 4, PlayerTheme::BRIGHT)
|
|
57
57
|
@painter.text(WIDTH - 16 - Painter.width(CLOCK), top, "#{clock(played)} / #{clock(state.length)}",
|
|
58
|
-
|
|
58
|
+
PlayerTheme::TEXT)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def draw_target(state, top)
|
|
62
62
|
target = along(state, state.target)
|
|
63
|
-
@painter.box(BAR_LEFT, top + 2, target, 4,
|
|
64
|
-
@painter.box(BAR_LEFT + [target, BAR_WIDTH - 1].min, top - 1, 1, 10,
|
|
63
|
+
@painter.box(BAR_LEFT, top + 2, target, 4, PlayerTheme::DIM)
|
|
64
|
+
@painter.box(BAR_LEFT + [target, BAR_WIDTH - 1].min, top - 1, 1, 10, PlayerTheme::BRIGHT)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# How far along the bar `seconds` into the subtune is.
|
|
@@ -21,10 +21,10 @@ module Badline
|
|
|
21
21
|
# Underlines the models playing while AUTO picks them.
|
|
22
22
|
def draw(state, tune, playing_models, credit)
|
|
23
23
|
unless tune.nil?
|
|
24
|
-
@painter.text(16, 8, fit(tune.name, 26),
|
|
25
|
-
@painter.text(16, 28, fit([tune.author, tune.released].reject(&:empty?).join(" - "), 50),
|
|
26
|
-
@painter.text(16, 40, fit(credit, 76),
|
|
27
|
-
@painter.text(16, 52, warning(state),
|
|
24
|
+
@painter.text(16, 8, fit(tune.name, 26), PlayerTheme::BRIGHT, scale: 2)
|
|
25
|
+
@painter.text(16, 28, fit([tune.author, tune.released].reject(&:empty?).join(" - "), 50), PlayerTheme::TEXT)
|
|
26
|
+
@painter.text(16, 40, fit(credit, 76), PlayerTheme::BRIGHT)
|
|
27
|
+
@painter.text(16, 52, warning(state), PlayerTheme::WARNING)
|
|
28
28
|
end
|
|
29
29
|
draw_views(state)
|
|
30
30
|
draw_chips(state, playing_models)
|
|
@@ -48,7 +48,7 @@ module Badline
|
|
|
48
48
|
CHIP_NAMES.each_with_index do |name, chip|
|
|
49
49
|
width = @buttons.text(x, 24, name, CHIPS[chip], on: chip == state.chip)
|
|
50
50
|
if state.chip.zero? && playing_models.include?(CHIPS[chip])
|
|
51
|
-
@painter.box(x + 2, 37, width - 4, 1,
|
|
51
|
+
@painter.box(x + 2, 37, width - 4, 1, PlayerTheme::BRIGHT)
|
|
52
52
|
end
|
|
53
53
|
x += width + 4
|
|
54
54
|
end
|
|
@@ -10,7 +10,6 @@ module Badline
|
|
|
10
10
|
HEADER = 64
|
|
11
11
|
SCALE = 2
|
|
12
12
|
TITLE = "Badline"
|
|
13
|
-
BACKGROUND = VIC::PALETTE[6]
|
|
14
13
|
|
|
15
14
|
attr_reader :buttons, :info
|
|
16
15
|
|
|
@@ -72,8 +71,8 @@ module Badline
|
|
|
72
71
|
def draw(state, player, history, tune, credit)
|
|
73
72
|
return unless open?
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
back = PlayerTheme::BACKGROUND
|
|
75
|
+
SDL.SDL_SetRenderDrawColor(@renderer, (back >> 16) & 0xff, (back >> 8) & 0xff, back & 0xff, 255)
|
|
77
76
|
SDL.SDL_RenderClear(@renderer)
|
|
78
77
|
@buttons.forget
|
|
79
78
|
@top.draw(state, tune, player.nil? ? [] : player.stereo.models, credit)
|
|
@@ -90,7 +89,7 @@ module Badline
|
|
|
90
89
|
|
|
91
90
|
def build
|
|
92
91
|
@painter = Painter.new(@renderer)
|
|
93
|
-
@buttons = Buttons.new(@painter)
|
|
92
|
+
@buttons = Buttons.new(@painter, [PlayerTheme::TEXT, PlayerTheme::BRIGHT, PlayerTheme::BACKGROUND])
|
|
94
93
|
@top = PlayerHeader.new(@painter, @buttons)
|
|
95
94
|
@bottom = PlayerFooter.new(@painter, @buttons)
|
|
96
95
|
@visualizer = VisualizerView.new(@painter, HEADER + 4)
|
|
@@ -117,7 +116,7 @@ module Badline
|
|
|
117
116
|
def draw_empty(view)
|
|
118
117
|
hint = "Drop .sid files or folders here"
|
|
119
118
|
@painter.text((WIDTH - Painter.width(hint, scale: 2)) / 2, HEADER + (body_height(view) / 2) - 8, hint,
|
|
120
|
-
|
|
119
|
+
PlayerTheme::TEXT, scale: 2)
|
|
121
120
|
end
|
|
122
121
|
end
|
|
123
122
|
end
|
|
@@ -21,7 +21,7 @@ module Badline
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def draw(history, played, channel, rgb)
|
|
24
|
-
@painter.box(@left, @top, @width, @height,
|
|
24
|
+
@painter.box(@left, @top, @width, @height, PlayerTheme::BOX)
|
|
25
25
|
span = @span
|
|
26
26
|
range = @range
|
|
27
27
|
start = history.scope_start(played, span, channel)
|
|
@@ -8,13 +8,6 @@ module Badline
|
|
|
8
8
|
# a tune on more than one gets a row of buttons above the voices that
|
|
9
9
|
# pick it, which makes the view PICKER taller.
|
|
10
10
|
class SIDView
|
|
11
|
-
COLORS = VIC::PALETTE
|
|
12
|
-
TEXT = COLORS[14]
|
|
13
|
-
BRIGHT = COLORS[1]
|
|
14
|
-
DIM = COLORS[11]
|
|
15
|
-
BOX = COLORS[0]
|
|
16
|
-
VOICE_COLORS = [COLORS[7], COLORS[13], COLORS[3]].freeze
|
|
17
|
-
|
|
18
11
|
NOTES = %w[C C# D D# E F F# G G# A A# B].freeze
|
|
19
12
|
WAVES = %w[TRI SAW PUL NOI].freeze
|
|
20
13
|
FLAGS = %w[GATE SYNC RING TEST].freeze
|
|
@@ -70,9 +63,11 @@ module Badline
|
|
|
70
63
|
top = @top + (picker * PICKER)
|
|
71
64
|
frame = history.frame(played, sid)
|
|
72
65
|
3.times { |voice| draw_voice(history, frame, voice, top + (voice * VOICE_HEIGHT), clock_hz) }
|
|
73
|
-
3.times
|
|
66
|
+
3.times do |voice|
|
|
67
|
+
@voices[picker][voice].draw(history, played, (sid * 4) + voice, PlayerTheme::VOICE_COLORS[voice])
|
|
68
|
+
end
|
|
74
69
|
@filter.draw(history, frame, top + (3 * VOICE_HEIGHT) + 4, model)
|
|
75
|
-
@outputs[picker].draw(history, played, history.output(sid), BRIGHT)
|
|
70
|
+
@outputs[picker].draw(history, played, history.output(sid), PlayerTheme::BRIGHT)
|
|
76
71
|
end
|
|
77
72
|
|
|
78
73
|
private
|
|
@@ -86,13 +81,14 @@ module Badline
|
|
|
86
81
|
|
|
87
82
|
def draw_voice(history, frame, voice, top, clock_hz)
|
|
88
83
|
base = voice * 7
|
|
89
|
-
color = VOICE_COLORS[voice]
|
|
84
|
+
color = PlayerTheme::VOICE_COLORS[voice]
|
|
90
85
|
control = history.register(frame, base + 4)
|
|
91
86
|
hertz = history.hertz(frame, voice, clock_hz)
|
|
92
87
|
paint = @painter
|
|
93
88
|
paint.text(LEFT, top, "VOICE #{voice + 1}", color)
|
|
94
|
-
paint.text(LEFT, top + 12, SIDView.note(hertz), history.level(frame, voice).zero? ? DIM : color,
|
|
95
|
-
|
|
89
|
+
paint.text(LEFT, top + 12, SIDView.note(hertz), history.level(frame, voice).zero? ? PlayerTheme::DIM : color,
|
|
90
|
+
scale: 2)
|
|
91
|
+
paint.text(LEFT + 112, top + 12, format("%<hertz>.1f HZ", hertz:), PlayerTheme::TEXT)
|
|
96
92
|
draw_waves(control, top, color)
|
|
97
93
|
draw_flags(history, frame, voice, control, top + 24)
|
|
98
94
|
draw_envelope(history, frame, voice, top + 36, color)
|
|
@@ -101,17 +97,17 @@ module Badline
|
|
|
101
97
|
def draw_waves(control, top, color)
|
|
102
98
|
x = LEFT + 224
|
|
103
99
|
WAVES.each_with_index do |wave, bit|
|
|
104
|
-
x += @painter.text(x, top, wave, control[bit + 4] == 1 ? color : DIM) + 8
|
|
100
|
+
x += @painter.text(x, top, wave, control[bit + 4] == 1 ? color : PlayerTheme::DIM) + 8
|
|
105
101
|
end
|
|
106
102
|
end
|
|
107
103
|
|
|
108
104
|
def draw_flags(history, frame, voice, control, top)
|
|
109
105
|
x = LEFT + 112
|
|
110
106
|
FLAGS.each_with_index do |flag, bit|
|
|
111
|
-
x += @painter.text(x, top, flag, control[bit] == 1 ? BRIGHT : DIM) + 8
|
|
107
|
+
x += @painter.text(x, top, flag, control[bit] == 1 ? PlayerTheme::BRIGHT : PlayerTheme::DIM) + 8
|
|
112
108
|
end
|
|
113
109
|
filtered = history.register(frame, 0x17)[voice] == 1
|
|
114
|
-
@painter.text(x, top, "FILT", filtered ? BRIGHT : DIM)
|
|
110
|
+
@painter.text(x, top, "FILT", filtered ? PlayerTheme::BRIGHT : PlayerTheme::DIM)
|
|
115
111
|
end
|
|
116
112
|
|
|
117
113
|
def draw_envelope(history, frame, voice, top, color)
|
|
@@ -120,14 +116,14 @@ module Badline
|
|
|
120
116
|
sustain_release = history.register(frame, base + 6)
|
|
121
117
|
paint = @painter
|
|
122
118
|
paint.text(LEFT + 112, top, format("PW %<pw>5.1f%%", pw: pulse_width(history, frame, voice) * 100.0 / 4096),
|
|
123
|
-
TEXT)
|
|
119
|
+
PlayerTheme::TEXT)
|
|
124
120
|
rates = "A#{hex(attack_decay >> 4)} D#{hex(attack_decay & 0x0f)} " \
|
|
125
121
|
"S#{hex(sustain_release >> 4)} R#{hex(sustain_release & 0x0f)}"
|
|
126
|
-
paint.text(LEFT + 224, top, rates, TEXT)
|
|
127
|
-
paint.box(LEFT, top + 12, 128, 6, BOX)
|
|
122
|
+
paint.text(LEFT + 224, top, rates, PlayerTheme::TEXT)
|
|
123
|
+
paint.box(LEFT, top + 12, 128, 6, PlayerTheme::BOX)
|
|
128
124
|
paint.box(LEFT, top + 12, history.level(frame, voice) / 2, 6, color)
|
|
129
|
-
paint.box(LEFT + ((sustain_release >> 4) * 17 / 2), top + 10, 1, 10, BRIGHT)
|
|
130
|
-
paint.text(LEFT + 136, top + 12, PHASES.fetch(history.state(frame, voice), ""), DIM)
|
|
125
|
+
paint.box(LEFT + ((sustain_release >> 4) * 17 / 2), top + 10, 1, 10, PlayerTheme::BRIGHT)
|
|
126
|
+
paint.text(LEFT + 136, top + 12, PHASES.fetch(history.state(frame, voice), ""), PlayerTheme::DIM)
|
|
131
127
|
end
|
|
132
128
|
|
|
133
129
|
def hex(nibble) = nibble.to_s(16).upcase
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Frontend
|
|
5
|
+
# The SID player's colours, which its window and views share, from the
|
|
6
|
+
# C64's palette: one for each voice, and light blue on blue as on the
|
|
7
|
+
# machine's screen.
|
|
8
|
+
module PlayerTheme
|
|
9
|
+
TEXT = VIC::PALETTE[14]
|
|
10
|
+
BRIGHT = VIC::PALETTE[1]
|
|
11
|
+
DIM = VIC::PALETTE[11]
|
|
12
|
+
BOX = VIC::PALETTE[0]
|
|
13
|
+
BACKGROUND = VIC::PALETTE[6]
|
|
14
|
+
WARNING = VIC::PALETTE[10]
|
|
15
|
+
VOICE_COLORS = [VIC::PALETTE[7], VIC::PALETTE[13], VIC::PALETTE[3]].freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -69,10 +69,10 @@ module Badline
|
|
|
69
69
|
top = row_top(count, sid)
|
|
70
70
|
scopes = @voices[count - 1]
|
|
71
71
|
3.times do |voice|
|
|
72
|
-
color =
|
|
72
|
+
color = PlayerTheme::VOICE_COLORS[voice]
|
|
73
73
|
note = SIDView.note(history.hertz(frame, voice, clock_hz))
|
|
74
74
|
silent = history.level(frame, voice).zero?
|
|
75
|
-
@painter.text(left(voice), top, note, silent ?
|
|
75
|
+
@painter.text(left(voice), top, note, silent ? PlayerTheme::DIM : color, scale: count == 1 ? 2 : 1)
|
|
76
76
|
scopes[(sid * 3) + voice].draw(history, played, (sid * 4) + voice, color)
|
|
77
77
|
end
|
|
78
78
|
draw_label(top, sid) if count > 1
|
|
@@ -80,21 +80,21 @@ module Badline
|
|
|
80
80
|
|
|
81
81
|
def draw_label(top, sid)
|
|
82
82
|
label = "SID #{sid + 1}"
|
|
83
|
-
@painter.text(SIDView::LEFT + MIX_WIDTH - Painter.width(label), top, label,
|
|
83
|
+
@painter.text(SIDView::LEFT + MIX_WIDTH - Painter.width(label), top, label, PlayerTheme::TEXT)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def draw_mix(history, played, count)
|
|
87
87
|
if count == 1
|
|
88
|
-
@mix.draw(history, played, SIDHistory::MIX,
|
|
88
|
+
@mix.draw(history, played, SIDHistory::MIX, PlayerTheme::BRIGHT)
|
|
89
89
|
return
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
sides = @sides[count - 2]
|
|
93
|
-
sides[0].draw(history, played, history.left,
|
|
94
|
-
sides[1].draw(history, played, history.right,
|
|
93
|
+
sides[0].draw(history, played, history.left, PlayerTheme::BRIGHT)
|
|
94
|
+
sides[1].draw(history, played, history.right, PlayerTheme::BRIGHT)
|
|
95
95
|
top = mix_top(count) + 2
|
|
96
|
-
@painter.text(SIDView::LEFT + 2, top, "L",
|
|
97
|
-
@painter.text(SIDView::LEFT + MIX_WIDTH - 10, top, "R",
|
|
96
|
+
@painter.text(SIDView::LEFT + 2, top, "L", PlayerTheme::DIM)
|
|
97
|
+
@painter.text(SIDView::LEFT + MIX_WIDTH - 10, top, "R", PlayerTheme::DIM)
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
end
|
|
@@ -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)
|
data/lib/badline/machine.rb
CHANGED
|
@@ -25,8 +25,9 @@ module Badline
|
|
|
25
25
|
# +mounted?+, +datasette+, +attach_cartridge+,
|
|
26
26
|
# +press_cartridge_button+, +release_cartridge_button+,
|
|
27
27
|
# +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
|
|
28
|
-
# for the drive Media::TrueDrive plugs in,
|
|
29
|
-
# +
|
|
28
|
+
# for the drive Media::TrueDrive plugs in, +attach_drive1581+,
|
|
29
|
+
# +drive1581+ and +plug_drive1581+ for a 1581 (Drive1581::Slot), and
|
|
30
|
+
# +address_bus+ for its +cartridge+ and +detach_cartridge+.
|
|
30
31
|
# - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
|
|
31
32
|
# +install_debug_register+ and +capture_output+.
|
|
32
33
|
#
|
|
@@ -34,8 +35,8 @@ module Badline
|
|
|
34
35
|
# input parts, with its one joystick as both +joystick1+ and +joystick2+
|
|
35
36
|
# and nil for +control_ports+, and of the rest +ram+, +load_prg+,
|
|
36
37
|
# +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
|
|
37
|
-
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+,
|
|
38
|
-
# +install_debug_register+ and +capture_output+.
|
|
38
|
+
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+, the
|
|
39
|
+
# 1581's, +install_debug_register+ and +capture_output+.
|
|
39
40
|
#
|
|
40
41
|
# The C128 answers all but the cartridge button, and besides +mode+,
|
|
41
42
|
# :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
|