114 lines
3.7 KiB
C++
114 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include <halp/audio.hpp>
|
|
#include <halp/midi.hpp>
|
|
#include <halp/controls.hpp>
|
|
#include <halp/meta.hpp>
|
|
|
|
namespace Amuencha
|
|
{
|
|
class Analyser
|
|
{
|
|
public:
|
|
halp_meta(name, "Amuencha")
|
|
halp_meta(category, "Audio")
|
|
halp_meta(c_name, "amuencha")
|
|
halp_meta(uuid, "b37351b4-7b8d-4150-9e1c-708eec9182b2")
|
|
|
|
// This one will be memcpy'd as it is a trivial type
|
|
struct processor_to_ui
|
|
{
|
|
int min;
|
|
int max;
|
|
std::vector<float> reassigned_frequencies;
|
|
std::vector<float> power_spectrum;
|
|
};
|
|
|
|
std::function<void(processor_to_ui&&)> send_message;
|
|
|
|
// Define inputs and outputs ports.
|
|
// See the docs at https://github.com/celtera/avendish
|
|
struct ins
|
|
{
|
|
halp::fixed_audio_bus<"Input", float, 1> audio;
|
|
struct : halp::hslider_i32<"Min", halp::range{.min = 0, .max = 127, .init = 24}>
|
|
{
|
|
void update(Analyser& self)
|
|
{
|
|
self.send_message({.min = this->value, .max = self.inputs.max.value});
|
|
}
|
|
} min;
|
|
struct : halp::hslider_i32<"Min", halp::range{.min = 0, .max = 127, .init = 72}>
|
|
{
|
|
void update(Analyser& self)
|
|
{
|
|
self.send_message({.min = self.inputs.min.value, .max = this->value});
|
|
}
|
|
} max;
|
|
halp::spinbox_i32<"Periods",
|
|
halp::range{.min = 0, .max = 99, .init = 30}> periods;
|
|
} inputs;
|
|
|
|
void process_message(const std::vector<float>& frequencies)
|
|
{
|
|
this->frequencies = frequencies;
|
|
reassigned_frequencies = frequencies;
|
|
power_spectrum.resize(frequencies.size());
|
|
analyzer_setup();
|
|
}
|
|
|
|
struct outs
|
|
{
|
|
halp::midi_bus<"Output"> midi;
|
|
} outputs;
|
|
|
|
using setup = halp::setup;
|
|
void prepare(halp::setup info);
|
|
|
|
// Do our processing for N samples
|
|
using tick = halp::tick;
|
|
|
|
// Defined in the .cpp
|
|
void operator()(halp::tick t);
|
|
|
|
// UI is defined in another file to keep things clear.
|
|
struct ui;
|
|
|
|
// max_buffer_duration in milliseconds, specifies the largest buffer for computing the frequency content
|
|
// At lower frequencies, long buffers are needed for accurate frequency separation.
|
|
// When that max buffer duration is reached, then it is capped and the frequency resolution decreases
|
|
// Too low buffers also limit the min_freq, duration must be >= period
|
|
void analyzer_setup(float max_buffer_duration = 500);
|
|
|
|
private:
|
|
// new data chunks arrived since the last periodic processing
|
|
std::vector<std::pair<float*,int>> chunks;
|
|
|
|
// The window is the usual Kaiser with alpha=3
|
|
static void initialize_window(std::vector<float>& window);
|
|
static void initialize_window_deriv(std::vector<float>& window);
|
|
// The filter bank. One filter per frequency
|
|
// The 4 entries in the v4sf are the real, imaginary parts of the windowed
|
|
// sine wavelet, and the real, imaginary parts of the derived windowed sine
|
|
// used for reassigning the power spectrum.
|
|
// Hopefully, with SIMD, computing all 4 of them is the same price as just one
|
|
// TODO: v8sf and compute 2 freqs at the same time
|
|
typedef float v4sf __attribute__ ((vector_size (16)));
|
|
std::vector<std::vector<v4sf>> windowed_sines;
|
|
std::vector<float> frequencies;
|
|
std::vector<float> power_normalization_factors;
|
|
float sampling_rate;
|
|
float samplerate_div_2pi;
|
|
std::vector<float> big_buffer;
|
|
std::vector<float> reassigned_frequencies;
|
|
std::vector<float> power_spectrum;
|
|
|
|
// caching computations for faster init
|
|
// on disk for persistence between executions,
|
|
// in memory for avoiding reloading from disk when changing the spiral size
|
|
bool read_from_cache(std::vector<float>& window, std::vector<float>& window_deriv);
|
|
void write_to_cache(std::vector<float>& window, std::vector<float>& window_deriv);
|
|
std::map<int, std::vector<float>> mem_win_cache, mem_winderiv_cache;
|
|
};
|
|
|
|
}
|