Add partial support for 4-cycle NOP; NOP opcode pairs

Count and report on the number of speaker toggles/sec during encoding
This commit is contained in:
kris 2020-12-28 12:29:05 +00:00
parent 089591737f
commit a1a1f33c21
2 changed files with 38 additions and 13 deletions

View File

@ -101,7 +101,8 @@ def frame_horizon(frame_offset: int, lookahead_steps: int):
return frame_offset
def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int):
def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
sample_rate: int):
"""Computes optimal sequence of player opcodes to reproduce audio data."""
dlen = len(data)
@ -117,6 +118,7 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int):
position = 0.0
voltage = -1.0
toggles = 0
all_partial_positions = {}
# Precompute partial_positions so we don't skew ETA during encoding.
for i in range(2048):
@ -178,6 +180,7 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int):
opcode = candidate_opcodes[opcode_idx][0]
opcode_length = opcodes.cycle_length(opcode)
opcode_counts[opcode] += 1
toggles += opcodes.TOGGLES[opcode]
# Apply this opcode to evolve the speaker position
delta_powers, partial_positions, last_voltage = \
@ -200,6 +203,8 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int):
yield opcodes.Opcode.EXIT
eta.done()
print("Total error %f" % total_err)
toggles_per_sec = toggles / dlen * sample_rate
print("%d speaker toggles/sec" % toggles_per_sec)
print("Opcodes used:")
for v, k in sorted(list(opcode_counts.items()), key=lambda kv: kv[1],
@ -240,7 +245,8 @@ def main(argv):
with open(out, "wb+") as f:
for opcode in audio_bytestream(
preprocess(serve_file, sample_rate), step, lookahead_steps):
preprocess(serve_file, sample_rate), step, lookahead_steps,
sample_rate):
f.write(bytes([opcode.value]))

View File

@ -12,19 +12,20 @@ class Opcode(enum.Enum):
INC = 5
INCX = 6
JMP_INDIRECT = 7
NOPNOP = 8
# Byte length of opcodes
OPCODE_LEN = {
Opcode.NOP: 1, Opcode.NOP3: 2, Opcode.STA: 3, Opcode.STAX: 3,
Opcode.INC: 3, Opcode.INCX: 3, Opcode.JMP_INDIRECT: 3
Opcode.INC: 3, Opcode.INCX: 3, Opcode.JMP_INDIRECT: 3, Opcode.NOPNOP: 2
}
ASM = {
Opcode.NOP: "NOP", Opcode.NOP3: "STA zpdummy",
Opcode.STA: "STA $C030", Opcode.STAX: "STA $C030,X",
Opcode.INC: "INC $C030", Opcode.INCX: "INC $C030,X",
Opcode.JMP_INDIRECT: "JMP (WDATA)"
Opcode.JMP_INDIRECT: "JMP (WDATA)", Opcode.NOPNOP: "NOP NOP"
}
# Applied speaker voltages resulting from executing opcode
@ -37,19 +38,24 @@ VOLTAGES = {
Opcode.NOP3: [1, 1, 1],
# TODO: support 6502 cycle counts as well
Opcode.JMP_INDIRECT: [1, 1, 1, 1, 1, 1],
Opcode.NOPNOP: [1, 1, 1, 1],
}
def voltage_sequence(
opcodes: Iterable[Opcode], starting_voltage=1.0) -> numpy.ndarray:
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 numpy.array(out, dtype=numpy.float64)
return tuple(numpy.array(out, dtype=numpy.float32)), toggles
def all_opcodes(
@ -72,27 +78,28 @@ def all_opcodes(
def generate_player(player_ops: List[Tuple[Opcode]], opcode_filename: str,
player_filename: str, truncate_to_page=True):
player_filename: str):
num_bytes = 0
seqs = {}
num_op = 0
offset = 0
unique_opcodes = {}
toggles = {}
done = False
with open(player_filename, "w+") as f:
for i, k in enumerate(player_ops):
is_unique = False
new_unique = []
player_op = []
player_op_len = 0
new_offset = offset
for j, o in enumerate(k):
seq = tuple(voltage_sequence(k[j:], 1.0))
seq, tog = voltage_sequence(k[j:], 1.0)
dup = seqs.setdefault(seq, i)
if dup == i:
player_op.append("tick_%02x: ; voltages %s" % (
new_offset, seq))
is_unique = True
unique_opcodes[new_offset] = seq
new_unique.append((new_offset, seq, tog))
player_op.append(" %s" % ASM[o])
player_op_len += OPCODE_LEN[o]
new_offset += OPCODE_LEN[o]
@ -100,13 +107,17 @@ def generate_player(player_ops: List[Tuple[Opcode]], opcode_filename: str,
# If at least one of the partial opcode sequences was not
# a dup, then add it to the player
if is_unique:
if (num_bytes + player_op_len) >= 252:
if new_unique:
# Reserve 9 bytes for EXIT and SLOWPATH
if (num_bytes + player_op_len) > (256 - 9):
print("Out of space, truncating.")
break
num_op += 1
f.write("\n".join(player_op))
num_bytes += player_op_len
for op_offset, seq, tog in new_unique:
unique_opcodes[op_offset] = seq
toggles[op_offset] = tog
offset = new_offset
f.write("; %d bytes\n" % num_bytes)
@ -126,6 +137,13 @@ def generate_player(player_ops: List[Tuple[Opcode]], opcode_filename: str,
"\n" % (o, v))
f.write("}\n")
f.write("\n\nTOGGLES = {\n")
for o, v in toggles.items():
f.write(
" Opcode.TICK_%02x: %d,\n" % (o, v)
)
f.write("}\n")
def all_opcode_combinations(
max_cycles: int, opcodes: List[Opcode], start_opcodes: List[Opcode]
@ -151,6 +169,7 @@ if __name__ == "__main__":
opcodes=[
Opcode.NOP,
Opcode.NOP3,
# Opcode.NOPNOP,
Opcode.STA,
# Opcode.INC,
# Opcode.INCX