diff --git a/CMakeLists.txt b/CMakeLists.txt index d5c89a76..0ff23a74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/auxiliary/libs/signalflow-stubs/signalflow.pyi b/auxiliary/libs/signalflow-stubs/signalflow.pyi index 7cc87435..e3566bb9 100644 --- a/auxiliary/libs/signalflow-stubs/signalflow.pyi +++ b/auxiliary/libs/signalflow-stubs/signalflow.pyi @@ -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. @@ -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]: @@ -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 @@ -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: @@ -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): """ @@ -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`. diff --git a/auxiliary/scripts/auto-generator.py b/auxiliary/scripts/auto-generator.py index 377b8143..785f72c8 100755 --- a/auxiliary/scripts/auto-generator.py +++ b/auxiliary/scripts/auto-generator.py @@ -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"] diff --git a/source/include/signalflow/node/analysis/nearest-neighbour.h b/source/include/signalflow/node/analysis/nearest-neighbour.h new file mode 100644 index 00000000..52e28266 --- /dev/null +++ b/source/include/signalflow/node/analysis/nearest-neighbour.h @@ -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") +} diff --git a/source/include/signalflow/signalflow.h b/source/include/signalflow/signalflow.h index 353577f5..7a9afe5a 100644 --- a/source/include/signalflow/signalflow.h +++ b/source/include/signalflow/signalflow.h @@ -158,6 +158,7 @@ * Analysis and MIR *-----------------------------------------------------------------------*/ #include +#include #include #include diff --git a/source/src/CMakeLists.txt b/source/src/CMakeLists.txt index c321c4bc..db6ccb3c 100644 --- a/source/src/CMakeLists.txt +++ b/source/src/CMakeLists.txt @@ -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 diff --git a/source/src/node/analysis/nearest-neighbour.cpp b/source/src/node/analysis/nearest-neighbour.cpp new file mode 100644 index 00000000..3b37ab05 --- /dev/null +++ b/source/src/node/analysis/nearest-neighbour.cpp @@ -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> 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({ 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 target_value_vector({ target_value }); + std::vector 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]; + } + } +} + +} diff --git a/source/src/python/nodes.cpp b/source/src/python/nodes.cpp index 778a570b..b0710c1a 100644 --- a/source/src/python/nodes.cpp +++ b/source/src/python/nodes.cpp @@ -19,9 +19,15 @@ void init_python_nodes(py::module &m) py::class_>(m, "CrossCorrelate", "Outputs the cross-correlation of the input signal with the given buffer. If hop_size is zero, calculates the cross-correlation every sample.") .def(py::init(), "input"_a = nullptr, "buffer"_a = nullptr, "hop_size"_a = 0); + py::class_>(m, "NearestNeighbour", "Nearest Neighbour.") + .def(py::init(), "buffer"_a = nullptr, "target"_a = 0.0); + py::class_>(m, "OnsetDetector", "Simple time-domain onset detector. Outputs an impulse when an onset is detected in the input. Maintains short-time and long-time averages. An onset is registered when the short-time average is threshold x the long-time average. min_interval is the minimum interval between onsets, in seconds.") .def(py::init(), "input"_a = 0.0, "threshold"_a = 2.0, "min_interval"_a = 0.1); + py::class_>(m, "VampAnalysis", "Feature extraction using the Vamp plugin toolkit.") + .def(py::init(), "input"_a = 0.0, "plugin_id"_a = "vamp-example-plugins:spectralcentroid:linearcentroid"); + py::class_>(m, "BeatCutter", "Cuts a buffer into segment_count segments, and stutters/jumps with the given probabilities.") .def(py::init(), "buffer"_a = nullptr, "segment_count"_a = 8, "stutter_probability"_a = 0.0, "stutter_count"_a = 1, "jump_probability"_a = 0.0, "duty_cycle"_a = 1.0, "rate"_a = 1.0, "segment_rate"_a = 1.0);