Merge branch 'main' into proceduralsynth

This commit is contained in:
2025-08-10 14:12:29 +02:00
4 changed files with 163 additions and 25 deletions

View File

@@ -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
)

145
gfx/dice.h Normal file
View File

@@ -0,0 +1,145 @@
#pragma once
#include <array>
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<point, 6> border_points;
array<rect, 7> pips;
array<quad, 6> 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

View File

@@ -1,6 +1,5 @@
#pragma once
#include "audio_buffer.h"
#include "ivoice.h"
#include "midi_event.h"
#include "voice_allocator.h"
#include <cstddef>
@@ -14,12 +13,14 @@ namespace trnr {
template <typename t_voice, typename t_sample>
class midi_synth : public voice_allocator<t_voice, t_sample> {
public:
std::vector<midi_event> 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<t_voice, t_sample>(num_voices)
, voice_allocator<t_voice, t_sample>(num_voices)
{
// checks whether template derives from ivoice
typedef t_voice assert_at_compile_time[is_convertible<t_voice, t_sample>::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<midi_event> m_event_queue;
int m_block_size;
bool m_voices_active;
};
} // namespace trnr

View File

@@ -1,6 +1,5 @@
#pragma once
#include "audio_buffer.h"
#include "ivoice.h"
#include "midi_event.h"
#include <algorithm>
@@ -15,14 +14,15 @@ template <typename t_voice, typename t_sample>
class voice_allocator {
public:
std::vector<std::shared_ptr<t_voice>> voice_ptrs;
std::vector<midi_event> 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<t_voice, t_sample>::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<midi_event> 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<t_voice> steal_voice()
{
// Try to find a voice that is not gated (not playing a note)
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;
}