Merge branch 'main' into proceduralsynth
This commit is contained in:
@@ -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
145
gfx/dice.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user