Autogenerate eof with speaker opcode interleaving
This commit is contained in:
parent
cb5de62cc8
commit
bc032ae3ca
|
@ -0,0 +1,168 @@
|
|||
import itertools
|
||||
from typing import Iterable, List
|
||||
|
||||
|
||||
class Opcode:
|
||||
"""6502 assembly language opcode with cycle length"""
|
||||
|
||||
def __init__(self, cycles, asm="", indent=4, toggle=False):
|
||||
self.cycles = cycles
|
||||
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, 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, "STA zpdummy")
|
||||
padding_cycles -= 3
|
||||
else:
|
||||
yield PaddingOpcode(2, "NOP")
|
||||
padding_cycles -= 2
|
||||
assert padding_cycles == 0
|
||||
else:
|
||||
yield op
|
||||
if interleaved_opcodes:
|
||||
print(interleaved_opcodes)
|
||||
raise OverflowError
|
||||
|
||||
|
||||
STA_C030 = Opcode(4, "STA $C030", toggle=True)
|
||||
|
||||
|
||||
def padding(cycles):
|
||||
return PaddingOpcode(cycles, "; pad %d cycles" % cycles)
|
||||
|
||||
|
||||
CORE_EOF = [
|
||||
Literal(
|
||||
"; We've read exactly 2KB from the socket buffer. Before continuing "
|
||||
"we need to ACK this read,"),
|
||||
Literal("; and make sure there's at least another 2KB in the buffer."),
|
||||
Literal(";"),
|
||||
Literal("; Save the W5100 address pointer so we can continue reading the "
|
||||
"socket buffer once we are done."),
|
||||
Literal(
|
||||
"; We know the low-order byte is 0 because Socket RX memory is "
|
||||
"page-aligned and so is 2K frame."),
|
||||
Literal(
|
||||
"; IMPORTANT - from now on until we restore this below, we can't "
|
||||
"trash the Y register!"),
|
||||
Opcode(4, "LDY WADRH"),
|
||||
Literal("; Update new Received Read pointer."),
|
||||
Literal(";"),
|
||||
Literal(
|
||||
"; We know we have received exactly 2KB, so we don't need to read the "
|
||||
"current value from the"),
|
||||
Literal("; hardware. We can track it ourselves instead, which saves a "
|
||||
"few cycles."),
|
||||
Opcode(2, "LDA #>S0RXRD"),
|
||||
Opcode(4, "STA WADRH"),
|
||||
Opcode(2, "LDA #<S0RXRD"),
|
||||
Opcode(4, "STA WADRL"),
|
||||
Opcode(4, "LDA RXRD ; TODO: in principle we could update RXRD outside of "
|
||||
"the EOF path"),
|
||||
Opcode(2, "CLC"),
|
||||
Opcode(2, "ADC #$08"),
|
||||
Opcode(4, "STA WDATA ; Store new high byte of received read pointer"),
|
||||
Opcode(4, "STA RXRD ; Save for next time"),
|
||||
Literal("; Send the Receive command"),
|
||||
Opcode(2, "LDA #<S0CR"),
|
||||
Opcode(4, "STA WADRL"),
|
||||
Opcode(2, "LDA #SCRECV"),
|
||||
Opcode(4, "STA WDATA"),
|
||||
Literal(
|
||||
"; Make sure we have at least 2KB more in the socket buffer so we can "
|
||||
"start another frame."
|
||||
),
|
||||
Opcode(2, "LDA #$07 ; 2"),
|
||||
Opcode(2, "LDX #<S0RXRSR ; Socket 0 Received Size register"),
|
||||
Literal(
|
||||
"; we might loop an unknown number of times here waiting for data but "
|
||||
"the default should be to"),
|
||||
Literal("; fall straight through"),
|
||||
Literal("@0:", indent=0),
|
||||
Opcode(4, "STX WADRL"),
|
||||
Opcode(4, "CMP WDATA ; High byte of received size"),
|
||||
Opcode(2,
|
||||
"BCS @0 ; 2 cycles in common case when there is already sufficient "
|
||||
"data waiting."),
|
||||
Literal(
|
||||
"; We're good to go for another frame. Restore W5100 address pointer "
|
||||
"where we last found it, to"),
|
||||
Literal("; begin iterating through the next 2KB of the socket buffer."),
|
||||
Literal(";"),
|
||||
Literal(
|
||||
"; It turns out that the W5100 automatically wraps the address pointer "
|
||||
"at the end of the 8K"),
|
||||
Literal(
|
||||
"; RX/TX buffers. Since we're using an 8K socket, that means we don't "
|
||||
"have to do any work to"),
|
||||
Literal("; manage the read pointer!"),
|
||||
Opcode(4, "STY WADRH"),
|
||||
Opcode(2, "LDA #$00"),
|
||||
Opcode(4, "STA WADRL"),
|
||||
Opcode(6, "JMP (WDATA)"),
|
||||
]
|
||||
|
||||
|
||||
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 res
|
||||
|
||||
|
||||
base = itertools.cycle([STA_C030, PaddingOpcode(6)])
|
||||
|
||||
eof = list(interleave_opcodes(base, CORE_EOF))
|
||||
for op in eof:
|
||||
print(op)
|
||||
|
||||
print(toggles(eof))
|
46
waveform.py
46
waveform.py
|
@ -3,19 +3,13 @@ import random
|
|||
import soundfile as sf
|
||||
|
||||
|
||||
def wave(count: int):
|
||||
freq = 3875
|
||||
dt = 1 / 44100.
|
||||
damping = -1210 # -0.015167
|
||||
def params(freq, damping, dt):
|
||||
w = freq * 2 * math.pi * dt
|
||||
d = damping * dt
|
||||
e = math.exp(d)
|
||||
c1 = 2 * e * math.cos(w)
|
||||
|
||||
c2 = e * e
|
||||
y1 = y2 = 0
|
||||
x1 = 0
|
||||
x2 = 0
|
||||
t0 = (1 - 2 * e * math.cos(w) + e * e) / (d * d + w * w)
|
||||
t = d * d + w * w - math.pi * math.pi
|
||||
t1 = (1 + 2 * e * math.cos(w) + e * e) / math.sqrt(t * t + 4 * d * d *
|
||||
|
@ -23,20 +17,42 @@ def wave(count: int):
|
|||
b2 = (t1 - t0) / (t1 + t0)
|
||||
b1 = b2 * dt * dt * (t0 + t1) / 2
|
||||
|
||||
return c1, c2, b1, b2
|
||||
|
||||
|
||||
def wave(count: int):
|
||||
freq = 3875
|
||||
dt = 1 / 44100.
|
||||
damping = -1210 # -0.015167
|
||||
|
||||
c1, c2, b1, b2 = params(freq, damping, dt)
|
||||
|
||||
# freq2 = 525
|
||||
# damping2 = -130
|
||||
#
|
||||
# cc1, cc2, bb1, bb2 = params(freq2, damping2, dt)
|
||||
# mult2 = 1# 0.11
|
||||
|
||||
y1 = y2 = 0
|
||||
x1 = 0
|
||||
x2 = 0
|
||||
|
||||
# tm = math.atan(w/d)/w
|
||||
# scale = 500 * math.sqrt(d * d+ w * w) * math.exp(-d * tm) / (dt * 2000)
|
||||
|
||||
x1 = 1.0
|
||||
th = 44100/3875/2
|
||||
th = 44100
|
||||
switch = th
|
||||
scale = 3 # TODO: analytic expression
|
||||
maxy = 0
|
||||
for i in range(count):
|
||||
y = c1 * y1 - c2 * y2 + b1 * x1 + b2 * x2
|
||||
# y = (c1 * y1 - c2 * y2 + b1 * x1 + b2 * x2) + mult2 * (
|
||||
# 1 - cc1 * y1 + cc2 * y2 - bb1 * x1 - bb2 * x2)
|
||||
y = (c1 * y1 - c2 * y2 + b1 * x1 + b2 * x2)
|
||||
x2 = x1
|
||||
# if i >= switch:
|
||||
# x1 = -x1
|
||||
# switch += th
|
||||
if i >= switch:
|
||||
x1 = -x1
|
||||
switch += th
|
||||
y2 = y1
|
||||
y1 = y
|
||||
if math.fabs(y) > maxy:
|
||||
|
@ -46,9 +62,9 @@ def wave(count: int):
|
|||
|
||||
|
||||
def main():
|
||||
print(list(wave(1020400)))
|
||||
# with sf.SoundFile("out.wav", "w", samplerate=44100, channels=1) as f:
|
||||
# f.write(list(wave(441000)))
|
||||
# print(list(wave(1020400)))
|
||||
with sf.SoundFile("out.wav", "w", samplerate=44100, channels=1) as f:
|
||||
f.write(list(wave(441000)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Reference in New Issue