mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2024-06-26 00:29:29 +00:00
Merge branch 'master' of https://github.com/KrisKennaway/ii-vision
This commit is contained in:
commit
75c0cce16a
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
__pycache__/
|
||||
venv/
|
||||
videos/
|
|
@ -15,11 +15,14 @@ class Audio:
|
|||
# TODO: take into account that the available range is slightly offset
|
||||
# as fraction of total cycle count?
|
||||
self._tick_range = [4, 66]
|
||||
self.cycles_per_tick = 73 # type: int
|
||||
|
||||
# TODO: round to divisor of video frame rate
|
||||
self.sample_rate = 14340 # int(1024. * 1024 /
|
||||
# self.cycles_per_tick)
|
||||
# At 73 cycles/tick, true audio playback sample rate is
|
||||
# roughly 1024*1024/73 = 14364 Hz (ignoring ACK slow path).
|
||||
# Typical audio encoding is 44100Hz which is close to 14700*3
|
||||
# Downscaling by 3x gives better results than trying to resample
|
||||
# to a non-divisor. So we cheat a bit and play back the video a tiny
|
||||
# bit (<2%) faster.
|
||||
self.sample_rate = 14700. # type: float
|
||||
|
||||
self.normalization = (
|
||||
normalization or self._normalization()) # type: float
|
||||
|
|
|
@ -7,35 +7,18 @@ import numpy as np
|
|||
import screen
|
||||
|
||||
|
||||
class CycleCounter:
|
||||
"""Counts clock cycles."""
|
||||
|
||||
def __init__(self):
|
||||
self.cycles = 0 # type:int
|
||||
|
||||
def tick(self, cycles: int) -> None:
|
||||
"""Advance cycle counter by some number of clock ticks.
|
||||
|
||||
:param cycles: How many clock cycles to advance
|
||||
"""
|
||||
self.cycles += cycles
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset cycle counter to 0."""
|
||||
|
||||
self.cycles = 0
|
||||
|
||||
|
||||
class Machine:
|
||||
"""Represents Apple II and player virtual machine state."""
|
||||
|
||||
def __init__(self, cycle_counter: CycleCounter,
|
||||
memmap: screen.MemoryMap, update_priority: np.array):
|
||||
def __init__(
|
||||
self,
|
||||
memmap: screen.MemoryMap,
|
||||
update_priority: np.array
|
||||
):
|
||||
self.page = 0x20 # type: int
|
||||
self.content = 0x7f # type: int
|
||||
|
||||
self.memmap = memmap # type: screen.MemoryMap
|
||||
self.cycle_counter = cycle_counter # type: CycleCounter
|
||||
self.update_priority = update_priority # type: np.array
|
||||
|
||||
def emit(self, opcode: "Opcode") -> Iterator[int]:
|
||||
|
@ -52,4 +35,4 @@ class Machine:
|
|||
yield from data
|
||||
|
||||
# Update changes in memory map, if any
|
||||
opcode.apply(self)
|
||||
opcode.apply(self)
|
||||
|
|
|
@ -23,17 +23,16 @@ class Movie:
|
|||
|
||||
self.audio = audio.Audio(
|
||||
filename, normalization=audio_normalization) # type: audio.Audio
|
||||
self.video = video.Video(filename, mode=video_mode) # type: video.Video
|
||||
self.video = video.Video(
|
||||
filename, mode=video_mode,
|
||||
ticks_per_second=self.audio.sample_rate
|
||||
) # type: video.Video
|
||||
|
||||
self.stream_pos = 0 # type: int
|
||||
|
||||
# TODO: don't use this as well as cycle_counter, it's a relic of when
|
||||
# I relied on variable-duration opcodes for frame timings.
|
||||
self.cycles = 0 # type: int
|
||||
self.cycle_counter = machine.CycleCounter()
|
||||
self.ticks = 0 # type: int
|
||||
|
||||
self.state = machine.Machine(
|
||||
self.cycle_counter,
|
||||
self.video.memory_map,
|
||||
self.video.update_priority
|
||||
)
|
||||
|
@ -52,9 +51,13 @@ class Movie:
|
|||
yield opcodes.Header(mode=self.video_mode)
|
||||
|
||||
for au in self.audio.audio_stream():
|
||||
self.cycles += self.audio.cycles_per_tick
|
||||
if self.video.tick(self.cycles):
|
||||
main, aux = next(video_frames)
|
||||
self.ticks += 1
|
||||
if self.video.tick(self.ticks):
|
||||
try:
|
||||
main, aux = next(video_frames)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
if ((self.video.frame_number - 1) % self.every_n_video_frames
|
||||
== 0):
|
||||
print("Starting frame %d" % self.video.frame_number)
|
||||
|
|
|
@ -15,11 +15,11 @@ class TestOpcodes(unittest.TestCase):
|
|||
op2 = opcodes.Nop()
|
||||
self.assertEqual(op1, op2)
|
||||
|
||||
op1 = opcodes.Ack()
|
||||
op2 = opcodes.Ack()
|
||||
op1 = opcodes.Ack(aux_active=False)
|
||||
op2 = opcodes.Ack(aux_active=False)
|
||||
self.assertEqual(op1, op2)
|
||||
|
||||
op1 = opcodes.Ack()
|
||||
op1 = opcodes.Ack(aux_active=False)
|
||||
op2 = opcodes.Nop()
|
||||
self.assertNotEqual(op1, op2)
|
||||
|
||||
|
|
|
@ -32,10 +32,12 @@ class Video:
|
|||
def __init__(
|
||||
self,
|
||||
filename: str,
|
||||
mode: Mode = Mode.HGR
|
||||
ticks_per_second: float,
|
||||
mode: Mode = Mode.HGR,
|
||||
):
|
||||
self.filename = filename # type: str
|
||||
self.mode = mode # type: Mode
|
||||
self.ticks_per_second = ticks_per_second # type: float
|
||||
|
||||
self._reader = skvideo.io.FFmpegReader(filename)
|
||||
|
||||
|
@ -46,8 +48,8 @@ class Video:
|
|||
self.input_frame_rate = float(
|
||||
rate_data[0]) / float(rate_data[1]) # type: float
|
||||
|
||||
self.cycles_per_frame = (
|
||||
self.CLOCK_SPEED / self.input_frame_rate) # type: float
|
||||
self.ticks_per_frame = (
|
||||
self.ticks_per_second / self.input_frame_rate) # type: float
|
||||
self.frame_number = 0 # type: int
|
||||
|
||||
# Initialize empty screen
|
||||
|
@ -62,8 +64,8 @@ class Video:
|
|||
if self.mode == mode.DHGR:
|
||||
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):
|
||||
def tick(self, ticks: int) -> bool:
|
||||
if ticks >= (self.ticks_per_frame * self.frame_number):
|
||||
self.frame_number += 1
|
||||
return True
|
||||
return False
|
||||
|
|
Loading…
Reference in New Issue
Block a user