Applies a first-order high-pass filter.
A first-order high-pass filter. Passes high-frequency components and attenuates low-frequency ones.
These serialized keys are exposed by the block inspector.
This page describes the native runtime behavior observed in the module C++ sources. Declared phases indicate when the simulation engine calls the block.
Code generation: supported for C and Rust.
Manifestmodules/nflow_blocks/libraries/continuous/library.json
{
"id": "builtin.continuous",
"title": "Continuous",
"version": "1.0.0",
"format": "nflow-2",
"metadata": {
"author": "Allan CORNET",
"created": "2026-03-21",
"tool": "Nelson nflow"
},
"comment": "Blocks for continuous-time systems",
"license": "LGPL-3.0",
"builtin": true,
"blocks": [
{
"type": "integrator",
"label": "Integrator",
"icon": "integrator.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"InitialCondition": 0,
"ExternalReset": "none",
"InitialConditionSource": "internal",
"LowerSaturationLimit": "-inf",
"UpperSaturationLimit": "inf"
},
"render": {
"type": "math",
"useRectElement": true,
"bodyClass": "block-body integrator-body",
"mathGroupClass": "integrator-math",
"formula": "\\frac{1}{s}"
}
},
{
"type": "tf",
"label": "Transfer Fn",
"icon": "tf.svg",
"phases": [
"INIT",
"OUTPUT",
"ALGEBRAIC",
"UPDATE"
],
"width": 85,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 85,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Numerator": [
3
],
"Denominator": [
1,
3
]
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "tf-math",
"formula": "\\frac{N(s)}{D(s)}"
}
},
{
"type": "delay",
"label": "Delay",
"icon": "delay.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"DelayTime": 0.1
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "delay-math",
"formula": "e^{-sT}"
}
},
{
"type": "stateSpace",
"label": "State-Space",
"icon": "stateSpace.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 160,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 160,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"A": 1,
"B": 1,
"C": 1,
"D": 0,
"InitialCondition": 0
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "state-space-math",
"formula": "\\dot{x}=Ax+Bu",
"textSize": "16px"
}
},
{
"type": "lpf",
"label": "LPF",
"icon": "lpf.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Cutoff": 1
},
"render": {
"src": "lpf.svg",
"type": "image"
}
},
{
"type": "hpf",
"label": "HPF",
"icon": "hpf.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Cutoff": 1
},
"render": {
"src": "hpf.svg",
"type": "image"
}
},
{
"type": "derivative",
"label": "Derivative",
"icon": "derivative.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {},
"render": {
"type": "math",
"useRectElement": true,
"bodyClass": "block-body",
"mathGroupClass": "derivative-math",
"formula": "\\frac{d}{dt}"
}
},
{
"type": "pid",
"label": "PID",
"icon": "pid.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"P": 1,
"I": 0,
"D": 0,
"N": 0,
"LowerSaturationLimit": "-inf",
"UpperSaturationLimit": "inf"
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "pid-math",
"formula": "\\mathsf{PID}"
}
},
{
"type": "constraint",
"label": "Constraint",
"icon": "constraint.svg",
"phases": [
"INIT",
"OUTPUT",
"DERIVATIVE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"InitialCondition": 0
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "constraint-math",
"formula": "g=0"
}
}
]
}
Runtimemodules/nflow_blocks/src/cpp/continuous/hpf.cpp
//=============================================================================
// Copyright (c) 2016-present Allan CORNET (Nelson)
//=============================================================================
// This file is part of Nelson.
//=============================================================================
// LICENCE_BLOCK_BEGIN
// SPDX-License-Identifier: LGPL-3.0-or-later
// LICENCE_BLOCK_END
//=============================================================================
#include "SimEngineTypes.hpp"
#include "BlockRegistry.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include "NFlowCodegenPolynomial.hpp"
#include "NFlowCodegenHelpers.hpp"
#include "NFlowCodegenLang.hpp"
#include <cmath>
#include <algorithm>
#include "continuous_blocks.hpp"
//=============================================================================
bool
Nelson::NFlow::handleHpf(SimCtx& ctx, const Block& b, Phase phase)
{
auto& st = getState(ctx, b.nid);
const int w = outputWidth(ctx, b.nid, 0);
if (phase == Phase::INIT) {
st.scalar = 0.0;
st.output = 0.0;
if (w > 1) {
st.vec.assign(w, 0.0); // state x
st.vec2.assign(w, 0.0); // latched high-pass output
}
return false;
}
if (phase == Phase::OUTPUT) {
// Variable-step minor step: y = u - x (feedthrough) from the scattered
// global state; else the latched value (fixed-step path, unchanged).
double* xs = blockX(ctx, b.nid);
if (w <= 1) {
if (xs) {
setOutput(ctx, b.nid, getInput(ctx, b.nid, 0, 0.0) - xs[0]);
} else {
setOutput(ctx, b.nid, st.output);
}
} else {
double* y = outputSlice(ctx, b.nid, 0);
if (xs) {
SigView u = getInputSig(ctx, b.nid, 0);
for (int i = 0; i < w; ++i) {
y[i] = sigAt(u, i) - xs[i];
}
} else {
for (int i = 0; i < w; ++i) {
y[i] = (i < (int)st.vec2.size()) ? st.vec2[i] : 0.0;
}
}
}
return false;
}
if (phase == Phase::UPDATE) {
nflow::BlockDescriptor bd(b, ctx.variables);
double fc = std::max(0.0, bd.paramDouble(nflow::kCutoff, 0.0));
double wc = 2.0 * M_PI * fc;
if (w <= 1) {
double inp = getInput(ctx, b.nid, 0, 0.0);
double next = st.scalar + ctx.dt * wc * (inp - st.scalar);
st.scalar = next;
st.output = inp - next;
} else {
SigView u = getInputSig(ctx, b.nid, 0);
if ((int)st.vec.size() != w) {
st.vec.assign(w, 0.0);
}
if ((int)st.vec2.size() != w) {
st.vec2.assign(w, 0.0);
}
for (int i = 0; i < w; ++i) {
double inp = sigAt(u, i);
st.vec[i] += ctx.dt * wc * (inp - st.vec[i]);
st.vec2[i] = inp - st.vec[i]; // latch the high-pass output
}
}
return false;
}
if (phase == Phase::DERIVATIVE) {
// Continuous state derivative: x' = wc*(u - x) (same state as lpf; the
// high-pass output y = u - x is formed in the OUTPUT phase).
double* xdot = blockXdot(ctx, b.nid);
if (xdot) {
nflow::BlockDescriptor bd(b, ctx.variables);
double fc = std::max(0.0, bd.paramDouble(nflow::kCutoff, 0.0));
double wc = 2.0 * M_PI * fc;
double* xs = blockX(ctx, b.nid);
if (w <= 1) {
double x = xs ? xs[0] : st.scalar;
xdot[0] = wc * (getInput(ctx, b.nid, 0, 0.0) - x);
} else {
SigView u = getInputSig(ctx, b.nid, 0);
for (int i = 0; i < w; ++i) {
double x = xs ? xs[i] : (i < (int)st.vec.size() ? st.vec[i] : 0.0);
xdot[i] = wc * (sigAt(u, i) - x);
}
}
}
return false;
}
return false;
}
//=============================================================================
// Single language-parameterized emitter (CodegenLang): the C and Rust
// templates were byte-for-byte clones apart from the state-field access.
static Nelson::NFlow::BlockCodegenTemplate
makeHpfCodegen(Nelson::NFlow::CodegenLang L)
{
using namespace Nelson::NFlow;
BlockCodegenTemplate t;
// Forward-Euler on the internal state x' = wc*(u - x); output y = u_prev - x
// (matching the interpreter's latched high-pass output). No Tustin.
t.emitState = [](const BlockCodegenStateArgs& a) {
a.addState("hpf_x_" + a.id, "", "");
a.addState("hpf_uprev_" + a.id, "", "");
};
t.emitStep = [L](const BlockCodegenArgs& a) {
nflow::BlockDescriptor bd(*a.block, *a.variables);
double wc = 2.0 * M_PI * std::fmax(0.0, bd.paramDouble(nflow::kCutoff, 0.0));
const std::string x = L.sref("hpf_x_" + a.id);
const std::string uprev = L.sref("hpf_uprev_" + a.id);
a.line("out_" + a.id + " = " + uprev + " - " + x + ";");
a.line(x + " = " + x + " + dt * " + a.fmt(wc) + " * (" + a.in[0] + " - " + x + ");");
a.line(uprev + " = " + a.in[0] + ";");
};
// Unified variable-step codegen. Under the solver the high-pass
// is a single continuous state x' = wc*(u - x) with a direct-feedthrough
// output y = u - x (the hpf_uprev field is only the fixed-step Euler path's
// one-sample latch and is unused here), matching the simulator's ode4
// OUTPUT/DERIVATIVE phases.
t.continuousWidth = [](const BlockCodegenArgs&) -> int { return 1; };
t.emitRk4 = [L](const BlockCodegenArgs& a) {
nflow::BlockDescriptor bd(*a.block, *a.variables);
const double wc = 2.0 * M_PI * std::fmax(0.0, bd.paramDouble(nflow::kCutoff, 0.0));
const std::string off = std::to_string(a.stateOffset);
const std::string x = L.sref("hpf_x_" + a.id);
switch (a.rk4Op) {
case Rk4Gather:
a.line(a.rk4Arr + "[" + off + "] = " + x + ";");
break;
case Rk4Scatter:
a.line(x + " = " + a.rk4Arr + "[" + off + "];");
break;
case Rk4Output:
a.line("out_" + a.id + " = " + a.in[0] + " - " + x + ";");
break;
case Rk4Deriv:
a.line(a.rk4Arr + "[" + off + "] = " + a.fmt(wc) + " * (" + a.in[0] + " - " + x + ");");
break;
default:
break;
}
};
return t;
}
//=============================================================================
Nelson::NFlow::BlockCodegenTemplate
Nelson::NFlow::getCodeGenCHpf()
{
return makeHpfCodegen({ false });
}
//=============================================================================
Nelson::NFlow::BlockCodegenTemplate
Nelson::NFlow::getCodeGenRustHpf()
{
return makeHpfCodegen({ true });
}
//=============================================================================