Skip to content

Commit 0b8d496

Browse files
committedApr 19, 2015
coefficients: cleanup and refactor some code into CoefficientSource
1 parent 904bcd2 commit 0b8d496

File tree

1 file changed

+48
-39
lines changed

1 file changed

+48
-39
lines changed
 

Diff for: ‎artiq/wavesynth/coefficients.py

+48-39
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(self, d, x0=None, order=4):
4646

4747
def pad_const(x, n, axis=0):
4848
"""Prefix and postfix the array `x` by `n` repetitions of the first and
49-
last vlaue along `axis`.
49+
last value along `axis`.
5050
"""
5151
a = np.repeat(x.take([0], axis), n, axis)
5252
b = np.repeat(x.take([-1], axis), n, axis)
@@ -86,9 +86,9 @@ class CoefficientSource:
8686
def crop_x(self, start, stop, num=2):
8787
"""Return an array of valid sample positions.
8888
89-
This function needs to be implemented only if this
90-
`CoefficientSource` does not support sampling at arbitrary
91-
positions.
89+
This method needs to be overloaded if this `CoefficientSource`
90+
does not support sampling at arbitrary positions or at arbitrary
91+
density.
9292
9393
:param start: First sample position.
9494
:param stop: Last sample position.
@@ -99,15 +99,23 @@ def crop_x(self, start, stop, num=2):
9999
return np.linspace(start, stop, num)
100100

101101
def scale_x(self, x, scale):
102+
# TODO: This could be moved to the the Driver/Mediator code as it is
103+
# device-specific.
102104
"""Scale and round sample positions.
103105
106+
The sample times may need to be changed and/or decimated if
107+
incompatible with hardware requirements.
108+
104109
:param x: Input sample positions in data space.
105110
:param scale: Data space position to cycles conversion scale,
106111
in units of x-units per clock cycle.
107112
:return: `x_sample`, the rounded sample positions and `durations`, the
108113
integer durations of the individual samples in cycles.
109114
"""
110-
raise NotImplementedError
115+
t = np.rint(x/scale)
116+
x_sample = t*scale
117+
durations = np.diff(t).astype(np.int)
118+
return x_sample, durations
111119

112120
def __call__(self, x, **kwargs):
113121
"""Perform sampling and return coefficients.
@@ -118,25 +126,18 @@ def __call__(self, x, **kwargs):
118126
raise NotImplementedError
119127

