forked from M-Labs/artiq
wavesynth: new semantics, fix compensation
* "trigger" now means that the corresponding line will only start once the trigger line is high. * "jump" is implicit as the last line in a frame must jump back. * spline coefficients are now compensated for finite time step size
This commit is contained in:
parent
1f545346e3
commit
e870b27830
|
@ -113,13 +113,9 @@ class _Frame:
|
||||||
"dac_divider": dac_divider,
|
"dac_divider": dac_divider,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
"channel_data": channel_data,
|
"channel_data": channel_data,
|
||||||
"wait_trigger": False,
|
|
||||||
"jump": False
|
|
||||||
} for dac_divider, duration, channel_data in segment.lines]
|
} for dac_divider, duration, channel_data in segment.lines]
|
||||||
segment_program[-1]["wait_trigger"] = True
|
segment_program[0]["trigger"] = True
|
||||||
r += segment_program
|
r += segment_program
|
||||||
r[-1]["wait_trigger"] = False
|
|
||||||
r[-1]["jump"] = True
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
|
@ -203,8 +199,7 @@ class CompoundPDQ2(AutoDB):
|
||||||
"duration": full_line["duration"],
|
"duration": full_line["duration"],
|
||||||
"channel_data": full_line["channel_data"]
|
"channel_data": full_line["channel_data"]
|
||||||
[n*channels_per_pdq2:(n+1)*channels_per_pdq2],
|
[n*channels_per_pdq2:(n+1)*channels_per_pdq2],
|
||||||
"wait_trigger": full_line["wait_trigger"],
|
"trigger": full_line["trigger"],
|
||||||
"jump": full_line["jump"]
|
|
||||||
}
|
}
|
||||||
frame_program.append(line)
|
frame_program.append(line)
|
||||||
program.append(frame_program)
|
program.append(frame_program)
|
||||||
|
|
|
@ -19,8 +19,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": False}
|
"clear": False}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": False,
|
"trigger": True
|
||||||
"jump": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# frame 0, segment 0, line 1
|
# frame 0, segment 0, line 1
|
||||||
|
@ -34,8 +33,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": False}
|
"clear": False}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": False,
|
"trigger": False
|
||||||
"jump": True
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -52,8 +50,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": False}
|
"clear": False}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": False,
|
"trigger": True
|
||||||
"jump": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# frame 1, segment 0, line 1
|
# frame 1, segment 0, line 1
|
||||||
|
@ -67,8 +64,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": False}
|
"clear": False}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": False,
|
"trigger": False
|
||||||
"jump": True
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -85,8 +81,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": False}
|
"clear": False}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": True,
|
"trigger": True
|
||||||
"jump": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# frame 2, segment 1, line 0
|
# frame 2, segment 1, line 0
|
||||||
|
@ -100,8 +95,7 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
"clear": True}
|
"clear": True}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wait_trigger": False,
|
"trigger": True
|
||||||
"jump": True
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -112,14 +106,11 @@ class TestSynthesizer(unittest.TestCase):
|
||||||
|
|
||||||
def drive(self):
|
def drive(self):
|
||||||
s = self.dev
|
s = self.dev
|
||||||
r = s.trigger(0)
|
y = []
|
||||||
y = r[0]
|
for f in 0, 2, None, 1:
|
||||||
r = s.trigger(2)
|
if f is not None:
|
||||||
y += r[0]
|
s.select(f)
|
||||||
r = s.trigger()
|
y += s.trigger()[0]
|
||||||
y += r[0]
|
|
||||||
r = s.trigger(1)
|
|
||||||
y += r[0]
|
|
||||||
x = list(range(600))
|
x = list(range(600))
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,21 @@ from copy import copy
|
||||||
from math import cos, pi
|
from math import cos, pi
|
||||||
|
|
||||||
|
|
||||||
|
def discrete_compensate(c):
|
||||||
|
if len(c) > 2:
|
||||||
|
c[1] += c[2]/2
|
||||||
|
if len(c) > 3:
|
||||||
|
c[1] += c[3]/6
|
||||||
|
c[2] += c[3]
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
class Spline:
|
class Spline:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.c = [0.0]
|
self.c = [0.0]
|
||||||
|
|
||||||
def set_coefficients(self, c):
|
def set_coefficients(self, c):
|
||||||
self.c = copy(c)
|
self.c = discrete_compensate(copy(c))
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
r = self.c[0]
|
r = self.c[0]
|
||||||
|
@ -22,17 +31,18 @@ class SplinePhase:
|
||||||
self.c0 = 0.0
|
self.c0 = 0.0
|
||||||
|
|
||||||
def set_coefficients(self, c):
|
def set_coefficients(self, c):
|
||||||
self.c = self.c[0:1] + c[1:]
|
|
||||||
self.c0 = c[0]
|
self.c0 = c[0]
|
||||||
|
self.c[1:] = discrete_compensate(c[1:])
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.c[0] = 0.0
|
self.c[0] = 0.0
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
r = self.c[0] + self.c0
|
r = self.c[0]
|
||||||
for i in range(len(self.c) - 1):
|
for i in range(len(self.c) - 1):
|
||||||
self.c[i] = (self.c[i] + self.c[i + 1]) % 1.0
|
self.c[i] += self.c[i + 1]
|
||||||
return r
|
self.c[i] %= 1.0
|
||||||
|
return r + self.c0
|
||||||
|
|
||||||
|
|
||||||
class DDS:
|
class DDS:
|
||||||
|
@ -48,14 +58,17 @@ class Wave:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bias = Spline()
|
self.bias = Spline()
|
||||||
self.dds = DDS()
|
self.dds = DDS()
|
||||||
self.last = 0.
|
self.v = 0.
|
||||||
self.silence = False
|
self.silence = False
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
v = self.bias.next() + self.dds.next()
|
v = self.bias.next() + self.dds.next()
|
||||||
if not self.silence:
|
if not self.silence:
|
||||||
self.last = v
|
self.v = v
|
||||||
return self.last
|
return self.v
|
||||||
|
|
||||||
|
def set_silence(self, s):
|
||||||
|
self.silence = s
|
||||||
|
|
||||||
|
|
||||||
class TriggerError(Exception):
|
class TriggerError(Exception):
|
||||||
|
@ -67,29 +80,30 @@ class Synthesizer:
|
||||||
self.channels = [Wave() for _ in range(nchannels)]
|
self.channels = [Wave() for _ in range(nchannels)]
|
||||||
self.program = program
|
self.program = program
|
||||||
# line_iter is None: "wait for segment selection" state
|
# line_iter is None: "wait for segment selection" state
|
||||||
# otherwise: iterator on the current position in the segment
|
# otherwise: iterator on the current position in the frame
|
||||||
self.line_iter = None
|
self.line_iter = None
|
||||||
|
|
||||||
def trigger(self, selection=None):
|
def select(self, selection):
|
||||||
if selection is None:
|
|
||||||
if self.line_iter is None:
|
|
||||||
raise TriggerError
|
|
||||||
else:
|
|
||||||
if self.line_iter is not None:
|
if self.line_iter is not None:
|
||||||
raise TriggerError
|
raise TriggerError
|
||||||
self.line_iter = iter(self.program[selection])
|
self.line_iter = iter(self.program[selection])
|
||||||
|
self.line = next(self.line_iter)
|
||||||
|
|
||||||
|
def trigger(self):
|
||||||
|
if self.line_iter is None:
|
||||||
|
raise TriggerError
|
||||||
|
|
||||||
|
line = self.line
|
||||||
|
if not line.get("trigger", False):
|
||||||
|
raise TriggerError
|
||||||
|
|
||||||
r = [[] for _ in self.channels]
|
r = [[] for _ in self.channels]
|
||||||
while True:
|
while True:
|
||||||
line = next(self.line_iter)
|
|
||||||
|
|
||||||
if line.get("dac_divider", 1) != 1:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
for channel, channel_data in zip(self.channels,
|
for channel, channel_data in zip(self.channels,
|
||||||
line["channel_data"]):
|
line["channel_data"]):
|
||||||
if "bias" in channel_data:
|
if "bias" in channel_data:
|
||||||
channel.bias.set_coefficients(channel_data["bias"]["amplitude"])
|
channel.bias.set_coefficients(
|
||||||
|
channel_data["bias"]["amplitude"])
|
||||||
if "dds" in channel_data:
|
if "dds" in channel_data:
|
||||||
channel.dds.amplitude.set_coefficients(
|
channel.dds.amplitude.set_coefficients(
|
||||||
channel_data["dds"]["amplitude"])
|
channel_data["dds"]["amplitude"])
|
||||||
|
@ -98,22 +112,20 @@ class Synthesizer:
|
||||||
channel_data["dds"]["phase"])
|
channel_data["dds"]["phase"])
|
||||||
if channel_data["dds"].get("clear", False):
|
if channel_data["dds"].get("clear", False):
|
||||||
channel.dds.phase.clear()
|
channel.dds.phase.clear()
|
||||||
channel.silence = channel_data.get("silence", False)
|
channel.set_silence(channel_data.get("silence", False))
|
||||||
|
|
||||||
|
if line.get("dac_divider", 1) != 1:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
for channel, rc in zip(self.channels, r):
|
for channel, rc in zip(self.channels, r):
|
||||||
for i in range(line["duration"]):
|
for i in range(line["duration"]):
|
||||||
rc.append(channel.next())
|
rc.append(channel.next())
|
||||||
|
|
||||||
if line.get("wait_trigger", False):
|
|
||||||
return r
|
|
||||||
if line.get("jump", False):
|
|
||||||
if not line.get("wait_trigger", False):
|
|
||||||
raise ValueError("Jumps should be with wait_trigger")
|
|
||||||
try:
|
try:
|
||||||
next(self.line_iter)
|
self.line = line = next(self.line_iter)
|
||||||
raise ValueError("Jump in the middle of a frame")
|
if line.get("trigger", False):
|
||||||
|
return r
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
|
||||||
self.line_iter = None
|
self.line_iter = None
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue