goofi v3.0.0

Resample

Change the sample rate of a complete window; use Buffer to set its length.

Resample py
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

SlotDirectionType
inputinputARRAY
outoutputARRAY

Parameters

resample

NameTypeDefaultRangeDoc
sfreqfloat2501 … 100000Target rate in Hz. The output carries the actual rate.
axisint-1-8 … 7Which axis holds the samples. -1 is time.

common

NameTypeDefaultRangeDoc
autotriggerboolfalseRun 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_frequencyfloat00 … 100Rate cap for this node, read through `frequency_mode`. 0 means uncapped — the node runs as often as the scheduler and its inputs allow.
frequency_modestringupdates_per_secondupdates_per_second | seconds_per_updateHow 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.

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}

← All nodes