From f6f71610c127003d4ec64421d69949fd8b5075c1 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Tue, 13 Aug 2024 18:15:53 +0100 Subject: [PATCH] Add TriggerRoundRobin node; because this requires an ordered sequence, store Node outputs as a vector, not a set --- .../libs/signalflow-stubs/signalflow.pyi | 8 +++- .../signalflow/buffer/wavetable-buffer.h | 6 +++ source/include/signalflow/node/node.h | 4 +- .../{operators => sequencing}/trigger-mult.h | 0 .../node/sequencing/trigger-round-robin.h | 31 ++++++++++++++ source/include/signalflow/signalflow.h | 3 +- source/src/CMakeLists.txt | 3 +- source/src/node/node.cpp | 17 ++++++-- .../trigger-mult.cpp | 5 +-- .../node/sequencing/trigger-round-robin.cpp | 40 +++++++++++++++++++ source/src/python/nodes.cpp | 9 +++-- 11 files changed, 112 insertions(+), 14 deletions(-) rename source/include/signalflow/node/{operators => sequencing}/trigger-mult.h (100%) create mode 100644 source/include/signalflow/node/sequencing/trigger-round-robin.h rename source/src/node/{operators => sequencing}/trigger-mult.cpp (78%) create mode 100644 source/src/node/sequencing/trigger-round-robin.cpp diff --git a/auxiliary/libs/signalflow-stubs/signalflow.pyi b/auxiliary/libs/signalflow-stubs/signalflow.pyi index 04d582dd..63a86f2f 100644 --- a/auxiliary/libs/signalflow-stubs/signalflow.pyi +++ b/auxiliary/libs/signalflow-stubs/signalflow.pyi @@ -10,7 +10,7 @@ from __future__ import annotations import numpy import typing import typing_extensions -__all__ = ['ADSREnvelope', 'ASREnvelope', 'Abs', 'Add', 'AllpassDelay', 'AmplitudeToDecibels', 'AudioGraph', 'AudioGraphConfig', 'AudioIOException', 'AudioIn', 'AudioOut', 'AudioOut_Abstract', 'AudioOut_Dummy', 'AzimuthPanner', 'BeatCutter', 'BiquadFilter', 'Buffer', 'Buffer2D', 'BufferLooper', 'BufferPlayer', 'BufferRecorder', 'CPUUsageAboveLimitException', 'ChannelArray', 'ChannelCrossfade', 'ChannelMixer', 'ChannelPanner', 'ChannelSelect', 'Clip', 'ClockDivider', 'CombDelay', 'Compressor', 'Constant', 'Cos', 'Counter', 'CrossCorrelate', 'DCFilter', 'DecibelsToAmplitude', 'DetectSilence', 'DeviceNotFoundException', 'Divide', 'EQ', 'Envelope', 'EnvelopeBuffer', 'Equal', 'Euclidean', 'FFT', 'FFTBuffer', 'FFTBufferPlayer', 'FFTContinuousPhaseVocoder', 'FFTContrast', 'FFTConvolve', 'FFTCrossFade', 'FFTFindPeaks', 'FFTLFO', 'FFTLPF', 'FFTMagnitudePhaseArray', 'FFTNode', 'FFTNoiseGate', 'FFTOpNode', 'FFTPhaseVocoder', 'FFTRandomPhase', 'FFTScaleMagnitudes', 'FFTTonality', 'FFTTransform', 'FeedbackBufferReader', 'FeedbackBufferWriter', 'FlipFlop', 'Fold', 'FrequencyToMidiNote', 'Gate', 'Granulator', 'GraphAlreadyCreatedException', 'GraphNotCreatedException', 'GreaterThan', 'GreaterThanOrEqual', 'IFFT', 'If', 'Impulse', 'ImpulseSequence', 'Index', 'InsufficientBufferSizeException', 'InvalidChannelCountException', 'KDTree', 'KDTreeMatch', 'LFO', 'Latch', 'LessThan', 'LessThanOrEqual', 'Line', 'Logistic', 'Maximiser', 'MidiNoteToFrequency', 'Modulo', 'MoogVCF', 'MouseDown', 'MouseX', 'MouseY', 'Multiply', 'NearestNeighbour', 'Node', 'NodeAlreadyPlayingException', 'NodeNotPlayingException', 'NodeRegistry', 'NotEqual', 'OneTapDelay', 'OnsetDetector', 'Patch', 'PatchFinishedPlaybackException', 'PatchRegistry', 'PatchSpec', 'PinkNoise', 'Pow', 'RMS', 'RandomBrownian', 'RandomChoice', 'RandomCoin', 'RandomExponential', 'RandomExponentialDist', 'RandomGaussian', 'RandomImpulse', 'RandomImpulseSequence', 'RandomUniform', 'RectangularEnvelope', 'Resample', 'Round', 'RoundToScale', 'SIGNALFLOW_DEFAULT_BLOCK_SIZE', 'SIGNALFLOW_DEFAULT_FFT_HOP_SIZE', 'SIGNALFLOW_DEFAULT_FFT_SIZE', 'SIGNALFLOW_DEFAULT_SAMPLE_RATE', 'SIGNALFLOW_DEFAULT_TRIGGER', 'SIGNALFLOW_EVENT_DISTRIBUTION_POISSON', 'SIGNALFLOW_EVENT_DISTRIBUTION_UNIFORM', 'SIGNALFLOW_FILTER_TYPE_BAND_PASS', 'SIGNALFLOW_FILTER_TYPE_HIGH_PASS', 'SIGNALFLOW_FILTER_TYPE_HIGH_SHELF', 'SIGNALFLOW_FILTER_TYPE_LOW_PASS', 'SIGNALFLOW_FILTER_TYPE_LOW_SHELF', 'SIGNALFLOW_FILTER_TYPE_NOTCH', 'SIGNALFLOW_FILTER_TYPE_PEAK', 'SIGNALFLOW_INTERPOLATION_MODE_COSINE', 'SIGNALFLOW_INTERPOLATION_MODE_LINEAR', 'SIGNALFLOW_INTERPOLATION_MODE_NONE', 'SIGNALFLOW_MAX_CHANNELS', 'SIGNALFLOW_MAX_FFT_SIZE', 'SIGNALFLOW_NODE_BUFFER_SIZE', 'SIGNALFLOW_NODE_INITIAL_OUTPUT_CHANNELS', 'SIGNALFLOW_NODE_STATE_ACTIVE', 'SIGNALFLOW_NODE_STATE_STOPPED', 'SIGNALFLOW_PATCH_STATE_ACTIVE', 'SIGNALFLOW_PATCH_STATE_STOPPED', 'SVFilter', 'SampleAndHold', 'SawLFO', 'SawOscillator', 'ScaleLinExp', 'ScaleLinLin', 'SegmentPlayer', 'SegmentedGranulator', 'Sequence', 'Sin', 'SineLFO', 'SineOscillator', 'Smooth', 'SpatialEnvironment', 'SpatialPanner', 'SpatialSpeaker', 'SquareLFO', 'SquareOscillator', 'Squiz', 'StereoBalance', 'StereoPanner', 'StereoWidth', 'StochasticNode', 'Stutter', 'Subtract', 'Sum', 'Tan', 'Tanh', 'TimeShift', 'TriangleLFO', 'TriangleOscillator', 'TriggerMult', 'UnknownTriggerNameException', 'VampAnalysis', 'WaveShaper', 'WaveShaperBuffer', 'Wavetable', 'Wavetable2D', 'WavetableBuffer', 'WetDry', 'WhiteNoise', 'Wrap', 'amplitude_to_db', 'clip', 'db_to_amplitude', 'fold', 'frequency_to_midi_note', 'midi_note_to_frequency', 'random_exponential', 'random_integer', 'random_seed', 'random_uniform', 'save_block_to_text_file', 'save_block_to_wav_file', 'scale_exp_lin', 'scale_lin_exp', 'scale_lin_lin', 'signalflow_event_distribution_t', 'signalflow_filter_type_t', 'signalflow_interpolation_mode_t', 'signalflow_node_state_t', 'signalflow_patch_state_t', 'wrap'] +__all__ = ['ADSREnvelope', 'ASREnvelope', 'Abs', 'Add', 'AllpassDelay', 'AmplitudeToDecibels', 'AudioGraph', 'AudioGraphConfig', 'AudioIOException', 'AudioIn', 'AudioOut', 'AudioOut_Abstract', 'AudioOut_Dummy', 'AzimuthPanner', 'BeatCutter', 'BiquadFilter', 'Buffer', 'Buffer2D', 'BufferLooper', 'BufferPlayer', 'BufferRecorder', 'CPUUsageAboveLimitException', 'ChannelArray', 'ChannelCrossfade', 'ChannelMixer', 'ChannelPanner', 'ChannelSelect', 'Clip', 'ClockDivider', 'CombDelay', 'Compressor', 'Constant', 'Cos', 'Counter', 'CrossCorrelate', 'DCFilter', 'DecibelsToAmplitude', 'DetectSilence', 'DeviceNotFoundException', 'Divide', 'EQ', 'Envelope', 'EnvelopeBuffer', 'Equal', 'Euclidean', 'FFT', 'FFTBuffer', 'FFTBufferPlayer', 'FFTContinuousPhaseVocoder', 'FFTContrast', 'FFTConvolve', 'FFTCrossFade', 'FFTFindPeaks', 'FFTLFO', 'FFTLPF', 'FFTMagnitudePhaseArray', 'FFTNode', 'FFTNoiseGate', 'FFTOpNode', 'FFTPhaseVocoder', 'FFTRandomPhase', 'FFTScaleMagnitudes', 'FFTTonality', 'FFTTransform', 'FeedbackBufferReader', 'FeedbackBufferWriter', 'FlipFlop', 'Fold', 'FrequencyToMidiNote', 'Gate', 'Granulator', 'GraphAlreadyCreatedException', 'GraphNotCreatedException', 'GreaterThan', 'GreaterThanOrEqual', 'IFFT', 'If', 'Impulse', 'ImpulseSequence', 'Index', 'InsufficientBufferSizeException', 'InvalidChannelCountException', 'KDTree', 'KDTreeMatch', 'LFO', 'Latch', 'LessThan', 'LessThanOrEqual', 'Line', 'Logistic', 'Maximiser', 'MidiNoteToFrequency', 'Modulo', 'MoogVCF', 'MouseDown', 'MouseX', 'MouseY', 'Multiply', 'NearestNeighbour', 'Node', 'NodeAlreadyPlayingException', 'NodeNotPlayingException', 'NodeRegistry', 'NotEqual', 'OneTapDelay', 'OnsetDetector', 'Patch', 'PatchFinishedPlaybackException', 'PatchRegistry', 'PatchSpec', 'PinkNoise', 'Pow', 'RMS', 'RandomBrownian', 'RandomChoice', 'RandomCoin', 'RandomExponential', 'RandomExponentialDist', 'RandomGaussian', 'RandomImpulse', 'RandomImpulseSequence', 'RandomUniform', 'RectangularEnvelope', 'Resample', 'Round', 'RoundToScale', 'SIGNALFLOW_DEFAULT_BLOCK_SIZE', 'SIGNALFLOW_DEFAULT_FFT_HOP_SIZE', 'SIGNALFLOW_DEFAULT_FFT_SIZE', 'SIGNALFLOW_DEFAULT_SAMPLE_RATE', 'SIGNALFLOW_DEFAULT_TRIGGER', 'SIGNALFLOW_EVENT_DISTRIBUTION_POISSON', 'SIGNALFLOW_EVENT_DISTRIBUTION_UNIFORM', 'SIGNALFLOW_FILTER_TYPE_BAND_PASS', 'SIGNALFLOW_FILTER_TYPE_HIGH_PASS', 'SIGNALFLOW_FILTER_TYPE_HIGH_SHELF', 'SIGNALFLOW_FILTER_TYPE_LOW_PASS', 'SIGNALFLOW_FILTER_TYPE_LOW_SHELF', 'SIGNALFLOW_FILTER_TYPE_NOTCH', 'SIGNALFLOW_FILTER_TYPE_PEAK', 'SIGNALFLOW_INTERPOLATION_MODE_COSINE', 'SIGNALFLOW_INTERPOLATION_MODE_LINEAR', 'SIGNALFLOW_INTERPOLATION_MODE_NONE', 'SIGNALFLOW_MAX_CHANNELS', 'SIGNALFLOW_MAX_FFT_SIZE', 'SIGNALFLOW_NODE_BUFFER_SIZE', 'SIGNALFLOW_NODE_INITIAL_OUTPUT_CHANNELS', 'SIGNALFLOW_NODE_STATE_ACTIVE', 'SIGNALFLOW_NODE_STATE_STOPPED', 'SIGNALFLOW_PATCH_STATE_ACTIVE', 'SIGNALFLOW_PATCH_STATE_STOPPED', 'SVFilter', 'SampleAndHold', 'SawLFO', 'SawOscillator', 'ScaleLinExp', 'ScaleLinLin', 'SegmentPlayer', 'SegmentedGranulator', 'Sequence', 'Sin', 'SineLFO', 'SineOscillator', 'Smooth', 'SpatialEnvironment', 'SpatialPanner', 'SpatialSpeaker', 'SquareLFO', 'SquareOscillator', 'Squiz', 'StereoBalance', 'StereoPanner', 'StereoWidth', 'StochasticNode', 'Stutter', 'Subtract', 'Sum', 'Tan', 'Tanh', 'TimeShift', 'TriangleLFO', 'TriangleOscillator', 'TriggerMult', 'TriggerRoundRobin', 'UnknownTriggerNameException', 'VampAnalysis', 'WaveShaper', 'WaveShaperBuffer', 'Wavetable', 'Wavetable2D', 'WavetableBuffer', 'WetDry', 'WhiteNoise', 'Wrap', 'amplitude_to_db', 'clip', 'db_to_amplitude', 'fold', 'frequency_to_midi_note', 'midi_note_to_frequency', 'random_exponential', 'random_integer', 'random_seed', 'random_uniform', 'save_block_to_text_file', 'save_block_to_wav_file', 'scale_exp_lin', 'scale_lin_exp', 'scale_lin_lin', 'signalflow_event_distribution_t', 'signalflow_filter_type_t', 'signalflow_interpolation_mode_t', 'signalflow_node_state_t', 'signalflow_patch_state_t', 'wrap'] class ADSREnvelope(Node): """ Attack-decay-sustain-release envelope. Sustain portion is held until gate is zero. @@ -1780,6 +1780,12 @@ class TriggerMult(Node): """ def __init__(self, a: Node = 0) -> None: ... +class TriggerRoundRobin(Node): + """ + Relay trigger() events to a single node from the list of connected outputs, with `direction` determining the direction: 1 (or above) = move forwards by N, -1 = move backwards by N, 0 = stationary. + """ + def __init__(self, direction: Node = 1) -> None: + ... class UnknownTriggerNameException(Exception): pass class VampAnalysis(Node): diff --git a/source/include/signalflow/buffer/wavetable-buffer.h b/source/include/signalflow/buffer/wavetable-buffer.h index 0e247dea..d6f81e91 100644 --- a/source/include/signalflow/buffer/wavetable-buffer.h +++ b/source/include/signalflow/buffer/wavetable-buffer.h @@ -18,6 +18,12 @@ namespace signalflow * A WavetableBuffer is a mono buffer with a fixed number of samples, * which can be sampled at a position [0,1] to give a bipolar [-1, 1] * amplitude value, intended for use with the Wavetable node. + * + * At some point, it would be nice to support multi-sampled wavetables + * band-limited per octave. + * + * Some useful notes: + * https://www.earlevel.com/main/category/digital-audio/oscillators/wavetable-oscillators/ *-----------------------------------------------------------------------*/ class WavetableBuffer : public Buffer { diff --git a/source/include/signalflow/node/node.h b/source/include/signalflow/node/node.h index 653979f0..8e4d990e 100644 --- a/source/include/signalflow/node/node.h +++ b/source/include/signalflow/node/node.h @@ -245,7 +245,7 @@ class Node virtual void set_value(float value); std::map get_inputs(); - std::set> get_outputs(); + std::vector> get_outputs(); std::map get_properties(); std::map get_buffers(); @@ -347,7 +347,7 @@ class Node * Note that a node may modulate two different parameters of the same * node. *-----------------------------------------------------------------------*/ - std::set> outputs; + std::vector> outputs; /*------------------------------------------------------------------------ * Hash table of properties: (name, PropertyRef *) diff --git a/source/include/signalflow/node/operators/trigger-mult.h b/source/include/signalflow/node/sequencing/trigger-mult.h similarity index 100% rename from source/include/signalflow/node/operators/trigger-mult.h rename to source/include/signalflow/node/sequencing/trigger-mult.h diff --git a/source/include/signalflow/node/sequencing/trigger-round-robin.h b/source/include/signalflow/node/sequencing/trigger-round-robin.h new file mode 100644 index 00000000..aaf432e6 --- /dev/null +++ b/source/include/signalflow/node/sequencing/trigger-round-robin.h @@ -0,0 +1,31 @@ +#pragma once + +#include "signalflow/core/constants.h" +#include "signalflow/node/node.h" + +namespace signalflow +{ + +/**--------------------------------------------------------------------------------* + * Relay trigger() events to a single node from the list of connected outputs, + * with `direction` determining the direction: 1 (or above) = move forwards by N, + * -1 = move backwards by N, 0 = stationary. + *---------------------------------------------------------------------------------*/ +class TriggerRoundRobin : public Node +{ + +public: + TriggerRoundRobin(NodeRef direction = 1); + + virtual void process(Buffer &out, int num_frames) override; + virtual void trigger(std::string = SIGNALFLOW_DEFAULT_TRIGGER, + float value = SIGNALFLOW_NULL_FLOAT) override; + +private: + unsigned int current_output_index = 0; + NodeRef direction; +}; + +REGISTER(TriggerRoundRobin, "trigger-round-robin") + +} diff --git a/source/include/signalflow/signalflow.h b/source/include/signalflow/signalflow.h index 42ccd208..a734abb1 100644 --- a/source/include/signalflow/signalflow.h +++ b/source/include/signalflow/signalflow.h @@ -57,7 +57,6 @@ #include #include #include -#include #include /*------------------------------------------------------------------------ @@ -168,6 +167,8 @@ #include #include #include +#include +#include /*------------------------------------------------------------------------ * Analysis and MIR diff --git a/source/src/CMakeLists.txt b/source/src/CMakeLists.txt index 7c076062..61de45fd 100644 --- a/source/src/CMakeLists.txt +++ b/source/src/CMakeLists.txt @@ -103,7 +103,6 @@ set(SRC ${SRC} ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/subtract.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/sum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/trigonometry.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/trigger-mult.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/comparison.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/time-shift.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/oscillators/constant.cpp @@ -136,6 +135,8 @@ set(SRC ${SRC} ${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/sequence.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/impulse-sequence.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/euclidean.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/trigger-mult.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/trigger-round-robin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/analysis/vamp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/analysis/cross-correlate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/analysis/nearest-neighbour.cpp diff --git a/source/src/node/node.cpp b/source/src/node/node.cpp index 4de342a2..03f79d70 100644 --- a/source/src/node/node.cpp +++ b/source/src/node/node.cpp @@ -1,3 +1,5 @@ +#include + #include "signalflow/node/node.h" #include "signalflow/node/operators/add.h" @@ -308,7 +310,7 @@ std::map Node::get_inputs() return this->inputs; } -std::set> Node::get_outputs() +std::vector> Node::get_outputs() { return this->outputs; } @@ -413,12 +415,21 @@ void Node::remove_input(NodeRef input) void Node::add_output(Node *target, std::string name) { - this->outputs.insert(std::make_pair(target, name)); + this->outputs.push_back(std::make_pair(target, name)); } void Node::remove_output(Node *target, std::string name) { - this->outputs.erase(std::make_pair(target, name)); + std::vector>::iterator iter; + + for (iter = this->outputs.begin(); iter != this->outputs.end();) + { + if (iter->second == name) + { + this->outputs.erase(iter); + break; + } + } } bool Node::get_matches_input_channels() diff --git a/source/src/node/operators/trigger-mult.cpp b/source/src/node/sequencing/trigger-mult.cpp similarity index 78% rename from source/src/node/operators/trigger-mult.cpp rename to source/src/node/sequencing/trigger-mult.cpp index fa5a1bfa..8c1318c8 100644 --- a/source/src/node/operators/trigger-mult.cpp +++ b/source/src/node/sequencing/trigger-mult.cpp @@ -1,4 +1,4 @@ -#include "signalflow/node/operators/trigger-mult.h" +#include "signalflow/node/sequencing/trigger-mult.h" #include namespace signalflow @@ -16,8 +16,7 @@ void TriggerMult::process(Buffer &out, int num_frames) { for (int frame = 0; frame < num_frames; frame++) { - float value = this->input->out[channel][frame]; - out[channel][frame] = value; + out[channel][frame] = this->input->out[channel][frame]; } } } diff --git a/source/src/node/sequencing/trigger-round-robin.cpp b/source/src/node/sequencing/trigger-round-robin.cpp new file mode 100644 index 00000000..74e94ac0 --- /dev/null +++ b/source/src/node/sequencing/trigger-round-robin.cpp @@ -0,0 +1,40 @@ +#include "signalflow/node/sequencing/trigger-round-robin.h" +#include + +namespace signalflow +{ + +TriggerRoundRobin::TriggerRoundRobin(NodeRef direction) + : Node(), direction(direction) +{ + this->name = "trigger-round-robin"; + this->create_input("direction", this->direction); +} + +void TriggerRoundRobin::process(Buffer &out, int num_frames) +{ + for (int channel = 0; channel < this->num_output_channels; channel++) + { + for (int frame = 0; frame < num_frames; frame++) + { + out[channel][frame] = 0.0; + } + } +} + +void TriggerRoundRobin::trigger(std::string name, float value) +{ + for (size_t index = 0; index < this->outputs.size(); index++) + { + if (index == current_output_index) + { + int direction = this->direction->out[0][0]; + auto output_node = this->outputs[index].first; + output_node->trigger(name, value); + this->current_output_index = (this->current_output_index + direction) % outputs.size(); + break; + } + } +} + +} diff --git a/source/src/python/nodes.cpp b/source/src/python/nodes.cpp index fce67202..92315786 100644 --- a/source/src/python/nodes.cpp +++ b/source/src/python/nodes.cpp @@ -247,9 +247,6 @@ void init_python_nodes(py::module &m) py::class_>(m, "TimeShift", "TimeShift") .def(py::init(), "a"_a = 0); - py::class_>(m, "TriggerMult", "Distribute any triggers to all output nodes.") - .def(py::init(), "a"_a = 0); - py::class_>(m, "Sin", "Outputs sin(a), per sample.") .def(py::init(), "a"_a = 0); @@ -412,6 +409,12 @@ void init_python_nodes(py::module &m) py::class_>(m, "Sequence", "Outputs the elements in `sequence`, incrementing position on each `clock`.") .def(py::init, NodeRef>(), "sequence"_a = std::vector(), "clock"_a = nullptr); + py::class_>(m, "TriggerMult", "Distribute any triggers to all output nodes.") + .def(py::init(), "a"_a = 0); + + py::class_>(m, "TriggerRoundRobin", "Relay trigger() events to a single node from the list of connected outputs, with `direction` determining the direction: 1 (or above) = move forwards by N, -1 = move backwards by N, 0 = stationary.") + .def(py::init(), "direction"_a = 1); + py::class_>(m, "Logistic", "Logistic noise.") .def(py::init(), "chaos"_a = 3.7, "frequency"_a = 0.0);