score-avnd-amuencha/Amuencha/SpiralDisplay.hpp

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();
};
}