52template <std::
size_t MaximumLogicalNotes = 32, std::
size_t MaximumVoicesPerNote = 16>
55 static_assert(MaximumLogicalNotes > 0 && MaximumVoicesPerNote > 0);
56 static_assert(MaximumLogicalNotes <=
57 std::numeric_limits<std::size_t>::max() / MaximumVoicesPerNote);
62 if (prepared_ && allocator_ != &allocator && !rebind_allowed_)
return false;
63 if (prepared_ && (!owned_empty() ||
64 (allocator_ && allocator_->allocated_voice_count() != 0)))
66 if (allocator.max_voices() == 0 ||
67 allocator.max_voices() > MaximumLogicalNotes * MaximumVoicesPerNote ||
68 allocator.allocated_voice_count() != 0)
return false;
69 allocator_ = &allocator;
72 rebind_allowed_ =
false;
76 bool prepared() const noexcept {
return prepared_; }
81 std::span<UnisonVoiceTermination> terminations,
82 std::span<VoiceModulationBuffer> modulation = {})
noexcept {
84 if (!valid(request, modulation) || find_source(request.source_id) != npos ||
85 !consistent())
return result;
87 std::array<bool, MaximumLogicalNotes> victims{};
88 std::size_t victim_voice_count = 0;
89 for (std::size_t i = 0; i < MaximumLogicalNotes; ++i) {
90 if (stacks_[i].used && request.choke_group != 0 &&
91 stacks_[i].choke_group == request.choke_group) {
93 victim_voice_count += stacks_[i].count;
96 std::size_t free_after = allocator_->max_voices() -
97 allocator_->allocated_voice_count() + victim_voice_count;
98 while (free_after <
request.voice_count) {
99 const auto oldest = oldest_unmarked(victims);
100 if (oldest == npos)
return result;
102 victim_voice_count += stacks_[
oldest].count;
103 free_after += stacks_[
oldest].count;
105 auto slot = first_unused_or_victim(victims);
107 slot = oldest_unmarked(victims);
108 if (slot == npos)
return result;
109 victims[slot] =
true;
110 victim_voice_count += stacks_[slot].count;
112 if (terminations.size() < victim_voice_count)
return result;
116 rebind_allowed_ =
false;
118 for (std::size_t i = 0;
i < MaximumLogicalNotes; ++
i) {
119 if (!victims[i])
continue;
120 const auto reason =
request.choke_group != 0 &&
121 stacks_[
i].choke_group ==
request.choke_group
130 next.choke_group =
request.choke_group;
131 next.serial = next_serial_++;
132 for (std::size_t i = 0;
i <
request.voice_count; ++
i) {
133 auto allocation = allocator_->trigger(InstrumentVoiceTrigger{
135 .sample_id =
request.sample_id,
136 .voice_group =
request.voice_group,
139 if (!allocation.allocated || allocation.stolen || allocation.termination_count != 0) {
140 for (std::size_t j = 0;
j < next.count; ++
j)
141 allocator_->finish_voice(next.children[j].voice_index);
144 next.children[next.count++] = {allocation.voice_index, allocation.voice_id,
146 if (!modulation.empty()) modulation[allocation.voice_index].reset();
148 stacks_[slot] = next;
155 std::span<const UnisonVoiceChild>
children(std::uint64_t source_id)
const noexcept {
156 const auto index = find_source(source_id);
157 if (index == npos)
return {};
158 return {stacks_[index].children.data(), stacks_[index].count};
162 bool release(std::uint64_t source_id)
noexcept {
163 const auto index = find_source(source_id);
164 if (index == npos || !consistent())
return false;
165 bool changed =
false;
166 for (std::size_t i = 0; i < stacks_[index].count; ++i) {
167 const auto child = stacks_[index].children[i];
168 changed = allocator_->release_voice(child.voice_index) || changed;
175 bool finish(std::uint32_t voice_index, std::uint64_t voice_id,
176 std::uint64_t generation,
177 std::span<VoiceModulationBuffer> modulation = {})
noexcept {
178 if (!prepared_ || !modulation_valid(modulation) || voice_index >= allocator_->
max_voices())
180 for (
auto& stack : stacks_) {
181 if (!stack.used)
continue;
182 for (std::size_t i = 0; i < stack.count; ++i) {
183 if (stack.children[i].voice_index != voice_index ||
184 stack.children[i].voice_id != voice_id ||
185 stack.children[i].generation != generation)
continue;
186 const auto& voice = allocator_->
voices()[voice_index];
189 if (!allocator_->
finish_voice(voice_index))
return false;
190 if (!modulation.empty()) modulation[voice_index].reset();
191 stack.children[i] = stack.children[stack.count - 1];
193 if (stack.count == 0) stack = {};
203 std::span<UnisonVoiceTermination> terminations,
204 std::span<VoiceModulationBuffer> modulation = {})
noexcept {
206 if (!prepared_ || !modulation_valid(modulation) || !consistent())
return result;
207 std::size_t required = 0;
208 for (
const auto& stack : stacks_)
if (stack.used) required += stack.count;
209 if (terminations.size() < required)
return result;
210 for (std::size_t i = 0; i < MaximumLogicalNotes; ++i)
223 bool reset(std::span<VoiceModulationBuffer> modulation = {})
noexcept {
224 if (!prepared_ || !modulation_valid(modulation))
return false;
226 if (!modulation.empty())
for (
auto& buffer : modulation) buffer.reset();
228 if (++generation_ == 0) ++generation_;
229 rebind_allowed_ =
true;
235 std::array<UnisonVoiceChild, MaximumVoicesPerNote> children{};
236 std::uint64_t source_id = 0;
237 std::uint64_t serial = 0;
238 std::uint32_t choke_group = 0;
239 std::size_t count = 0;
242 static constexpr std::size_t npos = std::numeric_limits<std::size_t>::max();
244 bool valid(
const UnisonVoiceStackTrigger& request,
245 std::span<VoiceModulationBuffer> modulation)
const noexcept {
246 return prepared_ &&
request.source_id != 0 &&
request.voice_count > 0 &&
247 request.voice_count <= MaximumVoicesPerNote &&
248 request.voice_count <= allocator_->max_voices() &&
request.note >= 0 &&
250 modulation_valid(modulation);
252 bool modulation_valid(std::span<VoiceModulationBuffer> modulation)
const noexcept {
253 return modulation.empty() ||
254 (allocator_ && modulation.size() == allocator_->max_voices());
256 std::size_t find_source(std::uint64_t
id)
const noexcept {
257 for (std::size_t i = 0;
i < MaximumLogicalNotes; ++
i)
258 if (stacks_[i].used && stacks_[i].source_id ==
id)
return i;
261 bool consistent() const noexcept {
262 std::size_t tracked = 0;
263 for (
const auto& stack : stacks_) {
264 if (!stack.used)
continue;
265 tracked += stack.count;
266 for (std::size_t i = 0;
i < stack.count; ++
i) {
267 const auto child = stack.children[
i];
268 if (child.voice_index >= allocator_->max_voices())
return false;
269 const auto& voice = allocator_->voices()[child.voice_index];
270 if (voice.state ==
VoiceState::Free || voice.voice_id != child.voice_id)
return false;
273 return tracked == allocator_->allocated_voice_count();
275 bool owned_empty() const noexcept {
276 for (
const auto& stack : stacks_)
if (stack.used)
return false;
279 std::size_t oldest_unmarked(
const std::array<bool, MaximumLogicalNotes>& marked)
const noexcept {
280 std::size_t result = npos;
281 for (std::size_t i = 0;
i < MaximumLogicalNotes; ++
i)
282 if (stacks_[i].used && !marked[i] &&
283 (result == npos || stacks_[i].serial < stacks_[result].serial)) result =
i;
286 std::size_t first_unused_or_victim(
287 const std::array<bool, MaximumLogicalNotes>& victims)
const noexcept {
288 for (std::size_t i = 0;
i < MaximumLogicalNotes; ++
i)
289 if (!stacks_[i].used || victims[i])
return i;
293 std::span<UnisonVoiceTermination> out, std::uint32_t& written,
294 std::span<VoiceModulationBuffer> modulation)
noexcept {
295 auto& stack = stacks_[
index];
296 for (std::size_t i = 0;
i < stack.count; ++
i) {
297 const auto child = stack.children[
i];
298 out[written++] = {{child.voice_index, child.voice_id, reason,
299 allocator_->termination_fade_frames()},
301 allocator_->finish_voice(child.voice_index);
302 if (!modulation.empty()) modulation[child.voice_index].reset();
306 void reset_records() noexcept {
307 for (
auto& stack : stacks_) stack = {};
311 std::array<Stack, MaximumLogicalNotes> stacks_{};
312 InstrumentVoiceAllocator* allocator_ =
nullptr;
313 std::uint64_t next_serial_ = 1;
314 bool prepared_ =
false;
315 bool rebind_allowed_ =
true;
316 std::uint64_t generation_ = 1;
Definition instrument_voice_allocator.hpp:68
std::span< const InstrumentVoice > voices() const noexcept
Definition instrument_voice_allocator.hpp:83
std::uint32_t max_voices() const noexcept
Definition instrument_voice_allocator.hpp:76
bool finish_voice(std::uint32_t voice_index) noexcept
Definition unison_voice_stack.hpp:53
bool prepared() const noexcept
Definition unison_voice_stack.hpp:76
bool release(std::uint64_t source_id) noexcept
Audio-thread-safe note-off for every still-active child in a stack.
Definition unison_voice_stack.hpp:162
UnisonVoiceStackResult trigger(const UnisonVoiceStackTrigger &request, std::span< UnisonVoiceTermination > terminations, std::span< VoiceModulationBuffer > modulation={}) noexcept
Audio-thread-safe, bounded complete-stack allocation.
Definition unison_voice_stack.hpp:80
bool reset(std::span< VoiceModulationBuffer > modulation={}) noexcept
Definition unison_voice_stack.hpp:223
bool prepare(InstrumentVoiceAllocator &allocator) noexcept
Control-side binding to an already prepared, empty allocator.
Definition unison_voice_stack.hpp:61
std::span< const UnisonVoiceChild > children(std::uint64_t source_id) const noexcept
Non-owning child view, invalidated by the next mutating manager call.
Definition unison_voice_stack.hpp:155
bool finish(std::uint32_t voice_index, std::uint64_t voice_id, std::uint64_t generation, std::span< VoiceModulationBuffer > modulation={}) noexcept
Definition unison_voice_stack.hpp:175
UnisonVoiceStackTerminationResult terminate_all(std::span< UnisonVoiceTermination > terminations, std::span< VoiceModulationBuffer > modulation={}) noexcept
Definition unison_voice_stack.hpp:202
Definition analyzer_provider.hpp:12
VoiceTerminationReason
Definition instrument_voice_allocator.hpp:23
@ Stolen
Definition instrument_voice_allocator.hpp:25
@ Choked
Definition instrument_voice_allocator.hpp:24
@ Free
Definition instrument_voice_allocator.hpp:18
@ Released
Definition instrument_voice_allocator.hpp:20
constexpr std::uint32_t kInvalidSampleId
Definition sample_identity.hpp:8
@ index
Definition gpu_pass.hpp:140
@ oldest
Recycle the grain furthest through its window — the quietest one when durations match,...
Definition granular.hpp:123
@ i
Definition formant_filter_bank.hpp:20
@ j
Definition input_events.hpp:40
Definition unison_voice_stack.hpp:25
std::uint64_t generation
Definition unison_voice_stack.hpp:28
std::uint64_t voice_id
Definition unison_voice_stack.hpp:27
std::uint32_t voice_index
Definition unison_voice_stack.hpp:26
Definition unison_voice_stack.hpp:36
std::uint64_t source_id
Definition unison_voice_stack.hpp:38
std::uint32_t termination_count
Definition unison_voice_stack.hpp:40
bool allocated
Definition unison_voice_stack.hpp:37
std::size_t voice_count
Definition unison_voice_stack.hpp:39
Definition unison_voice_stack.hpp:43
bool terminated
Definition unison_voice_stack.hpp:44
std::uint32_t termination_count
Definition unison_voice_stack.hpp:45
Definition unison_voice_stack.hpp:14
std::uint32_t voice_group
Definition unison_voice_stack.hpp:21
std::uint32_t sample_id
Definition unison_voice_stack.hpp:20
std::uint64_t source_id
Definition unison_voice_stack.hpp:16
std::size_t voice_count
Definition unison_voice_stack.hpp:18
std::uint32_t choke_group
Definition unison_voice_stack.hpp:22
int note
Definition unison_voice_stack.hpp:19
Definition unison_voice_stack.hpp:31
VoiceTermination voice
Definition unison_voice_stack.hpp:32
std::uint64_t generation
Definition unison_voice_stack.hpp:33
Definition instrument_voice_allocator.hpp:40