diff --git a/player/main.s b/player/main.s index fb12426..6a98fb2 100644 --- a/player/main.s +++ b/player/main.s @@ -304,8 +304,23 @@ exit_parmtable: init_mainloop: JSR hgr ; nukes the startup code we placed in HGR segment + + STA $C050 ; GRAPHICS + STA $C057 ; HIRES + STA $C05E ; DHR + STA $C00D ; 80 COLUMN MODE + + STA $C001 ; 80STOREON + + ; Clear aux screen + STA $C055 ; + LDA #$20 + JSR $F3EA + STA fullscr + STA $C054 ; MAIN memory active + ; establish invariant expected by decode loop LDX #$00 @@ -1256,11 +1271,11 @@ op_tick_64 63 op_tick_66 63 op_terminate: - ; Wait for keypress + LDA KBDSTRB ; clear strobe +@0: ; Wait for keypress LDA KBD - BMI @1 ; key pressed - BPL op_terminate -@1: LDA KBDSTRB ; clear strobe + BPL @0 +@1: ; key pressed JMP exit ; Manage W5100 socket buffer and ACK TCP stream. @@ -1268,30 +1283,35 @@ op_terminate: ; In order to simplify the buffer management we expect this ACK opcode to consume ; the last 4 bytes in a 2K "TCP frame". i.e. we can assume that we need to consume ; exactly 2K from the W5100 socket buffer. +; +; TODO: actually we are underrunning by 2 bytes currently, we've only consumed 2 +; bytes of 4 by this point. op_ack: BIT tick ; 4 - LDA WDATA ; 4 dummy read of second-last byte in TCP frame + ; allow flip-flopping the PAGE1/PAGE2 soft switches to steer writes to MAIN/AUX screens + ; actually this allows touching any $C0XX soft-switch, in case that is useful somehow + LDA WDATA ; 4 + STA @D+1 ; 4 +@D: + STA $C054 ; 4 low-byte is modified LDA WDATA ; 4 dummy read of last byte in TCP frame CLC ; 2 LDA #>S0RXRD ; 2 NEED HIGH BYTE HERE STA WADRH ; 4 - LDA # Iterator[opcodes.Opcode]: """ @@ -47,18 +49,23 @@ class Movie: for au in self.audio.audio_stream(): self.cycles += self.audio.cycles_per_tick if self.video.tick(self.cycles): - video_frame = next(video_frames) + main, aux = next(video_frames) if ((self.video.frame_number - 1) % self.every_n_video_frames == 0): print("Starting frame %d" % self.video.frame_number) - video_seq = self.video.encode_frame(video_frame) + main_seq = self.video.encode_frame( + main, self.video.memory_map, self.video.update_priority) + aux_seq = self.video.encode_frame( + aux, self.video.aux_memory_map, + self.video.aux_update_priority) # au has range -15 .. 16 (step=1) # Tick cycles are units of 2 tick = au * 2 # -30 .. 32 (step=2) tick += 34 # 4 .. 66 (step=2) - (page, content, offsets) = next(video_seq) + (page, content, offsets) = next( + aux_seq if self.aux_memory_bank else main_seq) yield opcodes.TICK_OPCODES[(tick, page)](content, offsets) @@ -86,7 +93,9 @@ class Movie: socket_pos = self.stream_pos % 2048 if socket_pos >= 2044: # 2 dummy bytes + 2 address bytes for next opcode - yield from self._emit_bytes(opcodes.Ack()) + yield from self._emit_bytes(opcodes.Ack(self.aux_memory_bank)) + # Flip-flop between MAIN and AUX banks + self.aux_memory_bank = not self.aux_memory_bank yield from self._emit_bytes(op) yield from self.done() diff --git a/transcoder/opcodes.py b/transcoder/opcodes.py index d942a96..e822cd1 100644 --- a/transcoder/opcodes.py +++ b/transcoder/opcodes.py @@ -76,13 +76,18 @@ class Ack(Opcode): """Instructs player to perform TCP stream + buffer management.""" COMMAND = OpcodeCommand.ACK + def __init__(self, aux_active: bool): + self.aux_active = aux_active + def emit_data(self) -> Iterator[int]: - # Dummy bytes to pad out TCP frame - yield 0xff + # Flip $C054 or $C055 soft-switches to steer subsequent writes to + # MAIN/AUX screen memory + yield 0x54 if self.aux_active else 0x55 + # Dummy byte to pad out TCP frame yield 0xff def __data_eq__(self, other): - return True + return self.aux_active == other.aux_active class BaseTick(Opcode): diff --git a/transcoder/video.py b/transcoder/video.py index e3ad893..d51e659 100644 --- a/transcoder/video.py +++ b/transcoder/video.py @@ -45,10 +45,14 @@ class Video: # Initialize empty screen self.memory_map = screen.MemoryMap( screen_page=1) # type: screen.MemoryMap + self.aux_memory_map = screen.MemoryMap( + screen_page=1) # type: screen.MemoryMap # Accumulates pending edit weights across frames self.update_priority = np.zeros((32, 256), dtype=np.int64) + self.aux_update_priority = np.zeros((32, 256), dtype=np.int64) + def tick(self, cycles: int) -> bool: if cycles > (self.cycles_per_frame * self.frame_number): self.frame_number += 1 @@ -110,51 +114,61 @@ class Video: def worker(): """Invoke bmp2dhr to encode input image frames and push to queue.""" for _idx, _frame in enumerate(self._frame_grabber()): - outfile = "%s/%08dC.BIN" % (frame_dir, _idx) + mainfile = "%s/%08d.BIN" % (frame_dir, _idx) + auxfile = "%s/%08d.AUX" % (frame_dir, _idx) + bmpfile = "%s/%08d.bmp" % (frame_dir, _idx) try: - os.stat(outfile) + os.stat(mainfile) + os.stat(auxfile) except FileNotFoundError: _frame = _frame.resize((280, 192), resample=Image.LANCZOS) _frame.save(bmpfile) subprocess.call( - ["/usr/local/bin/bmp2dhr", bmpfile, "hgr", "D9"]) + ["/usr/local/bin/bmp2dhr", bmpfile, "dhgr", "P0", "A", + "D9"]) os.remove(bmpfile) - _frame = np.fromfile(outfile, dtype=np.uint8) - q.put(_frame) + main = np.fromfile(mainfile, dtype=np.uint8) + aux = np.fromfile(auxfile, dtype=np.uint8) + q.put((main, aux)) - q.put(None) + q.put((None, None)) t = threading.Thread(target=worker, daemon=True) t.start() while True: - frame = q.get() - if frame is None: + main, aux = q.get() + if main is None: break - yield screen.FlatMemoryMap( - screen_page=1, data=frame).to_memory_map() + yield ( + screen.FlatMemoryMap(screen_page=1, data=main).to_memory_map(), + screen.FlatMemoryMap(screen_page=1, data=aux).to_memory_map() + ) q.task_done() t.join() def encode_frame( - self, target: screen.MemoryMap + self, target: screen.MemoryMap, + memory_map: screen.MemoryMap, + update_priority: np.array, ) -> Iterator[opcodes.Opcode]: """Update to match content of frame within provided budget.""" - print("Similarity %f" % (self.update_priority.mean())) - yield from self._index_changes(self.memory_map, target) + print("Similarity %f" % (update_priority.mean())) + yield from self._index_changes(memory_map, target, update_priority) def _index_changes( self, source: screen.MemoryMap, - target: screen.MemoryMap + target: screen.MemoryMap, + update_priority: np.array ) -> Iterator[Tuple[int, int, List[int]]]: """Transform encoded screen to sequence of change tuples.""" @@ -162,16 +176,16 @@ class Video: # Clear any update priority entries that have resolved themselves # with new frame - self.update_priority[diff_weights == 0] = 0 + update_priority[diff_weights == 0] = 0 # Halve existing weights to increase bias to new diffs. # In particular this means that existing updates with diff 1 will # become diff 0, i.e. will only be prioritized if they are still # diffs in the new frame. # self.update_priority >>= 1 - self.update_priority += diff_weights + update_priority += diff_weights - priorities = self._heapify_priorities() + priorities = self._heapify_priorities(update_priority) content_deltas = {} @@ -179,15 +193,15 @@ class Video: _, _, page, offset = heapq.heappop(priorities) # Check whether we've already cleared this diff while processing # an earlier opcode - if self.update_priority[page, offset] == 0: + if update_priority[page, offset] == 0: continue offsets = [offset] content = target.page_offset[page, offset] # Clear priority for the offset we're emitting - self.update_priority[page, offset] = 0 - self.memory_map.page_offset[page, offset] = content + update_priority[page, offset] = 0 + source.page_offset[page, offset] = content diff_weights[page, offset] = 0 # Make sure we don't emit this offset as a side-effect of some @@ -212,9 +226,9 @@ class Video: error=False) # Update priority for the offset we're emitting - self.update_priority[page, o] = p # 0 + update_priority[page, o] = p # 0 - self.memory_map.page_offset[page, o] = content + source.page_offset[page, o] = content if p: # This content byte introduced an error, so put back on the @@ -242,9 +256,9 @@ class Video: return edit_distance.screen_edit_distance( source.page_offset, target.page_offset) - def _heapify_priorities(self) -> List: + def _heapify_priorities(self, update_priority: np.array) -> List: priorities = [] - it = np.nditer(self.update_priority, flags=['multi_index']) + it = np.nditer(update_priority, flags=['multi_index']) while not it.finished: priority = it[0] if not priority: