#pragma once #include #include #include #include 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::function send_message; // Define inputs and outputs ports. // See the docs at https://github.com/celtera/avendish struct ins { halp::fixed_audio_bus<"Input", double, 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& frequencies) { this->frequencies = frequencies; reassigned_frequencies = frequencies; power_spectrum.resize(frequencies.size()); if (sampling_rate != 0) 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; // Arguments are: frequency bins [f,f+1), and power in each bin // hence the first vector size is 1 more than the second // TODO if useful: make a proper listener API with id, etc typedef std::function&, const std::vector&)> PowerHandler; PowerHandler power_handler; // sampling rate in Hz // freqs in Hz // PowerHandler for the callback // 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: // The window is the usual Kaiser with alpha=3 static void initialize_window(std::vector& window); static void initialize_window_deriv(std::vector& 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> windowed_sines; std::vector frequencies; std::vector power_normalization_factors; float sampling_rate; float samplerate_div_2pi; std::vector big_buffer; std::vector reassigned_frequencies; std::vector 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& window, std::vector& window_deriv); void write_to_cache(std::vector& window, std::vector& window_deriv); std::map> mem_win_cache, mem_winderiv_cache; }; }