ii-sound/opcodes_6502.py

125 lines
3.6 KiB
Python

from typing import Iterable, Tuple
import numpy
class Opcode:
"""6502 assembly language opcode with cycle length"""
def __init__(self, cycles, num_bytes, asm="", indent=4, toggle=False):
self.cycles = cycles
self.bytes = num_bytes
self.asm = asm
self.indent = indent
# The opcode toggles speaker (only) on the last cycle
self.toggle = toggle
def __repr__(self):
asm = "[%s] " % self.asm if self.asm else ""
return "%s<%d cycles>" % (asm, self.cycles)
def __str__(self):
indent = " " * self.indent
comment = ' ; %d cycles' % self.cycles if self.cycles else ""
return indent + self.asm + comment
class Literal(Opcode):
def __init__(self, asm, indent=4):
super(Literal, self).__init__(
cycles=0, num_bytes=0, asm=asm, indent=indent)
def __repr__(self):
return "<literal>"
class PaddingOpcode(Opcode):
"""Opcode variant that can be replaced by other interleaved opcodes."""
pass
STA_C030 = Opcode(4, 3, "STA $C030", toggle=True)
# TODO: support 6502 cycle timings (5 cycles instead of 6)
JMP_WDATA = Opcode(6, 3, "JMP (WDATA)")
def padding(cycles):
return PaddingOpcode(cycles, None, "; pad %d cycles" % cycles)
def nops(cycles: int) -> Iterable[Opcode]:
if cycles < 2:
raise ValueError
while cycles:
if cycles == 3:
yield Opcode(3, 2, "STA zpdummy")
cycles -= 3
continue
yield Opcode(2, 1, "NOP")
cycles -= 2
def interleave_opcodes(
base_opcodes: Iterable[Opcode],
interleaved_opcodes: Iterable[Opcode]) -> Iterable[Opcode]:
for op in base_opcodes:
if isinstance(op, PaddingOpcode):
padding_cycles = op.cycles
while padding_cycles > 0:
if interleaved_opcodes:
interleaved_op = interleaved_opcodes[0]
if (padding_cycles - interleaved_op.cycles) >= 0 and (
padding_cycles - interleaved_op.cycles) != 1:
yield interleaved_op
padding_cycles -= interleaved_op.cycles
interleaved_opcodes = interleaved_opcodes[1::]
if not interleaved_opcodes:
return
continue
if padding_cycles == 3:
yield PaddingOpcode(3, 2, "STA zpdummy")
padding_cycles -= 3
else:
yield PaddingOpcode(2, 1, "NOP")
padding_cycles -= 2
assert padding_cycles == 0
else:
yield op
if interleaved_opcodes:
print(interleaved_opcodes)
raise OverflowError
def total_bytes(opcodes: Iterable[Opcode]) -> int:
return sum(op.bytes for op in opcodes)
def total_cycles(opcodes: Iterable[Opcode]) -> int:
return sum(op.cycles for op in opcodes)
def voltages(opcodes: Iterable[Opcode]) -> Tuple[float]:
res = []
speaker = 1.0
for op in opcodes:
if not op.cycles:
continue
res.extend([speaker] * (op.cycles - 1))
if op.toggle:
speaker *= -1
res.append(speaker)
return tuple(res)
def join_voltages(op_seq: Iterable[Iterable[Opcode]]) -> numpy.ndarray:
res = []
last_voltage = 1.0
for ops in op_seq:
op_voltages = voltages(ops)
res.extend(
numpy.array(op_voltages, dtype=numpy.float32) * last_voltage)
last_voltage = res[-1]
return numpy.array(res, dtype=numpy.float32)