Skip to content

Commit

Permalink
Add FFTBuffer class
Browse files Browse the repository at this point in the history
  • Loading branch information
ideoforms committed Aug 5, 2024
1 parent 7b05060 commit ac7982c
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 4 deletions.
148 changes: 148 additions & 0 deletions source/include/signalflow/buffer/fftbuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#pragma once

/**-------------------------------------------------------------------------
* @file fftbuffer.h
* @brief Stores one or more spectra of FFT mag/phase pairs.
*
*-----------------------------------------------------------------------*/

#include "signalflow/core/constants.h"
#include "signalflow/core/util.h"

#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

namespace signalflow
{

template <class T>
class FFTBufferRefTemplate;
class FFTBuffer;
typedef FFTBufferRefTemplate<FFTBuffer> FFTBufferRef;

class FFTBuffer
{
public:
/**------------------------------------------------------------------------
* Construct an FFT buffer of specified dimensions.
* The contents are initialised to zero.
*
*------------------------------------------------------------------------*/
FFTBuffer(int num_frames, int fft_size, int hop_size);

/**------------------------------------------------------------------------
* Load the contents of the spectra file `filename` into a new buffer.
*
*------------------------------------------------------------------------*/
FFTBuffer(std::string filename, int fft_size, int hop_size);

/**------------------------------------------------------------------------
* Destroy the buffer.
*
*------------------------------------------------------------------------*/
virtual ~FFTBuffer();

/**------------------------------------------------------------------------
* Resize the FFT buffer.
*
* @param num_frames The number of FFT frames to allocate.
*
*------------------------------------------------------------------------*/
void resize(int num_frames);

/**------------------------------------------------------------------------
* Get the magnitude array corresponding to the given frame.
*
* @param frame The frame index, between [0, num_frames].
* @returns A sample value, between [-1, 1].
*
*------------------------------------------------------------------------*/
sample *get_magnitudes(int frame);
sample *get_phases(int frame);

/**------------------------------------------------------------------------
* Get the buffer's audio sample rate.
*
* @returns The sample rate, in Hz.
*
*------------------------------------------------------------------------*/
float get_sample_rate();

/**------------------------------------------------------------------------
* Set the buffer's audio sample rate.
*
* @param sample_rate The sample rate
*
*------------------------------------------------------------------------*/
void set_sample_rate(float sample_rate);

/**------------------------------------------------------------------------
* Get the number of spectrum frames in the buffer.
*
* @returns The number of frames.
*
*------------------------------------------------------------------------*/
unsigned long get_num_frames();

/**------------------------------------------------------------------------
* Get the duration of the buffer, based on the hop_size.
*
* @returns The duration, in seconds.
*
*------------------------------------------------------------------------*/
float get_duration();

/**------------------------------------------------------------------------
* Get the buffer's FFT size.
*
* @returns The FFT size.
*
*------------------------------------------------------------------------*/
unsigned int get_fft_size();

/**------------------------------------------------------------------------
* Get the buffer's hop size.
*
* @returns The hop size.
*
*------------------------------------------------------------------------*/
unsigned int get_hop_size();

/**------------------------------------------------------------------------
* Get the filename that the buffer was loaded from / saved to, if set.
*
* @returns The filename, or an empty string.
*
*------------------------------------------------------------------------*/
std::string get_filename();

sample **data = nullptr;

protected:
unsigned long get_total_num_values();

std::string filename;
float sample_rate;
unsigned int num_frames;
unsigned int fft_size;
unsigned int num_bins;
unsigned int hop_size;
float duration;
};

template <class T>
class FFTBufferRefTemplate : public std::shared_ptr<T>
{
public:
using std::shared_ptr<T>::shared_ptr;

FFTBufferRefTemplate()
: std::shared_ptr<T>(nullptr) {}
FFTBufferRefTemplate(T *ptr)
: std::shared_ptr<T>(ptr) {}
};

}
1 change: 1 addition & 0 deletions source/include/signalflow/core/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ class AudioGraph
int recording_num_channels;

friend class Buffer;
friend class FFTBuffer;
};

