From d55490d7d1db91697df86d923e4e4ca75817be54 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 10 Aug 2025 14:08:06 +0200 Subject: [PATCH 1/2] remove encapsulation from allocator + midi synth, parameter to disable stealing of non-gated voices --- synth/midi_synth.h | 14 +++++--------- synth/voice_allocator.h | 28 ++++++++++++---------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/synth/midi_synth.h b/synth/midi_synth.h index 4542b7d..a8a1829 100644 --- a/synth/midi_synth.h +++ b/synth/midi_synth.h @@ -1,6 +1,5 @@ #pragma once #include "audio_buffer.h" -#include "ivoice.h" #include "midi_event.h" #include "voice_allocator.h" #include @@ -14,12 +13,14 @@ namespace trnr { template class midi_synth : public voice_allocator { public: + std::vector m_event_queue; + int m_block_size; + bool m_voices_active; + midi_synth(size_t num_voices = 1) : m_voices_active {false} - , trnr::voice_allocator(num_voices) + , voice_allocator(num_voices) { - // checks whether template derives from ivoice - typedef t_voice assert_at_compile_time[is_convertible::value ? 1 : -1]; } void set_samplerate_blocksize(double _samplerate, int _block_size) @@ -86,10 +87,5 @@ public: { for (int i = 0; i < m_event_queue.size(); i++) { m_event_queue.at(i).offset -= frames; } } - -private: - std::vector m_event_queue; - int m_block_size; - bool m_voices_active; }; } // namespace trnr diff --git a/synth/voice_allocator.h b/synth/voice_allocator.h index bedc619..6bb20df 100644 --- a/synth/voice_allocator.h +++ b/synth/voice_allocator.h @@ -1,6 +1,5 @@ #pragma once #include "audio_buffer.h" -#include "ivoice.h" #include "midi_event.h" #include @@ -15,14 +14,15 @@ template class voice_allocator { public: std::vector> voice_ptrs; + std::vector input_queue; + int index_to_steal = 0; + const int internal_block_size = 16; + size_t active_voice_count; + bool steal_non_gated = true; voice_allocator(size_t num_voices = 1) { - // checks whether template derives from ivoice - typedef t_voice assert_at_compile_time[is_convertible::value ? 1 : -1]; - - assert(num_voices > 0 && "voice_reserve must be greater than 0"); - + assert(num_voices > 0 && "number of voices must be greater than 0"); init_voice_ptrs(num_voices); } @@ -89,12 +89,6 @@ public: for (const auto& v : voice_ptrs) { v->set_samplerate(_samplerate); } } -private: - std::vector input_queue; - int index_to_steal = 0; - const int internal_block_size = 16; - size_t active_voice_count; - void init_voice_ptrs(size_t num_voices) { voice_ptrs.reserve(num_voices); @@ -114,13 +108,15 @@ private: std::shared_ptr steal_voice() { // Try to find a voice that is not gated (not playing a note) - for (size_t i = 0; i < active_voice_count; ++i) { - if (!voice_ptrs[i]->gate) { return voice_ptrs[i]; } - } + if (steal_non_gated) + for (size_t i = 0; i < active_voice_count; ++i) { + if (!voice_ptrs[i]->gate) { return voice_ptrs[i]; } + } // If all voices are gated, steal one round-robin auto voice = voice_ptrs[index_to_steal]; - index_to_steal = (index_to_steal + 1) % active_voice_count; + index_to_steal++; + if (index_to_steal >= active_voice_count) index_to_steal = 0; return voice; } From 391e50ad7f912e1b27619e8785237c99bbf7cc69 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 10 Aug 2025 14:08:57 +0200 Subject: [PATCH 2/2] add dice --- CMakeLists.txt | 1 + gfx/dice.h | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 gfx/dice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 20e5554..db7f8a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,4 +11,5 @@ target_include_directories(trnr-lib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/oversampling ${CMAKE_CURRENT_SOURCE_DIR}/synth ${CMAKE_CURRENT_SOURCE_DIR}/util + ${CMAKE_CURRENT_SOURCE_DIR}/gfx ) diff --git a/gfx/dice.h b/gfx/dice.h new file mode 100644 index 0000000..f649866 --- /dev/null +++ b/gfx/dice.h @@ -0,0 +1,145 @@ +#pragma once +#include + +using namespace std; + +namespace trnr { +struct point { + float x; + float y; +}; + +struct rect { + float x; + float y; + float width; + float height; +}; + +struct quad { + point p1, p2, p3, p4; +}; + +struct dice { + int width; + int height; + array border_points; + array pips; + array segments_left; +}; + +inline void dice_init(dice& d, float width, float height) +{ + const float shortening = 0.866f; + + float face_height, dice_width; + face_height = dice_width = height / 2.f; + float mid_x = width / 2.f; + float face_half_height = face_height / 2.f; + float face_width = dice_width * shortening; + + // border points + point b_p1 = point {mid_x, 0}; + point b_p2 = point {mid_x + face_width, face_half_height}; + point b_p3 = point {mid_x + face_width, face_half_height + face_height}; + point b_p4 = point {mid_x, height}; + point b_p5 = point {mid_x - face_width, face_half_height + face_height}; + point b_p6 = point {mid_x - face_width, face_half_height}; + + d.border_points = {b_p1, b_p2, b_p3, b_p4, b_p5, b_p6}; + + const float padding = face_height * 0.09f; + const float pad_x = padding * shortening; + const float pad_y = padding; + const float padded_face_height = face_height - 2 * pad_y; + + const int segments = d.segments_left.size(); + // define the ratio between segments and gaps as 3:1 + const int segment_parts = 3; + const int gap_part = 1; + // the number of parts per segment + const int parts_per_segment = segment_parts + gap_part; + const int parts = segments * parts_per_segment - 2; // remove last gap + + const float segment_height = (padded_face_height / parts) * segment_parts; + const float gap_height = padded_face_height / parts; + + // calculate segments of the left face + for (int i = 0; i < d.segments_left.size(); i++) { + const point base_p1 = point {mid_x - face_width + pad_x, face_half_height + pad_y}; + const point base_p2 = point {mid_x - pad_x, face_height}; + float seg_y = i * (segment_height + gap_height); + point p1 = {base_p1.x, base_p1.y + seg_y}; + point p2 = {base_p2.x, base_p2.y + seg_y}; + point p3 = {base_p2.x, base_p2.y + seg_y + segment_height}; + point p4 = {base_p1.x, base_p1.y + seg_y + segment_height}; + d.segments_left[i] = {p1, p2, p3, p4}; + } + + // calculate pip positions for top face (30-degree isometric perspective) + // correct center of the diamond face + // the diamond spans from b_p1 (top) to the middle of the left face + float diamond_center_x = mid_x; + float diamond_center_y = face_half_height / 2.0f + face_half_height / 2.0f; // move down to actual center + + // for 30-degree isometric, the grid directions are: + float cos30 = 1.f; // cos(30°) = √3/2 ≈ 0.866 + float sin30 = 0.5f; // sin(30°) = 1/2 + + // scale the grid vectors to fit properly within the diamond + float grid_scale = 0.25f; // smaller scale to fit within diamond bounds + + // grid vector 1: 30 degrees from horizontal (toward top-left of diamond) + float grid1_x = -cos30 * face_width * grid_scale; + float grid1_y = -sin30 * face_height * grid_scale; + + // grid vector 2: -30 degrees from horizontal (toward top-right of diamond) + float grid2_x = cos30 * face_width * grid_scale; + float grid2_y = -sin30 * face_height * grid_scale; + + const float pip_h = face_height * 0.1f; + const float pip_w = pip_h * 2 * shortening; + + // position pips in the 30-degree isometric grid + for (int i = 0; i < 7; i++) { + float u = 0, v = 0; // grid coordinates + + switch (i) { + case 0: + u = 0; + v = 0; + break; // center + case 1: + u = -1; + v = 1; + break; // top-left + case 2: + u = 1; + v = -1; + break; // bottom-right + case 3: + u = 1; + v = 1; + break; // top-right + case 4: + u = -1; + v = -1; + break; // bottom-left + case 5: + u = -1; + v = 0; + break; // center-left + case 6: + u = 1; + v = 0; + break; // center-right + } + + // convert grid coordinates to world coordinates using 30-degree vectors + float pip_x = diamond_center_x + u * grid1_x + v * grid2_x; + float pip_y = diamond_center_y + u * grid1_y + v * grid2_y; + + d.pips[i] = {pip_x - pip_w / 2, pip_y - pip_h / 2, pip_w, pip_h}; + } +} +} // namespace trnr