mirror of
https://github.com/KrisKennaway/ii-sound.git
synced 2024-09-27 10:59:55 +00:00
127 lines
3.7 KiB
Python
127 lines
3.7 KiB
Python
import itertools
|
|
from typing import Iterable, List
|
|
|
|
|
|
class Opcode:
|
|
"""6502 assembly language opcode with cycle length"""
|
|
|
|
def __init__(self, cycles, bytes, asm="", indent=4, toggle=False):
|
|
self.cycles = cycles
|
|
self.bytes = bytes
|
|
self.asm = asm
|
|
self.indent = indent
|
|
# Assume toggles speaker 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, 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
|
|
|
|
|
|
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
|
|
|
|
|
|
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]:
|
|
print(cycles)
|
|
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 voltage_sequence(
|
|
# opcodes: Iterable[Opcode], starting_voltage=1.0
|
|
# ) -> Tuple[numpy.float32, int]:
|
|
# """Voltage sequence for sequence of opcodes."""
|
|
# out = []
|
|
# v = starting_voltage
|
|
# toggles = 0
|
|
# for op in opcodes:
|
|
# for nv in v * numpy.array(VOLTAGES[op]):
|
|
# if v != nv:
|
|
# toggles += 1
|
|
# v = nv
|
|
# out.append(v)
|
|
# return tuple(numpy.array(out, dtype=numpy.float32)), toggles
|
|
|
|
|
|
def toggles(opcodes: Iterable[Opcode]) -> List[bool]:
|
|
res = []
|
|
speaker = True
|
|
for op in opcodes:
|
|
if not op.cycles:
|
|
continue
|
|
res.extend([speaker] * (op.cycles - 1))
|
|
if op.toggle:
|
|
speaker = not speaker
|
|
res.append(speaker)
|
|
return tuple(res)
|
|
|
|
|
|
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)
|