142 lines
4.2 KiB
C++
142 lines
4.2 KiB
C++
#pragma once
|
|
|
|
#include <boost/math/constants/constants.hpp>
|
|
#include <avnd/concepts/painter.hpp>
|
|
|
|
#include <array>
|
|
#include <ranges>
|
|
#include <complex>
|
|
|
|
namespace Amuencha
|
|
{
|
|
using namespace boost::math::float_constants;
|
|
using namespace avnd;
|
|
|
|
struct SpiralDisplay
|
|
{
|
|
SpiralDisplay();
|
|
|
|
static consteval int width() { return 500; }
|
|
static consteval int height() { return 500; }
|
|
|
|
void set_min_max_notes(int min_midi_note, int max_midi_note);
|
|
|
|
float gain;
|
|
|
|
void paint(avnd::painter auto ctx)
|
|
{
|
|
half = height() * .5f;
|
|
|
|
if (display_bins.empty())
|
|
compute_frequencies();
|
|
|
|
ctx.set_stroke_color({0, 0, 0, 255});
|
|
|
|
for (int i{0}; i < 12; i++)
|
|
{
|
|
ctx.move_to(half, half);
|
|
ctx.line_to(x(note_positions[i].real()),
|
|
y(note_positions[i].imag()));
|
|
ctx.draw_text(x(note_positions[i].real() * 1.05 - .02),
|
|
y(note_positions[i].imag() * 1.05 - .01),
|
|
note_names[i]);
|
|
}
|
|
|
|
ctx.move_to(x(spiral_positions[0].real()), y(spiral_positions[0].imag()));
|
|
|
|
// // Overlay the base spiral in black
|
|
// // FIXME : find the avendish way to store a painter
|
|
// // if (base_spiral.isEmpty()) {
|
|
// ctx.move_to(x(spiral_positions.back().real()), y(spiral_positions.back().imag()));
|
|
// for (int b = spiral_positions.size() - 1; b >= 0; --b)
|
|
// ctx.line_to(x(spiral_positions[b].real()), y(spiral_positions[b].imag()));
|
|
// //base_spiral = base_spiral.simplified();
|
|
// // }
|
|
// ctx.stroke();
|
|
|
|
ctx.set_stroke_color({255, 255, 255, 255});
|
|
|
|
int num_octaves = (max_midi_note - min_midi_note + 11) / 12;
|
|
|
|
for (int b{0}; b < display_spectrum.size(); ++b)
|
|
{
|
|
float amplitude = 0.8 / num_octaves * std::min(1.f, display_spectrum[b] * gain);
|
|
//if (display_spectrum[b]>0) cout << display_spectrum[b] << endl;
|
|
// power normalised between 0 and 1 => 0.1 = spiral branch
|
|
float r = spiral_r_a[b].r + amplitude;
|
|
auto p = std::polar(r, spiral_r_a[b].a);
|
|
ctx.line_to(x(p.real()), y(p.imag()));
|
|
r = spiral_r_a[b + 1].r + amplitude;
|
|
p = std::polar(r, spiral_r_a[b + 1].a);
|
|
ctx.line_to(x(p.real()), y(p.imag()));
|
|
}
|
|
|
|
for (int b = spiral_positions.size() - 1; b >= 0; --b)
|
|
ctx.line_to(x(spiral_positions[b].real()), y(spiral_positions[b].imag()));
|
|
|
|
ctx.stroke();
|
|
ctx.update();
|
|
}
|
|
|
|
[[nodiscard]] std::vector<float> get_frequencies() const noexcept;
|
|
|
|
std::function<void()> on_new_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
|
|
void power_handler(const std::vector<float>& reassigned_frequencies,
|
|
const std::vector<float>& power_spectrum);
|
|
|
|
private:
|
|
static const constexpr std::string_view note_names[12]
|
|
{"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
|
|
|
// Cannot intialize note_positions array with consteval, as polar is not consterpx
|
|
// static consteval const array<complex<float>, 12> init_pos()
|
|
// {
|
|
// return [] <size_t... I> (index_sequence<I...>)
|
|
// -> array<complex<float>, 12>
|
|
// { return { polar(0.9f, half_pi - I * two_pi/12) ... }; }
|
|
// (make_index_sequence<12>{});
|
|
// }
|
|
|
|
// static const constexpr array<complex<float>, 12> note_positions{inti_pos()};
|
|
|
|
std::array<std::complex<float>, 12> note_positions{};
|
|
|
|
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
|
|
std::vector<float> display_spectrum;
|
|
|
|
// bin low bounds, each bin consists of [f_b, f_b+1)
|
|
std::vector<float> display_bins;
|
|
// duplicate info for faster processing = delta_f in each bin
|
|
std::vector<float> bin_sizes;
|
|
|
|
// xy position of that frequency bin bound on the spiral
|
|
std::vector<std::complex<float>> spiral_positions;
|
|
// same info, but r.exp(angle)
|
|
// avoid all the sqrt, cos and sin at each redraw
|
|
struct Radius_Angle {float r, a;};
|
|
std::vector<Radius_Angle> spiral_r_a;
|
|
|
|
float half;
|
|
|
|
float x(float x) const
|
|
{
|
|
return half + x * half;
|
|
}
|
|
|
|
float y(float y) const
|
|
{
|
|
return half - y * half;
|
|
}
|
|
|
|
void compute_frequencies();
|
|
};
|
|
|
|
}
|