Resample
Change the sample rate of a complete window; use Buffer to set its length.
input
out
- Type name
signal:Resample- Plane
signal- Tags
transform- Language
python- Tier
in-process- Bundle
signal- Source
node-bundles/signal/resample.py- Availability
- available
Slots
| Slot | Direction | Type | |
|---|---|---|---|
input | input | ARRAY | |
out | output | ARRAY |
Parameters
resample
| Name | Type | Default | Range | Doc |
|---|---|---|---|---|
sfreq | float | 250 | 1 … 100000 | Target rate in Hz. The output carries the actual rate. |
axis | int | -1 | -8 … 7 | Which axis holds the samples. -1 is time. |
common
| Name | Type | Default | Range | Doc |
|---|---|---|---|---|
autotrigger | bool | false | Run on the node's own schedule, instead of waiting for an input frame. Turn this on for sources; leave it off for transforms driven by their input. | |
max_frequency | float | 0 | 0 … 100 | Rate cap for this node, read through `frequency_mode`. 0 means uncapped — the node runs as often as the scheduler and its inputs allow. |
frequency_mode | string | updates_per_second | updates_per_second | seconds_per_update | How to read `max_frequency`: as a rate in Hz (updates per second), or as a period in seconds between updates — convenient for very slow nodes. |
Source
The current source of this node, as it stands in the goofi repository at node-bundles/signal/resample.py.
"""Resample each complete window independently with an anti-alias filter.
The output length rounds up to a whole sample. No history is kept between windows.
"""
from fractions import Fraction
import numpy as np
from scipy.signal import resample_poly
import goofi
class Resample(goofi.Node):
"""Change the sample rate of a complete window; use Buffer to set its length."""
TAGS = ["transform"]
INPUTS = {"input": goofi.InputSlot(goofi.DataType.ARRAY, required=True)}
OUTPUTS = {"out": goofi.DataType.ARRAY}
PARAMS = {
"resample": {
"sfreq": goofi.FloatParam(250.0, 1.0, 100000.0, doc="Target rate in Hz. The output carries the actual rate."),
"axis": goofi.IntParam(-1, -8, 7, options=[-2, -1, 0, 1, 2], doc="Which axis holds the samples. -1 is time."),
}
}
def process(self, input):
p = self.params.resample
source = input.meta.get("sfreq")
if source is None or not np.isfinite(source) or source <= 0:
raise ValueError("this node needs a positive finite sample rate")
if not np.isfinite(p.sfreq) or p.sfreq <= 0:
raise ValueError("the target sample rate must be positive and finite")
if not -input.data.ndim <= p.axis < input.data.ndim:
raise ValueError(f"axis {p.axis} is outside this {input.data.ndim}-dimensional window")
axis = p.axis if p.axis >= 0 else p.axis + input.data.ndim
ratio = Fraction(p.sfreq / source).limit_denominator(100000)
up, down = ratio.numerator, ratio.denominator
if up == 0 or max(up, down) > 100000:
raise ValueError("rate conversion is too large; choose a target closer to the input rate")
out = resample_poly(np.asarray(input.data, dtype=np.float64), up, down, axis=axis)
axes = {k: v for k, v in input.meta.get("channels", {}).items() if k != f"dim{axis}"}
return out.astype(np.float32), {**input.meta, "channels": axes, "sfreq": source * up / down}