120128
def get_segment_data(self, start, stop, scale, cutoff=1e-12,
121-
min_duration=1, min_length=20,
122129
target="bias", variable="amplitude"):
123130
"""Build wavesynth segment data.
124131
125132
:param start: see `crop_x()`.
126133
:param stop: see `crop_x()`.
127134
:param scale: see `scale_x()`.
128-
:param num: see `crop_x()`.
129135
:param cutoff: coefficient cutoff towards zero to compress data.
130-
:param min_duration: Minimum duration of a line.
131-
:param min_length: Minimum segment length to space triggers.
132136
"""
133137
x = self.crop_x(start, stop)
134138
x_sample, durations = self.scale_x(x, scale)
135139
coefficients = self(x_sample)
136-
np.clip(np.fabs(durations), min_duration, None, out=durations)
137-
if len(durations) == 1:
138-
durations[0] = max(durations[0], min_length)
139-
if start == stop:
140+
if len(x_sample) == 1 and start == stop:
140141
coefficients = coefficients[:1]
141142
# rescale coefficients accordingly
142143
coefficients *= (scale*np.sign(durations))**np.arange(
@@ -146,12 +147,14 @@ def get_segment_data(self, start, stop, scale, cutoff=1e-12,
146147
return build_segment(durations, coefficients, target=target,
147148
variable=variable)
148149

149-
def extend_segment(self, segment, *args, **kwargs):
150+
def extend_segment(self, segment, trigger=True, *args, **kwargs):
150151
"""Extend a wavesynth segment.
151152
152153
See `get_segment()` for arguments.
153154
"""
154-
for line in self.get_segment_data(*args, **kwargs):
155+
for i, line in enumerate(self.get_segment_data(*args, **kwargs)):
156+
if i == 0:
157+
line["trigger"] = True
155158
segment.add_line(**line)
156159

157160

@@ -183,36 +186,42 @@ def crop_x(self, start, stop):
183186
x = self.x[ia:ib]
184187
return np.r_[start, x, stop]
185188

186-
def scale_x(self, x, scale, nudge=1e-9):
189+
def scale_x(self, x, scale, min_duration=1, min_length=20):
190+
"""
191+
Due to minimum duration and/or minimum segment length constraints
192+
this method may drop samples from `x_sample` or adjust `durations` to
193+
comply. But `x_sample` and `durations` should be kept consistent.
194+
195+
:param min_duration: Minimum duration of a line.
196+
:param min_length: Minimum segment length to space triggers.
197+
"""
187198
# We want to only sample a spline at t_knot + epsilon
188199
# where the highest order derivative has just jumped
189200
# and is valid at least up to the next knot after t_knot.
190201
#
191-
# To ensure that we are on the right side of a knot:
202+
# To ensure that we are on the correct side of a knot:
192203
# * only ever increase t when rounding (for increasing t)
193204
# * or only ever decrease it (for decreasing t)
194-
#
195-
# The highest derivative is discontinuous at t
196-
# and the correct value for a segment is obtained
197-
# for t_int >= t_float == t_knot (and v.v. for t decreasing).
198-
x = x/scale
199-
inc = np.diff(x) >= 0
205+
t = x/scale
206+
inc = np.diff(t) >= 0
200207
inc = np.r_[inc, inc[-1]]
201-
x = np.where(inc, np.ceil(x + nudge), np.floor(x - nudge))
202-
if len(x) > 1 and x[0] == x[1]:
203-
x = x[1:]
204-
if len(x) > 1 and x[-2] == x[-1]:
205-
x = x[:-1]
206-
x_sample = x[:-1]*scale
207-
durations = np.diff(x.astype(np.int))
208-
return x_sample, durations
208+
t = np.where(inc, np.ceil(t), np.floor(t))
209+
dt = np.diff(t.astype(np.int))
210+
211+
valid = np.absolute(dt) >= min_duration
212+
dt = dt[valid]
213+
t = t[np.r_[True, valid]]
214+
if dt.shape[0] == 1:
215+
dt[0] = max(dt[0], min_length)
216+
x_sample = t[:-1]*scale
217+
return x_sample, dt
209218

210219
def __call__(self, x):
211220
return self.spline(x)
212221

213222

214223
class ComposingSplineSource(SplineSource):
215-
# TODO
224+
# TODO: verify, test, document
216225
def __init__(self, x, y, components, order=4, pad_dx=1.):
217226
self.x = np.asanyarray(x)
218227
assert self.x.ndim == 1
@@ -236,18 +245,18 @@ def __init__(self, x, y, components, order=4, pad_dx=1.):
236245
components, self.x, order)
237246

238247
def __call__(self, t, gain={}, offset={}):
239-
der = list((set(self.components.n) | set(offset)) & set(self.der))
248+
der = list((set(self.components.n) | set(offset))
249+
& set(range(len(self.splines))))
240250
u = np.zeros((self.splines[0].order, len(self.splines[0].s), len(t)))
241251
# der, order, ele, t
242252
p = np.array([self.splines[i](t) for i in der])
243-
# order, der, None, t
244-
s = self.components(t)
245253
s_gain = np.array([gain.get(_, 1.) for _ in self.components.n])
246-
s = s[:, :, None, :]*s_gain[None, :, None, None]
254+
# order, der, None, t
255+
s = self.components(t)[:, :, None, :]*s_gain[None, :, None, None]
247256
for k, v in offset.items():
248-
if v and k in self.der:
249-
u += v*p[self.der.index(k)]
250-
ps = p[[self.der.index(_) for _ in self.shims.der]]
257+
if v:
258+
u += v*p[k]
259+
ps = p[self.shims.n]
251260
for i in range(u.shape[1]):
252261
for j in range(i + 1):
253262
u[i] += binom(i, j)*(s[j]*ps[:, i - j]).sum(0)

0 commit comments

Comments
 (0)
Please sign in to comment.