badline 0.11.0 → 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 +26 -0
- data/CLAUDE.md +24 -6
- data/CONTRIBUTING.md +15 -9
- data/README.md +16 -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 +108 -0
- data/lib/badline/c128/fast_serial.rb +63 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +24 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +23 -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 +25 -26
- 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 +60 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +10 -5
- data/lib/badline/computer/attachments.rb +4 -50
- 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 +147 -0
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +11 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +14 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +120 -0
- data/lib/badline/drive1571.rb +175 -0
- 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 +2 -4
- 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/drive_led.rb +1 -1
- 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} +7 -9
- 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 +68 -6
- data/lib/badline/machine.rb +9 -6
- 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 +48 -25
- 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 +3 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/dos1571.rom +0 -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/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic20/attachments.rb +23 -3
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +113 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +89 -24
- /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
|
@@ -52,7 +52,7 @@ module Badline
|
|
|
52
52
|
def draw
|
|
53
53
|
index = 0
|
|
54
54
|
while index < ROWS && @offset + index < @lines.size
|
|
55
|
-
@painter.text(LEFT, @top + (index * LINE), @lines[@offset + index],
|
|
55
|
+
@painter.text(LEFT, @top + (index * LINE), @lines[@offset + index], PlayerTheme::TEXT)
|
|
56
56
|
index += 1
|
|
57
57
|
end
|
|
58
58
|
draw_bar if @lines.size > ROWS
|
|
@@ -110,10 +110,10 @@ module Badline
|
|
|
110
110
|
|
|
111
111
|
def draw_bar
|
|
112
112
|
@buttons.area([BAR_LEFT, @top, BAR, HEIGHT], :scroll)
|
|
113
|
-
@painter.box(BAR_LEFT, @top, BAR, HEIGHT,
|
|
113
|
+
@painter.box(BAR_LEFT, @top, BAR, HEIGHT, PlayerTheme::BOX)
|
|
114
114
|
thumb = [HEIGHT * ROWS / @lines.size, 8].max
|
|
115
115
|
place = (HEIGHT - thumb) * @offset / [last_offset, 1].max
|
|
116
|
-
@painter.box(BAR_LEFT, @top + place, BAR, thumb,
|
|
116
|
+
@painter.box(BAR_LEFT, @top + place, BAR, thumb, PlayerTheme::TEXT)
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
end
|
|
@@ -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"
|