class AudioGraphRef : public std::shared_ptr<AudioGraph>
Expand Down
1 change: 1 addition & 0 deletions source/include/signalflow/python/python.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using namespace pybind11::literals;
*----------------------------------------------------------------------------*/
PYBIND11_DECLARE_HOLDER_TYPE(T, NodeRefTemplate<T>, false)
PYBIND11_DECLARE_HOLDER_TYPE(T, BufferRefTemplate<T>, false)
PYBIND11_DECLARE_HOLDER_TYPE(T, FFTBufferRefTemplate<T>, false)
PYBIND11_DECLARE_HOLDER_TYPE(T, PatchRefTemplate<T>, false)
PYBIND11_DECLARE_HOLDER_TYPE(T, PatchSpecRefTemplate<T>, false)
PYBIND11_DECLARE_HOLDER_TYPE(T, PropertyRefTemplate<T>, false)
18 changes: 14 additions & 4 deletions source/include/signalflow/signalflow.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,26 @@
#include <signalflow/core/util.h>
#include <signalflow/core/version.h>

#include <signalflow/buffer/buffer.h>
#include <signalflow/buffer/ringbuffer.h>
/*------------------------------------------------------------------------
* Node
*-----------------------------------------------------------------------*/
#include <signalflow/node/node.h>
#include <signalflow/node/registry.h>

/*------------------------------------------------------------------------
* Patch
*-----------------------------------------------------------------------*/
#include <signalflow/patch/patch-node-spec.h>
#include <signalflow/patch/patch-registry.h>
#include <signalflow/patch/patch-spec.h>
#include <signalflow/patch/patch.h>

#include <signalflow/node/node.h>
#include <signalflow/node/registry.h>
/*------------------------------------------------------------------------
* Buffers
*-----------------------------------------------------------------------*/
#include <signalflow/buffer/buffer.h>
#include <signalflow/buffer/fftbuffer.h>
#include <signalflow/buffer/ringbuffer.h>

/*------------------------------------------------------------------------
* Operators
Expand Down
1 change: 1 addition & 0 deletions source/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(SRC ${SRC}
${CMAKE_CURRENT_SOURCE_DIR}/buffer/buffer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/buffer/buffer2d.cpp
${CMAKE_CURRENT_SOURCE_DIR}/buffer/fftbuffer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/graph.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/config.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/core.cpp
Expand Down
139 changes: 139 additions & 0 deletions source/src/buffer/fftbuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "signalflow/buffer/fftbuffer.h"
#include "signalflow/core/constants.h"
#include "signalflow/core/exceptions.h"
#include "signalflow/core/graph.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include <vector>

namespace signalflow
{

extern AudioGraph *shared_graph;

FFTBuffer::FFTBuffer(std::string filename, int fft_size, int hop_size)
: fft_size(fft_size), hop_size(hop_size)
{
this->num_bins = (fft_size / 2) + 1;

FILE *fd = fopen(filename.c_str(), "r");
if (fd == NULL)
{
throw std::runtime_error(std::string("Couldn't find file at path: ") + filename);
}

fseek(fd, 0, SEEK_END);
size_t file_size = ftell(fd);
fseek(fd, 0, SEEK_SET);
double num_frames_frac = (double) file_size / (this->num_bins * 2 * sizeof(float));
printf("FFTBuffer: File size %zu bytes, %.2f frames\n", file_size, num_frames_frac);
if (num_frames_frac != (int) num_frames_frac)
{
throw std::runtime_error("Error: Not an integer number of frames (found " + std::to_string(num_frames) + " frames)");
}
this->num_frames = (unsigned int) num_frames_frac;

/*--------------------------------------------------------------------------------
* If the AudioGraph has been instantiated, populate the buffer's sample
* rate and duration. Otherwise, zero them.
*-------------------------------------------------------------------------------*/
if (shared_graph)
{
this->sample_rate = shared_graph->get_sample_rate();
this->duration = (this->num_frames * this->hop_size) / this->sample_rate;
}
else
{
this->sample_rate = 0;
this->duration = 0;
}

this->resize(num_frames);
}

FFTBuffer::~FFTBuffer()
{
if (this->data)
{
delete this->data[0];
delete this->data;

if (shared_graph)
{
size_t num_bytes = this->get_total_num_values() * sizeof(sample);
shared_graph->register_memory_dealloc(num_bytes);
}
}
}

