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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1581
|
|
5
|
+
class WD1772
|
|
6
|
+
# The type II commands and read address, which find an ID field on
|
|
7
|
+
# the track under the head:
|
|
8
|
+
#
|
|
9
|
+
# 100mhE00 read sector the data field after the ID that carries
|
|
10
|
+
# the track and sector registers
|
|
11
|
+
# 101mhEPa write sector a data field after that ID, deleted with a
|
|
12
|
+
# 11000hE00 read address the next ID field's six bytes, the track
|
|
13
|
+
# going into the sector register
|
|
14
|
+
#
|
|
15
|
+
# The search reads each ID field as it passes. One that matches but
|
|
16
|
+
# whose CRC doesn't read back sets CRC_ERROR and the search goes on;
|
|
17
|
+
# after five index pulses it gives up with NOT_FOUND. Each byte of a
|
|
18
|
+
# field passes in BYTE cycles and sets DRQ as it lands in the data
|
|
19
|
+
# register, and LOST_DATA where the last one was still unread. With m
|
|
20
|
+
# the command goes on to the next sector until one isn't found. E
|
|
21
|
+
# lets the head settle first. SectorWrites writes the data field.
|
|
22
|
+
module Sectors
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def start_type2
|
|
26
|
+
start_search(@command < 0xa0 ? READ : WRITE)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def start_read_address
|
|
30
|
+
start_search(ADDRESS)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_search(mode)
|
|
34
|
+
@type1 = false
|
|
35
|
+
@status = 0
|
|
36
|
+
@drq = false
|
|
37
|
+
@mode = mode
|
|
38
|
+
resume_type2 unless spinning_up?(SEARCH)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resume_type2
|
|
42
|
+
if @mode == WRITE && @mechanism.write_protected?
|
|
43
|
+
@status |= PROTECTED
|
|
44
|
+
return finish
|
|
45
|
+
end
|
|
46
|
+
return begin_search if @command.nobits?(SETTLE_DELAY)
|
|
47
|
+
|
|
48
|
+
@phase = SETTLE_SEARCH
|
|
49
|
+
@due = @now + SETTLE
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Searches for five index pulses from now.
|
|
53
|
+
def begin_search
|
|
54
|
+
index = next_index
|
|
55
|
+
@deadline = index == NEVER ? NEVER : index + (4 * Mechanism::REVOLUTION)
|
|
56
|
+
search
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Waits for the next ID field to pass under the head.
|
|
60
|
+
def search
|
|
61
|
+
@phase = SEARCH
|
|
62
|
+
track = @mechanism.track
|
|
63
|
+
sector = track&.next_id((@mechanism.angle(@now) / BYTE) + 1)
|
|
64
|
+
@found = sector ? track.sectors.index(sector) : -1
|
|
65
|
+
@id_at = sector ? byte_end(sector.id.mark) : NEVER
|
|
66
|
+
@due = [@id_at, @deadline].min
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def found = @mechanism.track&.sectors&.[](@found)
|
|
70
|
+
|
|
71
|
+
def id_passed
|
|
72
|
+
return not_found unless @now == @id_at
|
|
73
|
+
|
|
74
|
+
sector = found
|
|
75
|
+
return search if sector.nil?
|
|
76
|
+
|
|
77
|
+
id = sector.id
|
|
78
|
+
return id_read(id) if @mode == ADDRESS
|
|
79
|
+
return search unless matches?(id.bytes)
|
|
80
|
+
return crc_mismatch unless id.good
|
|
81
|
+
|
|
82
|
+
@status &= ~CRC_ERROR
|
|
83
|
+
@mode == VERIFY_TRACK ? finish : sector_found(sector)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def matches?(id)
|
|
87
|
+
id[0] == @track && (@mode == VERIFY_TRACK || id[2] == @sector)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def crc_mismatch
|
|
91
|
+
@status |= CRC_ERROR
|
|
92
|
+
search
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def not_found
|
|
96
|
+
@status |= NOT_FOUND
|
|
97
|
+
finish
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def sector_found(sector)
|
|
101
|
+
return write_sector(sector) if @mode == WRITE
|
|
102
|
+
|
|
103
|
+
data = sector.data
|
|
104
|
+
return search if data.nil? || data.mark > sector.id.mark + 7 + Track::DATA_MARK_WINDOW
|
|
105
|
+
|
|
106
|
+
@status |= DELETED if data.deleted
|
|
107
|
+
transfer(data.bytes, data.mark, READ_DATA)
|
|
108
|
+
@crc_good = data.good
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Read address: the ID's bytes and its CRC.
|
|
112
|
+
def id_read(id)
|
|
113
|
+
@status |= CRC_ERROR unless id.good
|
|
114
|
+
transfer(id.bytes + Track.field_crc(Track::ID_MARK, id.bytes), id.mark, READ_ADDRESS)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def transfer(bytes, mark, phase)
|
|
118
|
+
@bytes = bytes
|
|
119
|
+
@index = 0
|
|
120
|
+
@phase = phase
|
|
121
|
+
@due = byte_end(mark + 1)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# A byte of the field lands in the data register. After the last,
|
|
125
|
+
# the CRC passes.
|
|
126
|
+
def byte_read
|
|
127
|
+
return field_read if @index == @bytes.length
|
|
128
|
+
|
|
129
|
+
@status |= LOST_DATA if @drq
|
|
130
|
+
@data = @bytes[@index]
|
|
131
|
+
@drq = true
|
|
132
|
+
@index += 1
|
|
133
|
+
@due += @index == @bytes.length && @phase == READ_DATA ? 2 * BYTE : BYTE
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def field_read
|
|
137
|
+
if @phase == READ_ADDRESS
|
|
138
|
+
@sector = @bytes[0]
|
|
139
|
+
elsif !@crc_good
|
|
140
|
+
@status |= CRC_ERROR
|
|
141
|
+
elsif @command.anybits?(MULTIPLE)
|
|
142
|
+
@sector = (@sector + 1) & 0xff
|
|
143
|
+
return begin_search
|
|
144
|
+
end
|
|
145
|
+
finish
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1581
|
|
5
|
+
class WD1772
|
|
6
|
+
# Saving and restoring the WD1772 for a snapshot: its registers and
|
|
7
|
+
# lines, and the command running, down to the byte it is at.
|
|
8
|
+
module State
|
|
9
|
+
def save_state(out)
|
|
10
|
+
[@now, @due, @phase, @command, @track, @sector, @data, @status, @steps, @count, @after_spin_up, @mode,
|
|
11
|
+
@deadline, @id_at, @found, @index, @crc, @write_id, @size].each { |value| out.int(value) }
|
|
12
|
+
[@type1, @drq, @intrq, @motor_on, @inward, @crc_good, @gate_open, @late,
|
|
13
|
+
@settling].each { |flag| out.boolean(flag) }
|
|
14
|
+
out.ints(@bytes).ints(@syncs.map { |sync| sync ? 1 : 0 })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load_state(input)
|
|
18
|
+
load_registers(input)
|
|
19
|
+
load_command(input)
|
|
20
|
+
load_flags(input)
|
|
21
|
+
@bytes = input.ints
|
|
22
|
+
@syncs = input.ints.map { |sync| sync == 1 }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def load_registers(input)
|
|
28
|
+
@now = input.int
|
|
29
|
+
@due = input.int
|
|
30
|
+
@phase = input.int
|
|
31
|
+
@command = input.int
|
|
32
|
+
@track = input.int
|
|
33
|
+
@sector = input.int
|
|
34
|
+
@data = input.int
|
|
35
|
+
@status = input.int
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def load_command(input)
|
|
39
|
+
@steps = input.int
|
|
40
|
+
@count = input.int
|
|
41
|
+
@after_spin_up = input.int
|
|
42
|
+
@mode = input.int
|
|
43
|
+
@deadline = input.int
|
|
44
|
+
@id_at = input.int
|
|
45
|
+
@found = input.int
|
|
46
|
+
@index = input.int
|
|
47
|
+
@crc = input.int
|
|
48
|
+
@write_id = input.int
|
|
49
|
+
@size = input.int
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def load_flags(input)
|
|
53
|
+
@type1 = input.boolean?
|
|
54
|
+
@drq = input.boolean?
|
|
55
|
+
@intrq = input.boolean?
|
|
56
|
+
@motor_on = input.boolean?
|
|
57
|
+
@inward = input.boolean?
|
|
58
|
+
@crc_good = input.boolean?
|
|
59
|
+
@gate_open = input.boolean?
|
|
60
|
+
@late = input.boolean?
|
|
61
|
+
@settling = input.boolean?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1581
|
|
5
|
+
class WD1772
|
|
6
|
+
# The type I commands, which move the head:
|
|
7
|
+
#
|
|
8
|
+
# 0000hVrr restore step out to track 0, the track register to 0
|
|
9
|
+
# 0001hVrr seek step until the track register holds the data
|
|
10
|
+
# register's track
|
|
11
|
+
# 001uhVrr step one step the way the last one went
|
|
12
|
+
# 010uhVrr step in one step towards the hub
|
|
13
|
+
# 011uhVrr step out one step towards the rim
|
|
14
|
+
#
|
|
15
|
+
# Each step waits out the step rate rr picks (STEP_RATES), and u has
|
|
16
|
+
# a step update the track register, as restore and seek always do.
|
|
17
|
+
# With V the head settles and the command reads IDs until one carries
|
|
18
|
+
# the track register's track, or sets NOT_FOUND after five turns.
|
|
19
|
+
module Stepping
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def start_type1
|
|
23
|
+
@type1 = true
|
|
24
|
+
@status = 0
|
|
25
|
+
@drq = false
|
|
26
|
+
@phase = STEPPED
|
|
27
|
+
@steps = 0
|
|
28
|
+
if @command < 0x10
|
|
29
|
+
@track = 0xff
|
|
30
|
+
@data = 0
|
|
31
|
+
elsif @command >= 0x40
|
|
32
|
+
@inward = @command < 0x60
|
|
33
|
+
end
|
|
34
|
+
step_done unless spinning_up?(STEPPED)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def spun_up
|
|
38
|
+
@status |= SPUN_UP
|
|
39
|
+
@phase = @after_spin_up
|
|
40
|
+
case @after_spin_up
|
|
41
|
+
when STEPPED then step_done
|
|
42
|
+
when SEARCH then resume_type2
|
|
43
|
+
else resume_track
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The step rate has passed: the next step, or the end of the moves.
|
|
48
|
+
def step_done
|
|
49
|
+
return next_step if @steps.zero? || @command < 0x20
|
|
50
|
+
|
|
51
|
+
moved
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Restore and seek step until the track register matches, restore
|
|
55
|
+
# also stopping at track 0. The others step once.
|
|
56
|
+
def next_step
|
|
57
|
+
return single_step if @command >= 0x20
|
|
58
|
+
return seek_restore if @command < 0x10 && restored?
|
|
59
|
+
|
|
60
|
+
if @track == @data
|
|
61
|
+
moved
|
|
62
|
+
else
|
|
63
|
+
@inward = @data > @track
|
|
64
|
+
@track = (@track + (@inward ? 1 : -1)) & 0xff
|
|
65
|
+
pulse
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def restored?
|
|
70
|
+
return false unless @mechanism.track0? || @steps >= 255
|
|
71
|
+
|
|
72
|
+
@status |= NOT_FOUND if !@mechanism.track0? && @command.anybits?(VERIFY)
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def seek_restore
|
|
77
|
+
@track = 0 if @mechanism.track0?
|
|
78
|
+
moved
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def single_step
|
|
82
|
+
@track = (@track + (@inward ? 1 : -1)) & 0xff if @command.anybits?(UPDATE)
|
|
83
|
+
pulse
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def pulse
|
|
87
|
+
@steps += 1
|
|
88
|
+
@mechanism.step(@inward)
|
|
89
|
+
@phase = STEPPED
|
|
90
|
+
@due = @now + STEP_RATES[@command & 0x03]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The head is there: with V it settles, and the command ends
|
|
94
|
+
# otherwise.
|
|
95
|
+
def moved
|
|
96
|
+
return finish unless @command.anybits?(VERIFY) && @status.nobits?(NOT_FOUND)
|
|
97
|
+
|
|
98
|
+
@phase = SETTLED
|
|
99
|
+
@due = @now + SETTLE
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def verify_track
|
|
103
|
+
@mode = VERIFY_TRACK
|
|
104
|
+
begin_search
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1581
|
|
5
|
+
class WD1772
|
|
6
|
+
# The type III commands that take a whole turn, from one index pulse
|
|
7
|
+
# to the next:
|
|
8
|
+
#
|
|
9
|
+
# 11100hE00 read track every byte under the head (Track#raw)
|
|
10
|
+
# 11110hEP0 write track every byte the CPU sends, for a format
|
|
11
|
+
#
|
|
12
|
+
# Write track asks for its first byte at once and starts at the index
|
|
13
|
+
# pulse, giving up with LOST_DATA if the byte hasn't come three bytes
|
|
14
|
+
# later. $F5 writes a sync mark, an $A1, and the first of a run starts
|
|
15
|
+
# the CRC over; $F6 writes $C2; $F7 writes the CRC's two bytes. At
|
|
16
|
+
# the next index pulse the track under the head becomes the fields
|
|
17
|
+
# found in what went down (Track.parse).
|
|
18
|
+
module Tracks
|
|
19
|
+
LATE = 3
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def start_type3
|
|
24
|
+
@type1 = false
|
|
25
|
+
@status = 0
|
|
26
|
+
@drq = false
|
|
27
|
+
@mode = @command.anybits?(0x10) ? WRITE : READ
|
|
28
|
+
resume_track unless spinning_up?(TRACK_INDEX)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resume_track
|
|
32
|
+
if @mode == WRITE
|
|
33
|
+
if @mechanism.write_protected?
|
|
34
|
+
@status |= PROTECTED
|
|
35
|
+
return finish
|
|
36
|
+
end
|
|
37
|
+
@drq = true
|
|
38
|
+
end
|
|
39
|
+
@phase = @mode == WRITE ? WRITE_TRACK_START : TRACK_INDEX
|
|
40
|
+
return wait_index(@phase) if @command.nobits?(SETTLE_DELAY)
|
|
41
|
+
|
|
42
|
+
@due = @now + SETTLE
|
|
43
|
+
@settling = true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def wait_index(phase)
|
|
47
|
+
@settling = false
|
|
48
|
+
@phase = phase
|
|
49
|
+
@due = next_index
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def track_index
|
|
53
|
+
return wait_index(TRACK_INDEX) if @settling
|
|
54
|
+
|
|
55
|
+
@bytes = (@mechanism.track || Track.new([])).raw
|
|
56
|
+
@index = 0
|
|
57
|
+
@phase = READ_TRACK
|
|
58
|
+
@due = @now + BYTE
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def track_byte_read
|
|
62
|
+
@status |= LOST_DATA if @drq
|
|
63
|
+
@data = @bytes[@index]
|
|
64
|
+
@drq = true
|
|
65
|
+
@index += 1
|
|
66
|
+
@index == Track::LENGTH ? finish : @due += BYTE
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def write_track_start
|
|
70
|
+
return wait_index(WRITE_TRACK_START) if @settling
|
|
71
|
+
|
|
72
|
+
@bytes = []
|
|
73
|
+
@syncs = []
|
|
74
|
+
@crc = 0xffff
|
|
75
|
+
@phase = WRITE_TRACK
|
|
76
|
+
return track_byte_written unless @drq
|
|
77
|
+
|
|
78
|
+
@late = true
|
|
79
|
+
@due = @now + (LATE * BYTE)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The next byte from the data register goes under the head, a 0
|
|
83
|
+
# where DRQ went unanswered.
|
|
84
|
+
def track_byte_written
|
|
85
|
+
return lost if @late && @drq
|
|
86
|
+
return track_written if @bytes.length >= Track::LENGTH
|
|
87
|
+
|
|
88
|
+
@late = false
|
|
89
|
+
value = @drq ? 0 : @data
|
|
90
|
+
@status |= LOST_DATA if @drq
|
|
91
|
+
@drq = true
|
|
92
|
+
before = @bytes.length
|
|
93
|
+
lay_byte(value)
|
|
94
|
+
@due = @now + ((@bytes.length - before) * BYTE)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def lay_byte(value)
|
|
98
|
+
case value
|
|
99
|
+
when 0xf5
|
|
100
|
+
@crc = 0xffff unless @syncs.last
|
|
101
|
+
push_byte(Track::SYNC, true)
|
|
102
|
+
when 0xf6 then push_byte(0xc2, false)
|
|
103
|
+
when 0xf7
|
|
104
|
+
crc = @crc
|
|
105
|
+
push_byte(crc >> 8, false)
|
|
106
|
+
push_byte(crc & 0xff, false)
|
|
107
|
+
else push_byte(value, false)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def push_byte(value, sync)
|
|
112
|
+
@bytes << value
|
|
113
|
+
@syncs << sync
|
|
114
|
+
@crc = Track.crc([value], @crc)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def track_written
|
|
118
|
+
@drq = false
|
|
119
|
+
@mechanism.write(Track.parse(@bytes.first(Track::LENGTH), @syncs))
|
|
120
|
+
finish
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1581
|
|
5
|
+
class WD1772
|
|
6
|
+
# The disk turning under the head: the index pulses a phase waits
|
|
7
|
+
# for or counts, and the cycle the head comes to a place round the
|
|
8
|
+
# track.
|
|
9
|
+
module Turn
|
|
10
|
+
# The disk began or stopped turning: a phase waiting on the turn
|
|
11
|
+
# starts its wait again.
|
|
12
|
+
def spin_changed
|
|
13
|
+
case @phase
|
|
14
|
+
when SPIN_UP, MOTOR_IDLE, INDEX_INTERRUPT then count_index
|
|
15
|
+
when SEARCH then begin_search
|
|
16
|
+
when TRACK_INDEX, WRITE_TRACK_START then wait_index(@phase)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# The motor starts: with h clear and the motor off, the command waits
|
|
23
|
+
# out six index pulses first, and the phase comes after them.
|
|
24
|
+
def spinning_up?(phase)
|
|
25
|
+
spin_up = !@motor_on && @command.nobits?(NO_SPIN_UP)
|
|
26
|
+
@motor_on = true
|
|
27
|
+
return false unless spin_up
|
|
28
|
+
|
|
29
|
+
@after_spin_up = phase
|
|
30
|
+
@phase = SPIN_UP
|
|
31
|
+
@count = 6
|
|
32
|
+
count_index
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Waits for @count index pulses, or for the disk to turn.
|
|
37
|
+
def count_index
|
|
38
|
+
@due = next_index
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def index_counted
|
|
42
|
+
@count -= 1
|
|
43
|
+
return @due += Mechanism::REVOLUTION if @count.positive?
|
|
44
|
+
|
|
45
|
+
case @phase
|
|
46
|
+
when SPIN_UP then spun_up
|
|
47
|
+
when MOTOR_IDLE
|
|
48
|
+
@motor_on = false
|
|
49
|
+
idle
|
|
50
|
+
else
|
|
51
|
+
@intrq = true
|
|
52
|
+
idle
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The cycle the next index pulse starts, or NEVER while no disk turns.
|
|
57
|
+
def next_index = at_angle(0)
|
|
58
|
+
|
|
59
|
+
# The cycle the head next comes to +angle+, from the index.
|
|
60
|
+
def at_angle(angle)
|
|
61
|
+
return NEVER unless @mechanism.spinning?
|
|
62
|
+
|
|
63
|
+
delta = (angle - @mechanism.angle(@now)) % Mechanism::REVOLUTION
|
|
64
|
+
@now + (delta.zero? ? Mechanism::REVOLUTION : delta)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The cycle the byte at +position+ round the track has passed under
|
|
68
|
+
# the head.
|
|
69
|
+
def byte_end(position) = at_angle(((position + 1) % Track::LENGTH) * BYTE)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|