Skip to content

Commit

Permalink
Initial implementation of NearestNeighbour
Browse files Browse the repository at this point in the history
  • Loading branch information
ideoforms committed Jan 20, 2024
1 parent 6963fed commit 92a2745
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 32 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ endif()
# By default, build arm64 on Apple.
#-------------------------------------------------------------------------------
if (NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "arm64")
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
Expand Down
73 changes: 43 additions & 30 deletions auxiliary/libs/signalflow-stubs/signalflow.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,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', 'FFTContinuousPhaseVocoder', 'FFTConvolve', 'FFTFindPeaks', 'FFTLPF', 'FFTNoiseGate', 'FFTPhaseVocoder', 'FFTTonality', 'FeedbackBufferReader', 'FeedbackBufferWriter', 'FlipFlop', 'Fold', 'FrequencyToMidiNote', 'Gate', 'Granulator', 'GraphAlreadyCreatedException', 'GraphNotCreatedException', 'GreaterThan', 'GreaterThanOrEqual', 'IFFT', 'If', 'Impulse', 'ImpulseSequence', 'Index', 'InvalidChannelCountException', 'LFO', 'Latch', 'LessThan', 'LessThanOrEqual', 'Line', 'Logistic', 'Maximiser', 'MidiNoteToFrequency', 'Modulo', 'MoogVCF', 'MouseDown', 'MouseX', 'MouseY', 'Multiply', '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_STATE_ACTIVE', 'SIGNALFLOW_NODE_STATE_STOPPED', 'SIGNALFLOW_PATCH_STATE_ACTIVE', 'SIGNALFLOW_PATCH_STATE_STOPPED', 'SVFilter', 'SampleAndHold', 'SawLFO', 'SawOscillator', 'ScaleLinExp', 'ScaleLinLin', 'SegmentPlayer', 'Sequence', 'Sin', 'SineLFO', 'SineOscillator', 'Smooth', 'SpatialEnvironment', 'SpatialPanner', 'SpatialSpeaker', 'SquareLFO', 'SquareOscillator', 'Squiz', 'StereoBalance', 'StereoPanner', 'StereoWidth', 'StochasticNode', 'Stutter', 'Subtract', 'Sum', 'Tan', 'Tanh', 'TriangleLFO', 'TriangleOscillator', 'WaveShaper', 'WaveShaperBuffer', 'Wavetable', 'Wavetable2D', 'WetDry', 'WhiteNoise', 'Wrap', 'amplitude_to_db', 'clip', 'db_to_amplitude', 'fold', 'frequency_to_midi_note', 'midi_note_to_frequency', 'random_exponential', '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', 'FFTContinuousPhaseVocoder', 'FFTConvolve', 'FFTFindPeaks', 'FFTLPF', 'FFTNoiseGate', 'FFTPhaseVocoder', 'FFTTonality', 'FeedbackBufferReader', 'FeedbackBufferWriter', 'FlipFlop', 'Fold', 'FrequencyToMidiNote', 'Gate', 'Granulator', 'GraphAlreadyCreatedException', 'GraphNotCreatedException', 'GreaterThan', 'GreaterThanOrEqual', 'IFFT', 'If', 'Impulse', 'ImpulseSequence', 'Index', 'InvalidChannelCountException', 'KDTree', 'LFO', 'Latch', 'LessThan', 'LessThanOrEqual', 'Line', 'Logistic', 'Maximiser', 'MidiNoteToFrequency', 'Modulo', 'MoogVCF', 'MouseDown', 'MouseX', 'MouseY', 'Multiply', '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_STATE_ACTIVE', 'SIGNALFLOW_NODE_STATE_STOPPED', 'SIGNALFLOW_PATCH_STATE_ACTIVE', 'SIGNALFLOW_PATCH_STATE_STOPPED', 'SVFilter', 'SampleAndHold', 'SawLFO', 'SawOscillator', 'ScaleLinExp', 'ScaleLinLin', 'SegmentPlayer', 'Sequence', 'Sin', 'SineLFO', 'SineOscillator', 'Smooth', 'SpatialEnvironment', 'SpatialPanner', 'SpatialSpeaker', 'SquareLFO', 'SquareOscillator', 'Squiz', 'StereoBalance', 'StereoPanner', 'StereoWidth', 'StochasticNode', 'Stutter', 'Subtract', 'Sum', 'Tan', 'Tanh', 'TriangleLFO', 'TriangleOscillator', 'VampAnalysis', 'WaveShaper', 'WaveShaperBuffer', 'Wavetable', 'Wavetable2D', 'WetDry', 'WhiteNoise', 'Wrap', 'amplitude_to_db', 'clip', 'db_to_amplitude', 'fold', 'frequency_to_midi_note', 'midi_note_to_frequency', 'random_exponential', '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.
Expand Down Expand Up @@ -296,100 +295,100 @@ class Buffer:
"""
A buffer of audio samples, containing one or more channels.
"""
def __add__(self, value: float) -> typing_extensions.Buffer:
def __add__(self: typing_extensions.Buffer, value: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` added to `value`.
"""
def __div__(self, value: float) -> typing_extensions.Buffer:
def __div__(self: typing_extensions.Buffer, value: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` divided by `value`.
"""
def __getitem__(self, arg0: int) -> typing_extensions.Buffer:
def __getitem__(self: typing_extensions.Buffer, arg0: int) -> typing_extensions.Buffer:
...
@typing.overload
def __init__(self) -> None:
def __init__(self: typing_extensions.Buffer) -> None:
"""
Create a null Buffer with no memory allocated.
"""
@typing.overload
def __init__(self, filename: str) -> None:
def __init__(self: typing_extensions.Buffer, filename: str) -> None:
"""
Load a Buffer from an audio file.
"""
@typing.overload
def __init__(self, num_channels: int, num_frames: int) -> None:
def __init__(self: typing_extensions.Buffer, num_channels: int, num_frames: int) -> None:
"""
Allocate a buffer with `num_channels` channels and `num_frames` frames.
"""
@typing.overload
def __init__(self, num_channels: int, num_frames: int, data: list[list[float]]) -> None:
def __init__(self: typing_extensions.Buffer, num_channels: int, num_frames: int, data: list[list[float]]) -> None:
"""
Allocate a buffer with `num_channels` channels and `num_frames` frames, containing the floating-point samples in `data`.
"""
@typing.overload
def __init__(self, arg0: list[list[float]]) -> None:
def __init__(self: typing_extensions.Buffer, arg0: list[list[float]]) -> None:
"""
Allocate a buffer with `num_channels` channels and `num_frames` frames, containing the floating-point samples in `data`.
"""
@typing.overload
def __init__(self, data: list[float]) -> None:
def __init__(self: typing_extensions.Buffer, data: list[float]) -> None:
"""
Allocate a buffer containing the floating-point samples in `data`.
"""
@typing.overload
def __init__(self, function: typing.Callable[[float], float]) -> None:
def __init__(self: typing_extensions.Buffer, function: typing.Callable[[float], float]) -> None:
"""
Allocate a buffer filled with the output of the function `function`.
"""
@typing.overload
def __init__(self, num_frames: int, function: typing.Callable[[float], float]) -> None:
def __init__(self: typing_extensions.Buffer, num_frames: int, function: typing.Callable[[float], float]) -> None:
"""
Allocate a mono buffer with `num_frames` frames, filled with the output of the function `function`.
"""
@typing.overload
def __init__(self, num_channels: int, num_frames: int, function: typing.Callable[[float], float]) -> None:
def __init__(self: typing_extensions.Buffer, num_channels: int, num_frames: int, function: typing.Callable[[float], float]) -> None:
"""
Allocate a buffer with `num_channels` channels and `num_frames` frames, filled with the output of the function `function`.
"""
def __len__(self) -> int:
def __len__(self: typing_extensions.Buffer) -> int:
"""
Returns the length of the buffer `self`, in frames.
"""
def __mul__(self, value: float) -> typing_extensions.Buffer:
def __mul__(self: typing_extensions.Buffer, value: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` multiplied by `value`.
"""
def __radd__(self, value_: float) -> typing_extensions.Buffer:
def __radd__(self: typing_extensions.Buffer, value_: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` added to `value`.
"""
def __rmul__(self, value_: float) -> typing_extensions.Buffer:
def __rmul__(self: typing_extensions.Buffer, value_: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` multiplied by `value`.
"""
def __str__(self) -> str:
def __str__(self: typing_extensions.Buffer) -> str:
...
def __sub__(self, value: float) -> typing_extensions.Buffer:
def __sub__(self: typing_extensions.Buffer, value: float) -> typing_extensions.Buffer:
"""
Returns a new Buffer containing the samples in `self` subtracted by `value`.
"""
@typing.overload
def fill(self, sample: float) -> None:
def fill(self: typing_extensions.Buffer, sample: float) -> None:
...
@typing.overload
def fill(self, function: typing.Callable[[float], float]) -> None:
def fill(self: typing_extensions.Buffer, function: typing.Callable[[float], float]) -> None:
...
def get(self, channel: int, frame: float) -> float:
def get(self: typing_extensions.Buffer, channel: int, frame: float) -> float:
...
def get_frame(self, channel: int, frame: float) -> float:
def get_frame(self: typing_extensions.Buffer, channel: int, frame: float) -> float:
...
def load(self, filename: str) -> None:
def load(self: typing_extensions.Buffer, filename: str) -> None:
...
def save(self, filename: str) -> None:
def save(self: typing_extensions.Buffer, filename: str) -> None:
...
def set(self, channel: int, frame: int, value: float) -> bool:
def set(self: typing_extensions.Buffer, channel: int, frame: int, value: float) -> bool:
...
def split(self, num_frames_per_part: int) -> list[typing_extensions.Buffer]:
def split(self: typing_extensions.Buffer, num_frames_per_part: int) -> list[typing_extensions.Buffer]:
...
@property
def data(self) -> numpy.ndarray[numpy.float32]:
Expand Down Expand Up @@ -772,6 +771,14 @@ class Index(Node):
...
class InvalidChannelCountException(Exception):
pass
class KDTree:
"""
A KDTree structure
"""
def __init__(self, data: list[list[float]] = None) -> None:
...
def get_nearest(self, target: list[float]) -> list[float]:
...
class LFO(Node):
"""
LFO
Expand Down Expand Up @@ -1060,7 +1067,7 @@ class Node:
"""
def set_buffer(self, string: str, buffer: ...) -> None:
"""
The length of the node's output buffer, in frames
Set the value of a node's buffer input
"""
@typing.overload
def set_input(self, name: str, value: float) -> None:
Expand Down Expand Up @@ -1490,7 +1497,7 @@ class Sequence(Node):
"""
Outputs the elements in `sequence`, incrementing position on each `clock`.
"""
def __init__(self, sequence: list[float] = [], clock: Node = None) -> None:
def __init__(self: typing.Sequence, sequence: list[float] = [], clock: Node = None) -> None:
...
class Sin(Node):
"""
Expand Down Expand Up @@ -1627,6 +1634,12 @@ class TriangleOscillator(Node):
"""
def __init__(self, frequency: Node = 440) -> None:
...
class VampAnalysis(Node):
"""
Feature extraction using the Vamp plugin toolkit.
"""
def __init__(self, input: Node = 0.0, plugin_id: str = 'vamp-example-plugins:spectralcentroid:linearcentroid') -> None:
...
class WaveShaper(Node):
"""
Applies wave-shaping as described in the WaveShaperBuffer `buffer`.
Expand Down
2 changes: 1 addition & 1 deletion auxiliary/scripts/auto-generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def identifier(self):


node_superclasses = ["Node", "UnaryOpNode", "BinaryOpNode", "StochasticNode", "FFTNode", "FFTOpNode", "LFO"]
omitted_classes = ["VampAnalysis", "GrainSegments", "FFTZeroPhase", "FFTOpNode", "FFTNode",
omitted_classes = ["GrainSegments", "FFTZeroPhase", "FFTOpNode", "FFTNode",
"StochasticNode"]
macos_only_classes = ["MouseX", "MouseY", "MouseDown", "FFTConvolve"]
known_parent_classes = ["Node", "StochasticNode"]
Expand Down
24 changes: 24 additions & 0 deletions source/include/signalflow/node/analysis/nearest-neighbour.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "signalflow/core/kdtree.h"
#include "signalflow/node/node.h"

namespace signalflow
{
/**--------------------------------------------------------------------------------*
* Nearest Neighbour.
*---------------------------------------------------------------------------------*/
class NearestNeighbour : public Node
{
public:
NearestNeighbour(BufferRef buffer = nullptr, NodeRef target = 0.0);

virtual void set_buffer(std::string name, BufferRef buffer);
virtual void process(Buffer &out, int num_frames);

private:
BufferRef buffer;
NodeRef target;
KDTree *kdtree;
};

REGISTER(NearestNeighbour, "nearest-neighbour")
}
1 change: 1 addition & 0 deletions source/include/signalflow/signalflow.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
* Analysis and MIR
*-----------------------------------------------------------------------*/
#include <signalflow/node/analysis/cross-correlate.h>
#include <signalflow/node/analysis/nearest-neighbour.h>
#include <signalflow/node/analysis/onset-detector.h>
#include <signalflow/node/analysis/vamp.h>

Expand Down
1 change: 1 addition & 0 deletions source/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ set(SRC ${SRC}
${CMAKE_CURRENT_SOURCE_DIR}/node/sequencing/euclidean.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
${CMAKE_CURRENT_SOURCE_DIR}/node/analysis/onset-detector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/patch/patch-node-spec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/patch/patch.cpp
Expand Down
73 changes: 73 additions & 0 deletions source/src/node/analysis/nearest-neighbour.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "signalflow/core/graph.h"
#include "signalflow/node/analysis/nearest-neighbour.h"

namespace signalflow
{

NearestNeighbour::NearestNeighbour(BufferRef buffer, NodeRef target)
: target(target)
{
SIGNALFLOW_CHECK_GRAPH();

this->name = "nearest-neighbour";
this->kdtree = nullptr;

this->create_buffer("buffer", this->buffer);
this->create_input("target", this->target);

if (buffer)
{
/*--------------------------------------------------------------------------------
* Note that buffer can initially be null (e.g, when a node is deserialised).
*--------------------------------------------------------------------------------*/
this->set_buffer("buffer", buffer);
}
}

void NearestNeighbour::set_buffer(std::string name, BufferRef buffer)
{
if (name == "buffer")
{
this->Node::set_buffer(name, buffer);

/*--------------------------------------------------------------------------------
* Initialise K-D tree data structure.
* If the tree had previously been assigned, free it first.
*--------------------------------------------------------------------------------*/
if (this->kdtree)
{
delete this->kdtree;
}
std::vector<std::vector<float>> data;
for (auto i = 0; i < buffer->get_num_frames(); i++)
{
// must be a more efficient way to express this
data.push_back(std::vector<float>({ this->buffer->data[0][i] }));
}
this->kdtree = new KDTree(data);

// TODO: set num output channels to # channels in buffer
}
}

void NearestNeighbour::process(Buffer &out, int num_frames)
{
/*--------------------------------------------------------------------------------
* If buffer is null or empty, don't try to process.
*--------------------------------------------------------------------------------*/
if (!this->buffer || !this->buffer->get_num_frames())
return;

float target_value = this->target->out[0][0];
std::vector<float> target_value_vector({ target_value });
std::vector<float> output_value_vector = this->kdtree->get_nearest(target_value_vector);
for (auto channel = 0; channel < this->get_num_output_channels(); channel++)
{
for (auto frame = 0; frame < num_frames; frame++)
{
this->out[channel][frame] = output_value_vector[channel];
}
}
}

}
Loading

0 comments on commit 92a2745

Please sign in to comment.