[frequencies] seperate compute_frequencies betwin analyzer and display
This commit is contained in:
parent
a4a4ba249e
commit
c8e051a4a3
7 changed files with 48 additions and 71 deletions
|
@ -4,7 +4,17 @@ namespace Amuencha
|
||||||
{
|
{
|
||||||
void Model::prepare(setup info)
|
void Model::prepare(setup info)
|
||||||
{
|
{
|
||||||
sampling_rate = info.rate;
|
if (analyzer.isRunning()) return;
|
||||||
|
|
||||||
|
analyzer.setup(info.rate,
|
||||||
|
[&] (const std::vector<float>& r_f,
|
||||||
|
const std::vector<float>& p_s)
|
||||||
|
{
|
||||||
|
this->send_message({.reassigned_frequencies = r_f,
|
||||||
|
.power_spectrum = p_s});
|
||||||
|
});
|
||||||
|
|
||||||
|
analyzer.start(QThread::NormalPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::operator()(tick t)
|
void Model::operator()(tick t)
|
||||||
|
|
|
@ -35,22 +35,6 @@ public:
|
||||||
halp::spinbox_i32<"Periods", halp::range{.min = 0, .max = 99, .init = 30}> periods;
|
halp::spinbox_i32<"Periods", halp::range{.min = 0, .max = 99, .init = 30}> periods;
|
||||||
} inputs;
|
} inputs;
|
||||||
|
|
||||||
void process_message(const std::vector<float>& frequencies)
|
|
||||||
{
|
|
||||||
analyzer.setup(sampling_rate,
|
|
||||||
frequencies,
|
|
||||||
[&] (const std::vector<float>& r_f,
|
|
||||||
const std::vector<float>& p_s)
|
|
||||||
{
|
|
||||||
this->send_message({.reassigned_frequencies = r_f,
|
|
||||||
.power_spectrum = p_s});
|
|
||||||
},
|
|
||||||
inputs.periods);
|
|
||||||
|
|
||||||
if (!analyzer.isRunning())
|
|
||||||
analyzer.start(QThread::NormalPriority);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct outs
|
struct outs
|
||||||
{
|
{
|
||||||
halp::midi_bus<"Output"> midi;
|
halp::midi_bus<"Output"> midi;
|
||||||
|
@ -68,7 +52,6 @@ public:
|
||||||
struct ui;
|
struct ui;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float sampling_rate;
|
|
||||||
FrequencyAnalyzer analyzer;
|
FrequencyAnalyzer analyzer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,34 +30,22 @@ struct Model::ui
|
||||||
// Define the communication between UI and processor.
|
// Define the communication between UI and processor.
|
||||||
struct bus
|
struct bus
|
||||||
{
|
{
|
||||||
std::function<void(std::vector<float>&&)> send_message;
|
|
||||||
|
|
||||||
// Set up connections
|
// Set up connections
|
||||||
void init(ui& self)
|
void init(ui& self)
|
||||||
{
|
{
|
||||||
self.spiral.set_frequencies_callback(
|
|
||||||
[&] { this->send_message(self.spiral.get_frequencies()); }
|
|
||||||
);
|
|
||||||
|
|
||||||
self.controls.min.on_changed = [&] (int min)
|
self.controls.min.on_changed = [&] (int min)
|
||||||
{
|
{
|
||||||
// self.spiral.set_min_max_notes(min, self.controls.max.value);
|
self.spiral.set_min_max_notes(min, self.controls.max.value);
|
||||||
};
|
};
|
||||||
self.controls.max.on_changed = [&] (int max)
|
self.controls.max.on_changed = [&] (int max)
|
||||||
{
|
{
|
||||||
// self.spiral.set_min_max_notes(self.controls.min.value, max);
|
self.spiral.set_min_max_notes(self.controls.min.value, max);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive a message on the UI thread from the processing thread
|
// Receive a message on the UI thread from the processing thread
|
||||||
static void process_message(ui& self, const processor_to_ui& msg)
|
static void process_message(ui& self, const processor_to_ui& msg)
|
||||||
{
|
{
|
||||||
if (!first_msg)
|
|
||||||
{
|
|
||||||
self.spiral.compute_frequencies();
|
|
||||||
first_msg = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.power_spectrum.empty() ||
|
if (msg.power_spectrum.empty() ||
|
||||||
msg.reassigned_frequencies.empty())
|
msg.reassigned_frequencies.empty())
|
||||||
return;
|
return;
|
||||||
|
@ -65,10 +53,6 @@ struct Model::ui
|
||||||
self.spiral.power_handler(msg.reassigned_frequencies,
|
self.spiral.power_handler(msg.reassigned_frequencies,
|
||||||
msg.power_spectrum);
|
msg.power_spectrum);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool first_msg;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Amuencha::Model::ui::bus::first_msg = false;
|
|
||||||
|
|
|
@ -174,13 +174,32 @@ void Amuencha::FrequencyAnalyzer::run()
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Amuencha::FrequencyAnalyzer::setup(float sampling_rate, const std::vector<float>& frequencies, PowerHandler&& handler, float periods, float max_buffer_duration)
|
void Amuencha::FrequencyAnalyzer::setup(float sampling_rate,
|
||||||
|
PowerHandler&& handler,
|
||||||
|
int min_midi_note,
|
||||||
|
int max_midi_note,
|
||||||
|
float periods,
|
||||||
|
float max_buffer_duration)
|
||||||
{
|
{
|
||||||
// Block data processing while changing the data structures
|
// Block data processing while changing the data structures
|
||||||
data_mutex.lock();
|
data_mutex.lock();
|
||||||
|
|
||||||
this->samplerate_div_2pi = sampling_rate/two_pi;
|
this->samplerate_div_2pi = sampling_rate/two_pi;
|
||||||
this->frequencies = frequencies;
|
|
||||||
|
// Start with A440, but this could be parametrizable as well
|
||||||
|
const float fref = 440;
|
||||||
|
const float log2_fref = log2(fref);
|
||||||
|
const int aref = 69; // use the midi numbering scheme, because why not
|
||||||
|
float log2_fmin = (min_midi_note - aref) / 12. + log2_fref;
|
||||||
|
float log2_fmax = (max_midi_note - aref) / 12. + log2_fref;
|
||||||
|
int num_bins = max_midi_note - min_midi_note;
|
||||||
|
|
||||||
|
frequencies.resize(num_bins);
|
||||||
|
for (int b{0}; b < num_bins; ++b)
|
||||||
|
{
|
||||||
|
float bratio = (float)b / (num_bins - 1.);
|
||||||
|
frequencies[b] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
||||||
|
}
|
||||||
|
|
||||||
this->reassigned_frequencies = frequencies;
|
this->reassigned_frequencies = frequencies;
|
||||||
this->power_spectrum.resize(frequencies.size());
|
this->power_spectrum.resize(frequencies.size());
|
||||||
|
|
|
@ -56,7 +56,12 @@ public:
|
||||||
// At lower frequencies, long buffers are needed for accurate frequency separation.
|
// 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
|
// 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
|
// Too low buffers also limit the min_freq, duration must be >= period
|
||||||
void setup(float sampling_rate, const std::vector<float>& frequencies, PowerHandler&& handler, float periods = 20, float max_buffer_duration = 500);
|
void setup(float sampling_rate,
|
||||||
|
PowerHandler&& handler,
|
||||||
|
int min_midi_note = 24,
|
||||||
|
int max_midi_note = 72,
|
||||||
|
float periods = 20,
|
||||||
|
float max_buffer_duration = 500);
|
||||||
|
|
||||||
// call to remove all existing chunk references
|
// call to remove all existing chunk references
|
||||||
// this may cause signal loss, but this is usually called precisely when the signal is lost...
|
// this may cause signal loss, but this is usually called precisely when the signal is lost...
|
||||||
|
|
|
@ -5,7 +5,6 @@ Amuencha::SpiralDisplay::SpiralDisplay()
|
||||||
, max_midi_note{72}
|
, max_midi_note{72}
|
||||||
, gain{1.f}
|
, gain{1.f}
|
||||||
, visual_fading{1}
|
, visual_fading{1}
|
||||||
, on_new_frequencies{[]{}}
|
|
||||||
{
|
{
|
||||||
for (int i{0}; i < 12; i++)
|
for (int i{0}; i < 12; i++)
|
||||||
note_positions[i] = std::polar(.9f, half_pi - i * two_pi / 12);
|
note_positions[i] = std::polar(.9f, half_pi - i * two_pi / 12);
|
||||||
|
@ -24,16 +23,6 @@ void Amuencha::SpiralDisplay::set_min_max_notes(int min_midi_note, int max_midi_
|
||||||
// update();
|
// update();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<float> Amuencha::SpiralDisplay::get_frequencies() const noexcept
|
|
||||||
{
|
|
||||||
return frequencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Amuencha::SpiralDisplay::set_frequencies_callback(std::function<void ()> &&callback)
|
|
||||||
{
|
|
||||||
on_new_frequencies = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Amuencha::SpiralDisplay::compute_frequencies()
|
void Amuencha::SpiralDisplay::compute_frequencies()
|
||||||
{
|
{
|
||||||
// Now the spiral
|
// Now the spiral
|
||||||
|
@ -43,16 +32,17 @@ void Amuencha::SpiralDisplay::compute_frequencies()
|
||||||
const int aref = 69; // use the midi numbering scheme, because why not
|
const int aref = 69; // use the midi numbering scheme, because why not
|
||||||
float log2_fmin = (min_midi_note - aref) / 12. + log2_fref;
|
float log2_fmin = (min_midi_note - aref) / 12. + log2_fref;
|
||||||
float log2_fmax = (max_midi_note - aref) / 12. + log2_fref;
|
float log2_fmax = (max_midi_note - aref) / 12. + log2_fref;
|
||||||
int approx_pix_bin_width = 3;
|
// int approx_pix_bin_width = 3;
|
||||||
// number of frequency bins is the number of pixels
|
// number of frequency bins is the number of pixels
|
||||||
// along the spiral path / approx_pix_bin_width
|
// along the spiral path / approx_pix_bin_width
|
||||||
// According to mathworld, the correct formula for the path length
|
// According to mathworld, the correct formula for the path length
|
||||||
// from the origin involves sqrt and log computations.
|
// from the origin involves sqrt and log computations.
|
||||||
// Here, we just want some approximate pixel count
|
// Here, we just want some approximate pixel count
|
||||||
// => use all circles for the approx
|
// => use all circles for the approx
|
||||||
int num_octaves = (max_midi_note - min_midi_note + 11) / 12;
|
// int num_octaves = (max_midi_note - min_midi_note + 11) / 12;
|
||||||
float approx_num_pix = 0.5 * half * pi * num_octaves;
|
// float approx_num_pix = 0.5 * half * pi * num_octaves;
|
||||||
int num_bins = (int)(approx_num_pix / approx_pix_bin_width);
|
// int num_bins = (int)(approx_num_pix / approx_pix_bin_width);
|
||||||
|
int num_bins = max_midi_note - min_midi_note;
|
||||||
// one more bound than number of bins
|
// one more bound than number of bins
|
||||||
display_bins.resize(num_bins + 1);
|
display_bins.resize(num_bins + 1);
|
||||||
bin_sizes.resize(num_bins);
|
bin_sizes.resize(num_bins);
|
||||||
|
@ -66,21 +56,18 @@ void Amuencha::SpiralDisplay::compute_frequencies()
|
||||||
// used to it (e.g. wikipedia note circle)
|
// used to it (e.g. wikipedia note circle)
|
||||||
const float theta_min = half_pi - two_pi * (min_midi_note % 12) / 12;
|
const float theta_min = half_pi - two_pi * (min_midi_note % 12) / 12;
|
||||||
// wrap in anti-trigonometric direction
|
// wrap in anti-trigonometric direction
|
||||||
const float theta_max = theta_min - two_pi * (max_midi_note - min_midi_note) / 12;
|
const float theta_max = theta_min - two_pi * (max_midi_note - min_midi_note - 1) / 12;
|
||||||
|
|
||||||
frequencies.resize(num_bins);
|
|
||||||
for (int b{0}; b < num_bins; ++b)
|
for (int b{0}; b < num_bins; ++b)
|
||||||
{
|
{
|
||||||
float bratio = (float)b / (num_bins - 1.);
|
float bratio = (float)(b - 0.5) / (float)(num_bins - 1.);
|
||||||
frequencies[b] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
|
||||||
bratio = (float)(b - 0.5) / (float)(num_bins - 1.);
|
|
||||||
display_bins[b] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
display_bins[b] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
||||||
spiral_r_a[b].r = rmin + (rmax - rmin) * bratio;
|
spiral_r_a[b].r = rmin + (rmax - rmin) * bratio;
|
||||||
spiral_r_a[b].a = theta_min + (theta_max - theta_min) * bratio;
|
spiral_r_a[b].a = theta_min + (theta_max - theta_min) * bratio;
|
||||||
spiral_positions[b] = std::polar(spiral_r_a[b].r, spiral_r_a[b].a);
|
spiral_positions[b] = std::polar(spiral_r_a[b].r, spiral_r_a[b].a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// repeat one more time to avoid a second for loops
|
// repeat one more time to avoid a second for loop
|
||||||
float bratio = (float)(num_bins - 0.5) / (float)(num_bins - 1.);
|
float bratio = (float)(num_bins - 0.5) / (float)(num_bins - 1.);
|
||||||
display_bins[num_bins] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
display_bins[num_bins] = exp2(log2_fmin + (log2_fmax - log2_fmin) * bratio);
|
||||||
spiral_r_a[num_bins].r = rmin + (rmax - rmin) * bratio;
|
spiral_r_a[num_bins].r = rmin + (rmax - rmin) * bratio;
|
||||||
|
@ -92,8 +79,6 @@ void Amuencha::SpiralDisplay::compute_frequencies()
|
||||||
|
|
||||||
display_spectrum.resize(num_bins);
|
display_spectrum.resize(num_bins);
|
||||||
fill(display_spectrum.begin(), display_spectrum.end(), 0.);
|
fill(display_spectrum.begin(), display_spectrum.end(), 0.);
|
||||||
|
|
||||||
on_new_frequencies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Amuencha::SpiralDisplay::power_handler(const std::vector<float>& reassigned_frequencies,
|
void Amuencha::SpiralDisplay::power_handler(const std::vector<float>& reassigned_frequencies,
|
||||||
|
|
|
@ -78,10 +78,6 @@ struct SpiralDisplay
|
||||||
ctx.update();
|
ctx.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::vector<float> get_frequencies() const noexcept;
|
|
||||||
|
|
||||||
void set_frequencies_callback(std::function<void()>&& callback);
|
|
||||||
|
|
||||||
// // Callback when the power spectrum is available at the prescribed frequencies
|
// // Callback when the power spectrum is available at the prescribed frequencies
|
||||||
// // The ID is that of the caller, setting the color of the display
|
// // The ID is that of the caller, setting the color of the display
|
||||||
void power_handler(const std::vector<float>& reassigned_frequencies,
|
void power_handler(const std::vector<float>& reassigned_frequencies,
|
||||||
|
@ -107,9 +103,6 @@ private:
|
||||||
|
|
||||||
int min_midi_note, max_midi_note, visual_fading;
|
int min_midi_note, max_midi_note, visual_fading;
|
||||||
|
|
||||||
// central frequencies (log space)
|
|
||||||
std::vector<float> frequencies;
|
|
||||||
|
|
||||||
// local copy for maintaining the display, adapted to the drawing bins
|
// local copy for maintaining the display, adapted to the drawing bins
|
||||||
std::vector<float> display_spectrum;
|
std::vector<float> display_spectrum;
|
||||||
|
|
||||||
|
@ -136,8 +129,6 @@ private:
|
||||||
{
|
{
|
||||||
return half - y * half;
|
return half - y * half;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void()> on_new_frequencies;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue