#pragma once #include #include #include #include #include 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(); // Draw note axis and names // TODO : replace with CSV background 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] * 100); //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(); } // // 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& reassigned_frequencies, const std::vector& power_spectrum); void compute_frequencies(); 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, 12> init_pos() // { // return [] (index_sequence) // -> array, 12> // { return { polar(0.9f, half_pi - I * two_pi/12) ... }; } // (make_index_sequence<12>{}); // } // static const constexpr array, 12> note_positions{inti_pos()}; std::array, 12> note_positions{}; int min_midi_note, max_midi_note, visual_fading; // local copy for maintaining the display, adapted to the drawing bins std::vector display_spectrum; // bin low bounds, each bin consists of [f_b, f_b+1) std::vector display_bins; // duplicate info for faster processing = delta_f in each bin std::vector bin_sizes; // xy position of that frequency bin bound on the spiral std::vector> 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 spiral_r_a; float half; float x(float x) const { return half + x * half; } float y(float y) const { return half - y * half; } }; }