void FFTBuffer::resize(int num_frames)
{
if (this->data)
{
delete this->data[0];
delete this->data;

if (shared_graph)
{
size_t num_bytes = this->get_total_num_values() * sizeof(sample);
shared_graph->register_memory_dealloc(num_bytes);
}
}

this->num_frames = num_frames;

/*--------------------------------------------------------------------------------
* For use in numpy, memory allocation needs to be contiguous with a fixed
* stride between vectors. Allocate as one block and set element indices
* accordingly.
*-------------------------------------------------------------------------------*/
if (num_frames)
{
this->data = new sample *[this->num_frames]();

sample *data_frames = new sample[this->get_total_num_values()]();
for (unsigned int frame = 0; frame < this->num_frames; frame++)
{
this->data[frame] = data_frames + frame * (this->num_bins * 2);
}

if (shared_graph)
{
size_t num_bytes = this->get_total_num_values() * sizeof(sample);
shared_graph->register_memory_alloc(num_bytes);
}
}
else
{
this->data = nullptr;
}
}

float FFTBuffer::get_sample_rate() { return this->sample_rate; }

void FFTBuffer::set_sample_rate(float sample_rate) { this->sample_rate = sample_rate; }

unsigned long FFTBuffer::get_num_frames() { return this->num_frames; }

std::string FFTBuffer::get_filename() { return this->filename; }

unsigned int FFTBuffer::get_fft_size() { return this->fft_size; }

unsigned int FFTBuffer::get_hop_size() { return this->hop_size; }

float FFTBuffer::get_duration() { return this->duration; }

unsigned long FFTBuffer::get_total_num_values() { return this->num_frames * this->num_bins * 2; }

template class FFTBufferRefTemplate<FFTBuffer>;

}
39 changes: 39 additions & 0 deletions source/src/python/buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,43 @@ void init_python_buffer(py::module &m)
R"pbdoc(Create an envelope buffer filled with the output of a given function.)pbdoc")
.def_property_readonly("frame_offsets", &Buffer::get_frame_offsets,
R"pbdoc(Returns a list containing the offset in the envelope buffer for each frame, ranging over 0..1.)pbdoc");

/*--------------------------------------------------------------------------------
* Buffer
*-------------------------------------------------------------------------------*/
py::class_<FFTBuffer, FFTBufferRefTemplate<FFTBuffer>>(m, "FFTBuffer",
"A buffer of audio spectra in magnitude/phase format")
/*--------------------------------------------------------------------------------
* Constructors
*-------------------------------------------------------------------------------*/
.def(py::init<std::string, int, int>(), "filename"_a, "fft_size"_a, "hop_size"_a, R"pbdoc(Load an FFTBuffer from a .spectra file.)pbdoc")

/*--------------------------------------------------------------------------------
* Operators
*-------------------------------------------------------------------------------*/
.def("__str__",
[](FFTBufferRef a) {
std::string filename = a->get_filename();
if (filename.empty())
{
return "FFTBuffer (" + std::to_string(a->get_num_frames()) + " frames)";
}
else
{
return "FFTBuffer (" + filename + ", " + std::to_string(a->get_num_frames()) + " frames)";
}
})

/*--------------------------------------------------------------------------------
* Properties
*-------------------------------------------------------------------------------*/
.def_property_readonly("num_frames", &FFTBuffer::get_num_frames,
R"pbdoc(Returns the number of spectral frames in the FFT buffer.)pbdoc")
.def_property_readonly("sample_rate", &FFTBuffer::get_sample_rate,
R"pbdoc(Returns the FFT buffer's sample rate.)pbdoc")
.def_property_readonly("duration", &FFTBuffer::get_duration,
R"pbdoc(Returns the FFT buffer's duration, in seconds.)pbdoc")
.def_property_readonly(
"filename", &FFTBuffer::get_filename,
R"pbdoc(Returns the FFT buffer's filename, if the buffer has been loaded from/saved to file.)pbdoc");
}

0 comments on commit ac7982c

Please sign in to comment.