diff --git a/source/include/signalflow/node/operators/channel-offset.h b/source/include/signalflow/node/operators/channel-offset.h new file mode 100644 index 00000000..62dafd1b --- /dev/null +++ b/source/include/signalflow/node/operators/channel-offset.h @@ -0,0 +1,29 @@ +#pragma once + +#include "signalflow/core/constants.h" +#include "signalflow/node/node.h" + +#include + +namespace signalflow +{ + +/**--------------------------------------------------------------------------------* + * Offsets the input by a specified number of channels. With an N-channel input + * and an offset of M, the output will have M+N channels. + *---------------------------------------------------------------------------------*/ +class ChannelOffset : public UnaryOpNode +{ + +public: + ChannelOffset(int offset = 0, NodeRef input = nullptr); + + virtual void process(Buffer &out, int num_frames); + +private: + PropertyRef offset; +}; + +REGISTER(ChannelOffset, "channel-offset") + +} diff --git a/source/include/signalflow/signalflow.h b/source/include/signalflow/signalflow.h index 467d4e3d..7ea3e070 100644 --- a/source/include/signalflow/signalflow.h +++ b/source/include/signalflow/signalflow.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/source/src/CMakeLists.txt b/source/src/CMakeLists.txt index 1083986d..94d1b569 100644 --- a/source/src/CMakeLists.txt +++ b/source/src/CMakeLists.txt @@ -92,6 +92,7 @@ set(SRC ${SRC} ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/divide.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/channel-mixer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/channel-array.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/channel-offset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/channel-select.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/channel-crossfade.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/frequency-to-midi-note.cpp diff --git a/source/src/node/operators/channel-offset.cpp b/source/src/node/operators/channel-offset.cpp new file mode 100644 index 00000000..494d86f6 --- /dev/null +++ b/source/src/node/operators/channel-offset.cpp @@ -0,0 +1,37 @@ +#include "signalflow/node/operators/channel-offset.h" + +namespace signalflow +{ + +ChannelOffset::ChannelOffset(int offset, NodeRef input) + : UnaryOpNode(input), offset(offset) +{ + if (!input) + { + throw std::runtime_error("ChannelOffset: No input specified"); + } + + this->name = "channel-offset"; + + this->create_property("offset", this->offset); + + this->set_channels(this->input->get_num_output_channels(), + this->input->get_num_output_channels() + this->offset->int_value()); +} + +void ChannelOffset::process(Buffer &out, int num_frames) +{ + int output_channel = 0; + for (int channel = 0; channel < this->offset->int_value(); channel++) + { + memset(out[output_channel], 0, num_frames * sizeof(sample)); + output_channel++; + } + for (int channel = 0; channel < this->input->get_num_output_channels(); channel++) + { + memcpy(out[output_channel], this->input->out[channel], num_frames * sizeof(sample)); + output_channel++; + } +} + +} diff --git a/source/src/python/nodes.cpp b/source/src/python/nodes.cpp index 37a7de34..8bfbd531 100644 --- a/source/src/python/nodes.cpp +++ b/source/src/python/nodes.cpp @@ -183,6 +183,9 @@ void init_python_nodes(py::module &m) py::class_>(m, "ChannelMixer", "Downmix a multichannel input to a lower-channel output. If num_channels is greater than one, spreads the input channels across the field. If amplitude_compensation is enabled, scale down the amplitude based on the ratio of input to output channels.") .def(py::init(), "num_channels"_a = 1, "input"_a = 0, "amplitude_compensation"_a = true); + py::class_>(m, "ChannelOffset", "Offsets the input by a specified number of channels. With an N-channel input and an offset of M, the output will have M+N channels.") + .def(py::init(), "offset"_a = 0, "input"_a = nullptr); + py::class_>(m, "ChannelSelect", "Select a subset of channels from a multichannel input, starting at offset, up to a maximum of maximum, with the given step.") .def(py::init(), "input"_a = nullptr, "offset"_a = 0, "maximum"_a = 0, "step"_a = 1); diff --git a/tests/test_node_operators.py b/tests/test_node_operators.py index fbd2c01b..f0dcf6a0 100644 --- a/tests/test_node_operators.py +++ b/tests/test_node_operators.py @@ -1,4 +1,4 @@ -from signalflow import Constant, ChannelArray, Sum, SelectInput, Counter, Impulse +from signalflow import Constant, ChannelArray, ChannelOffset, Sum, SelectInput, Counter, Impulse import numpy as np from . import graph @@ -204,6 +204,23 @@ def test_sum(graph): assert np.all(c.output_buffer[1] == 6) assert np.all(c.output_buffer[2] == 6) +def test_channel_offset(graph): + a = ChannelArray([1, 2]) + b = ChannelOffset(0, a) + graph.render_subgraph(b, reset=True) + assert b.num_output_channels == 2 + assert np.all(b.output_buffer[0] == 1) + assert np.all(b.output_buffer[1] == 2) + + b = ChannelOffset(2, a) + graph.render_subgraph(b, reset=True) + assert b.num_output_channels == 4 + assert np.all(b.output_buffer[0] == 0) + assert np.all(b.output_buffer[1] == 0) + assert np.all(b.output_buffer[2] == 1) + assert np.all(b.output_buffer[3] == 2) + + def test_select_input(graph): a = SelectInput([1, 2, 3], 0)