140 delete pending_.exchange(
nullptr, std::memory_order_acq_rel);
141 delete retired_.exchange(
nullptr, std::memory_order_acq_rel);
161 polyphony_ = std::clamp(maxVoices, 1, 256);
162 voices_.assign(
static_cast<size_t>(polyphony_ + polyphony_ / 4 + 2), Voice {});
163 for (
auto& v : voices_)
165 v.env.prepare(sampleRate_);
166 for (
auto& a : v.acc) a.assign(
static_cast<size_t>(kAccLength), T(0));
168 weights_.assign(
static_cast<size_t>(2 * kMaxHalfTaps + 2), T(0));
169 (void)polyphaseTable();
170 stealFadeSamples_ = std::max(1,
static_cast<int>(std::lround(0.003 * sampleRate_)));
178 for (
auto& v : voices_) { v.active =
false; v.env.reset(); }
180 sustainPedal_ =
false;
182 activeVoices_.store(0, std::memory_order_relaxed);
200 auto set = std::make_unique<ZoneSet>();
201 set->zones.reserve(zones.size());
205 if (!buildZone(z, d))
return false;
206 set->zones.push_back(std::move(d));
208 publish(set.release());
219 return setZones(std::span<const ZoneSpec>(&z, 1));
229 void setEnvelope(T attackMs, T decayMs, T sustain, T releaseMs)
noexcept
231 if (!std::isfinite(attackMs) || !std::isfinite(decayMs)
232 || !std::isfinite(sustain) || !std::isfinite(releaseMs))
return;
233 attackMs_.store(std::max(0.01,
static_cast<double>(attackMs)), std::memory_order_relaxed);
234 decayMs_.store(std::max(0.01,
static_cast<double>(decayMs)), std::memory_order_relaxed);
235 sustain_.store(std::clamp(
static_cast<double>(sustain), 0.0, 1.0), std::memory_order_relaxed);
236 releaseMs_.store(std::max(0.01,
static_cast<double>(releaseMs)), std::memory_order_relaxed);
243 if (!std::isfinite(rangeDb))
return;
244 velocityRangeDb_.store(std::clamp(
static_cast<double>(rangeDb), 0.0, 96.0),
245 std::memory_order_relaxed);
251 if (!std::isfinite(dB))
return;
252 outputGain_.store(std::pow(10.0, std::clamp(
static_cast<double>(dB), -120.0, 24.0) / 20.0),
253 std::memory_order_relaxed);
260 void noteOn(
int key, T velocity,
int offset = 0) noexcept
262 if (!std::isfinite(velocity))
return;
263 if (velocity <= T(0)) {
noteOff(key, offset);
return; }
264 push({ EventType::NoteOn, std::clamp(key, 0, 127),
265 std::clamp(
static_cast<double>(velocity), 0.0, 1.0), offset });
269 void noteOff(
int key,
int offset = 0) noexcept
271 push({ EventType::NoteOff, std::clamp(key, 0, 127), 0.0, offset });
277 push({ EventType::Pedal, 0, down ? 1.0 : 0.0, offset });
283 if (!std::isfinite(semitones))
return;
284 push({ EventType::Bend, 0, std::clamp(
static_cast<double>(semitones), -48.0, 48.0), offset });
290 push({ EventType::AllOff, 0, 0.0, offset });
306 const int n = out.getNumSamples();
307 const int nCh = out.getNumChannels();
308 for (
int c = 0; c < nCh; ++c)
309 std::fill_n(out.getChannel(c), n, T(0));
310 if (!prepared_ || n <= 0) { eventCount_ = 0;
return; }
315 for (
int e = 0; e < eventCount_; ++e)
317 const int at = std::clamp(events_[
static_cast<size_t>(e)].offset, 0, n);
318 if (at > cursor) { render(out, cursor, at); cursor = at; }
319 apply(events_[
static_cast<size_t>(e)]);
322 if (cursor < n) render(out, cursor, n);
324 const double g = outputGain_.load(std::memory_order_relaxed);
326 for (
int c = 0; c < std::min(nCh, 2); ++c)
328 T* d = out.getChannel(c);
329 for (
int i = 0; i < n; ++i) d[i] = static_cast<T>(d[i] * g);
334 for (
const auto& v : voices_) count += v.active ? 1 : 0;
335 activeVoices_.store(count, std::memory_order_relaxed);
341 return activeVoices_.load(std::memory_order_relaxed);
351 static constexpr int kHalfTaps = 32;
353 static constexpr int kTablePerUnit = 2048;
355 static constexpr double kMaxRate = 8.0;
356 static constexpr int kMaxHalfTaps =
static_cast<int>(kHalfTaps * kMaxRate);
359 static constexpr double kCutoff = 0.9;
360 static constexpr double kBeta = 10.06;
363 static const std::vector<double>& kernelTable()
365 static const std::vector<double> table = [] {
366 std::vector<double> t(
static_cast<size_t>(kHalfTaps * kTablePerUnit + 2), 0.0);
367 auto i0 = [](
double x) {
368 double sum = 1.0, term = 1.0;
369 for (
int k = 1; k < 64; ++k)
371 term *= (x / (2.0 * k)) * (x / (2.0 * k));
373 if (term < sum * 1e-17)
break;
377 const double norm = i0(kBeta);
378 for (
size_t j = 0; j + 1 < t.size(); ++j)
380 const double x =
static_cast<double>(j) / kTablePerUnit;
381 if (x >= kHalfTaps)
break;
382 const double arg = pi<double> * kCutoff * x;
383 const double sinc = (x == 0.0) ? 1.0 : std::sin(arg) / arg;
384 const double r = x / kHalfTaps;
385 t[j] = kCutoff * sinc * i0(kBeta * std::sqrt(std::max(0.0, 1.0 - r * r))) / norm;
393 static constexpr int kPhases = 2048;
394 static constexpr int kTaps = 2 * kHalfTaps;
403 static const std::vector<T>& polyphaseTable()
405 static const std::vector<T> rows = [] {
406 const auto& h = kernelTable();
407 std::vector<T> r(
static_cast<size_t>((kPhases + 1) * kTaps), T(0));
408 for (
int q = 0; q <= kPhases; ++q)
409 for (
int t = 0; t < kTaps; ++t)
412 const long u = std::labs(
static_cast<long>(t - (kHalfTaps - 1)) * kTablePerUnit - q);
413 r[
static_cast<size_t>(q * kTaps + t)] =
414 (
static_cast<size_t>(u) < h.size()) ?
static_cast<T
>(h[
static_cast<size_t>(u)]) : T(0);
425 std::vector<T> audio;
426 std::vector<T> baked;
429 double sampleRate = 48000.0;
431 double tuneCents = 0.0;
432 int keyLow = 0, keyHigh = 127;
433 double velLow = 0.0, velHigh = 1.0;
438 int64_t loopStart = 0, loopEnd = 0, xfade = 0;
441 struct ZoneSet { std::vector<ZoneData> zones; };
443 static bool buildZone(
const ZoneSpec& z, ZoneData& d)
445 const int ch = z.audio.getNumChannels();
446 const int64_t len = z.audio.getNumSamples();
447 if (ch < 1 || len < 1 || !(z.sampleRate > 0.0) || !std::isfinite(z.sampleRate))
449 for (
int c = 0; c < ch; ++c)
450 if (z.audio.getChannel(c) ==
nullptr)
return false;
451 if (z.keyLow > z.keyHigh || z.velocityLow > z.velocityHigh
452 || !std::isfinite(z.tuneCents) || !std::isfinite(z.gainDb) || !std::isfinite(z.pan))
457 d.sampleRate = z.sampleRate;
458 d.rootKey = z.rootKey;
459 d.tuneCents = z.tuneCents;
460 d.keyLow = std::clamp(z.keyLow, 0, 127);
461 d.keyHigh = std::clamp(z.keyHigh, 0, 127);
462 d.velLow = std::clamp(z.velocityLow, 0.0, 1.0);
463 d.velHigh = std::clamp(z.velocityHigh, 0.0, 1.0);
464 d.gain = std::pow(10.0, std::clamp(z.gainDb, -120.0, 24.0) / 20.0);
465 d.pan = std::clamp(z.pan, -1.0, 1.0);
466 d.start = std::clamp<int64_t>(z.startOffset, 0, len - 1);
467 d.audio.resize(
static_cast<size_t>(ch) *
static_cast<size_t>(len));
468 for (
int c = 0; c < ch; ++c)
470 const T* src = z.audio.getChannel(c);
471 T* dst = d.audio.data() +
static_cast<size_t>(c) *
static_cast<size_t>(len);
472 for (int64_t i = 0; i < len; ++i)
473 dst[i] = std::isfinite(src[i]) ? src[i] : T(0);
477 d.loopStart = std::clamp<int64_t>(z.loopStart, 0, len);
478 d.loopEnd = std::clamp<int64_t>(z.loopEnd, 0, len);
483 d.xfade = std::clamp<int64_t>(z.loopCrossfade, 0,
484 std::min(d.loopStart, d.loopEnd - d.loopStart));
487 d.baked.resize(
static_cast<size_t>(ch) *
static_cast<size_t>(d.xfade));
488 for (
int c = 0; c < ch; ++c)
490 const T* src = d.audio.data() +
static_cast<size_t>(c) *
static_cast<size_t>(len);
491 T* dst = d.baked.data() +
static_cast<size_t>(c) *
static_cast<size_t>(d.xfade);
492 for (int64_t i = 0; i < d.xfade; ++i)
496 const double x = (
static_cast<double>(i) + 0.5) /
static_cast<double>(d.xfade);
497 const double fin = z.equalPowerCrossfade ? std::sin(0.5 * pi<double> * x) : x;
498 const double fout = z.equalPowerCrossfade ? std::cos(0.5 * pi<double> * x) : 1.0 - x;
499 const double tail =
static_cast<double>(src[d.loopEnd - d.xfade + i]);
500 const double lead =
static_cast<double>(src[d.loopStart - d.xfade + i]);
501 dst[i] =
static_cast<T
>(fout * tail + fin * lead);
511 void reclaim() noexcept
513 delete retired_.exchange(
nullptr, std::memory_order_acq_rel);
517 void publish(ZoneSet* set)
noexcept
519 delete pending_.exchange(set, std::memory_order_acq_rel);
523 void adoptPending() noexcept
525 if (fading_ !=
nullptr)
return;
526 ZoneSet* next = pending_.exchange(
nullptr, std::memory_order_acq_rel);
527 if (next ==
nullptr)
return;
533 void retireFadedSet() noexcept
535 if (fading_ ==
nullptr)
return;
536 for (
const auto& v : voices_)
537 if (v.active && v.set == fading_) return;
538 if (retired_.load(std::memory_order_acquire) !=
nullptr)
return;
539 retired_.store(fading_, std::memory_order_release);
548 const ZoneSet* set =
nullptr;
549 const ZoneData* zone =
nullptr;
552 double baseRatio = 1.0;
553 bool loopEngaged =
false;
554 bool releaseLoop =
false;
556 bool sustained =
false;
557 double gainL = 1.0, gainR = 1.0, gainMono = 1.0;
560 ADSREnvelope<double> env;
564 bool scatter =
false;
565 bool inputDone =
false;
570 std::array<std::vector<T>, 2> acc;
573 enum class EventType { NoteOn, NoteOff, Pedal, Bend, AllOff };
574 struct Event { EventType type;
int key;
double value;
int offset; };
576 void push(
const Event& e)
noexcept
578 if (!prepared_ || eventCount_ >= kMaxEvents)
return;
581 int i = eventCount_++;
582 while (i > 0 && events_[
static_cast<size_t>(i - 1)].offset > e.offset)
584 events_[
static_cast<size_t>(i)] = events_[
static_cast<size_t>(i - 1)];
587 events_[
static_cast<size_t>(i)] = e;
590 void apply(
const Event& e)
noexcept
594 case EventType::NoteOn: startNote(e.key, e.value);
break;
595 case EventType::NoteOff: releaseKey(e.key);
break;
596 case EventType::Bend: pitchBend_ = e.value;
break;
597 case EventType::Pedal:
598 sustainPedal_ = e.value > 0.5;
600 for (
auto& v : voices_)
601 if (v.active && v.sustained) release(v);
603 case EventType::AllOff:
604 sustainPedal_ =
false;
605 for (
auto& v : voices_)
606 if (v.active && v.stealLeft < 0) release(v);
611 void release(Voice& v)
noexcept
615 v.env.setRelease(
static_cast<double>(releaseMs_.load(std::memory_order_relaxed)));
620 void releaseKey(
int key)
noexcept
622 for (
auto& v : voices_)
623 if (v.active && v.held && v.key == key && v.stealLeft < 0)
625 if (sustainPedal_) { v.held =
false; v.sustained =
true; }
630 void startNote(
int key,
double velocity)
noexcept
632 if (active_ ==
nullptr)
return;
634 for (
auto& v : voices_)
635 if (v.active && v.key == key && v.stealLeft < 0 && (v.held || v.sustained))
638 const double velGain = std::pow(10.0, velocityRangeDb_.load(std::memory_order_relaxed)
639 * (velocity - 1.0) / 20.0);
640 for (
const ZoneData& z : active_->zones)
642 if (key < z.keyLow || key > z.keyHigh || velocity < z.velLow || velocity > z.velHigh)
644 Voice* v = freeVoice();
645 if (v ==
nullptr)
return;
650 v->pos =
static_cast<double>(z.start);
651 v->baseRatio = z.sampleRate / sampleRate_
652 * std::pow(2.0, (key - z.rootKey + z.tuneCents / 100.0) / 12.0);
653 v->scatter = v->baseRatio * std::pow(2.0, pitchBend_ / 12.0) > 1.0;
654 v->inputDone =
false;
660 for (
auto& a : v->acc) std::fill(a.begin(), a.end(), T(0));
662 v->releaseLoop =
false;
664 v->sustained =
false;
667 const double g = z.gain * velGain;
668 const double theta = 0.25 * pi<double> * (z.pan + 1.0);
673 v->gainL = g * std::cos(theta) * sqrt2<double>;
674 v->gainR = g * std::sin(theta) * sqrt2<double>;
678 v->gainL = g * std::min(1.0, 1.0 - z.pan);
679 v->gainR = g * std::min(1.0, 1.0 + z.pan);
681 v->env.setParameters(attackMs_.load(std::memory_order_relaxed),
682 decayMs_.load(std::memory_order_relaxed),
683 sustain_.load(std::memory_order_relaxed),
684 releaseMs_.load(std::memory_order_relaxed));
692 Voice* freeVoice() noexcept
695 Voice* idle =
nullptr;
696 for (
auto& v : voices_)
698 if (!v.active) {
if (idle ==
nullptr) idle = &v; }
699 else if (v.stealLeft < 0) ++sounding;
701 if (sounding < polyphony_ && idle !=
nullptr)
return idle;
704 Voice* victim =
nullptr;
705 for (
auto& v : voices_)
707 if (!v.active || v.stealLeft >= 0)
continue;
708 if (victim ==
nullptr) { victim = &v;
continue; }
709 const bool vr = !v.held && !v.sustained, cr = !victim->held && !victim->sustained;
710 if (vr != cr) {
if (vr) victim = &v;
continue; }
711 const double va = v.env.getCurrentValue(), ca = victim->env.getCurrentValue();
712 if (va < ca - 1e-9 || (std::abs(va - ca) <= 1e-9 && v.age < victim->age)) victim = &v;
714 if (victim !=
nullptr) victim->stealLeft = stealFadeSamples_;
715 if (idle !=
nullptr)
return idle;
717 Voice* oldest =
nullptr;
718 for (
auto& v : voices_)
719 if (v.active && v.stealLeft >= 0 && (oldest == nullptr || v.age < oldest->age)) oldest = &v;
720 if (oldest !=
nullptr) oldest->active =
false;
728 [[nodiscard]]
static double dot(
const T* w,
const T* x,
int n)
noexcept
730 T a[8] = { T(0), T(0), T(0), T(0), T(0), T(0), T(0), T(0) };
732 for (; t + 8 <= n; t += 8)
733 for (
int l = 0; l < 8; ++l)
734 a[l] += w[t + l] * x[t + l];
735 T sum = ((a[0] + a[4]) + (a[1] + a[5])) + ((a[2] + a[6]) + (a[3] + a[7]));
736 for (; t < n; ++t) sum += w[t] * x[t];
737 return static_cast<double>(sum);
741 [[nodiscard]]
static double fetch(
const ZoneData& z,
const T* chan,
const T* baked,
742 int64_t j,
bool loopEngaged)
noexcept
744 if (loopEngaged && j >= z.loopEnd)
745 j = z.loopStart + (j - z.loopEnd) % (z.loopEnd - z.loopStart);
746 if (j < 0 || j >= z.length)
return 0.0;
747 if (loopEngaged && baked !=
nullptr && j >= z.loopEnd - z.xfade && j < z.loopEnd)
748 return static_cast<double>(baked[j - (z.loopEnd - z.xfade)]);
749 return static_cast<double>(chan[j]);
754 int kernelWeights(
double frac,
double fc,
int half)
noexcept
758 const T* rows = polyphaseTable().data();
759 const double qf = frac * kPhases;
760 const int q = std::min(
static_cast<int>(qf), kPhases - 1);
761 const T g =
static_cast<T
>(qf -
static_cast<double>(q));
762 const T* r0 = rows +
static_cast<size_t>(q) * kTaps;
763 const T* r1 = r0 + kTaps;
764 T* w = weights_.data();
765 for (
int t = 0; t < kTaps; ++t)
766 w[t] = r0[t] + g * (r1[t] - r0[t]);
770 const auto& table = kernelTable();
771 const double step = fc * kTablePerUnit;
772 double x = (
static_cast<double>(-half + 1) - frac) * step;
773 const size_t last = table.size() - 1;
774 for (
int t = 0; t < 2 * half; ++t, x += step)
776 const double u = std::abs(x);
777 const size_t iu =
static_cast<size_t>(u);
781 const double f = u -
static_cast<double>(iu);
782 w = table[iu] + f * (table[iu + 1] - table[iu]);
784 weights_[
static_cast<size_t>(t)] =
static_cast<T
>(w * fc);
791 static bool advanceGain(Voice& v,
int stealFade,
double& gain)
noexcept
793 gain = v.env.getNextValue();
794 if (v.stealLeft >= 0)
796 gain *=
static_cast<double>(v.stealLeft) /
static_cast<double>(stealFade);
797 if (--v.stealLeft < 0) { v.active =
false;
return false; }
799 if (!v.env.isActive()) { v.active =
false;
return false; }
803 void emit(AudioBufferView<T> out,
int s,
const Voice& v,
double a0,
double a1,
double gain)
noexcept
805 const int nCh = out.getNumChannels();
807 out.getChannel(0)[s] +=
static_cast<T
>(a0 * v.gainMono * gain);
810 out.getChannel(0)[s] +=
static_cast<T
>(a0 * v.gainL * gain);
811 out.getChannel(1)[s] +=
static_cast<T
>((v.zone->channels == 1 ? a0 : a1) * v.gainR * gain);
815 void render(AudioBufferView<T> out,
int from,
int to)
noexcept
817 const double bendRatio = std::pow(2.0, pitchBend_ / 12.0);
818 for (
auto& v : voices_)
820 if (!v.active)
continue;
821 const double ratio = std::clamp(v.baseRatio * bendRatio, 1.0 / kMaxRate, kMaxRate);
822 if (v.scatter) renderScatter(v, out, from, to, ratio);
823 else renderGather(v, out, from, to, ratio);
833 void renderGather(Voice& v, AudioBufferView<T> out,
int from,
int to,
double ratio)
noexcept
835 const ZoneData& z = *v.zone;
836 const int nCh = out.getNumChannels();
837 const double fc = std::min(1.0, 1.0 / ratio);
838 const int half =
static_cast<int>(std::ceil(kHalfTaps / fc));
839 const double loopLen =
static_cast<double>(z.loopEnd - z.loopStart);
840 const int zc = z.channels;
841 const int used = (nCh == 1) ? zc : std::min(zc, 2);
843 for (
int s = from; s < to; ++s)
847 if (v.releaseLoop && v.pos + half <
static_cast<double>(z.loopEnd - z.xfade))
849 v.loopEngaged =
false;
850 v.releaseLoop =
false;
852 if (!v.loopEngaged && v.pos >=
static_cast<double>(z.length)) { v.active =
false;
return; }
854 const double fl = std::floor(v.pos);
855 const int64_t ip =
static_cast<int64_t
>(fl);
856 const int taps = kernelWeights(v.pos - fl, fc, half);
859 if (!advanceGain(v, stealFadeSamples_, gain))
return;
861 const int64_t first = ip - half + 1;
862 const bool direct = first >= 0 && first + taps <= z.length
863 && (!v.loopEngaged || first + taps <= z.loopEnd - z.xfade);
864 double acc[2] = { 0.0, 0.0 };
865 for (
int c = 0; c < used; ++c)
867 const T* chan = z.audio.data() +
static_cast<size_t>(c) *
static_cast<size_t>(z.length);
868 const T* baked = z.baked.empty() ? nullptr
869 : z.baked.data() +
static_cast<size_t>(c) *
static_cast<size_t>(z.xfade);
872 sum = dot(weights_.data(), chan + first, taps);
874 for (
int t = 0; t < taps; ++t)
875 sum +=
static_cast<double>(weights_[
static_cast<size_t>(t)])
876 * fetch(z, chan, baked, first + t, v.loopEngaged);
877 if (nCh == 1) acc[0] += sum / zc;
880 emit(out, s, v, acc[0], acc[1], gain);
883 if (v.loopEngaged && v.pos >=
static_cast<double>(z.loopEnd))
900 void renderScatter(Voice& v, AudioBufferView<T> out,
int from,
int to,
double ratio)
noexcept
902 const ZoneData& z = *v.zone;
903 const int nCh = out.getNumChannels();
904 const double fc = std::min(1.0, ratio);
905 const int half =
static_cast<int>(std::ceil(kHalfTaps / fc));
906 const double spacing = 1.0 / ratio;
907 const int zc = z.channels;
908 const int used = (nCh == 1) ? zc : std::min(zc, 2);
909 const int rings = (nCh == 1 || zc == 1) ? 1 : 2;
911 for (
int s = from; s < to; ++s)
914 while (!v.inputDone && v.tNext <
static_cast<double>(half))
916 if (v.releaseLoop && v.jIn < z.loopEnd - z.xfade)
918 v.loopEngaged =
false;
919 v.releaseLoop =
false;
921 double x[2] = { 0.0, 0.0 };
922 for (
int c = 0; c < used; ++c)
924 const T* chan = z.audio.data() +
static_cast<size_t>(c) *
static_cast<size_t>(z.length);
925 const T* baked = z.baked.empty() ? nullptr
926 : z.baked.data() +
static_cast<size_t>(c) *
static_cast<size_t>(z.xfade);
927 const double sample = fetch(z, chan, baked, v.jIn, v.loopEngaged);
928 if (rings == 1) x[0] += sample / used;
931 const double fl = std::floor(v.tNext);
932 const int taps = kernelWeights(v.tNext - fl, fc, half);
933 const int base = v.accPos +
static_cast<int>(fl) - half + 1;
934 for (
int r = 0; r < rings; ++r)
936 const T xs =
static_cast<T
>(x[r] * spacing);
937 T* acc = v.acc[
static_cast<size_t>(r)].data();
938 const T* w = weights_.data();
939 for (
int t = 0; t < taps; ++t)
941 const int idx = base + t;
942 if (idx >= v.accPos) acc[idx] += w[t] * xs;
946 if (v.loopEngaged && v.jIn >= z.loopEnd) v.jIn = z.loopStart;
947 if (!v.loopEngaged && v.jIn >= z.length) { v.inputDone =
true; v.drainLeft = 2 * half; }
952 if (!advanceGain(v, stealFadeSamples_, gain))
return;
953 const double a0 =
static_cast<double>(v.acc[0][
static_cast<size_t>(v.accPos)]);
954 const double a1 = (rings == 2) ?
static_cast<double>(v.acc[1][
static_cast<size_t>(v.accPos)]) : a0;
955 emit(out, s, v, a0, a1, gain);
958 for (
int r = 0; r < rings; ++r) v.acc[
static_cast<size_t>(r)][
static_cast<size_t>(v.accPos)] = T(0);
961 if (v.accPos + 2 * kMaxHalfTaps + 2 >= kAccLength)
963 const int keep = kAccLength - v.accPos;
964 for (
int r = 0; r < rings; ++r)
966 T* acc = v.acc[
static_cast<size_t>(r)].data();
967 std::copy(acc + v.accPos, acc + kAccLength, acc);
968 std::fill(acc + keep, acc + kAccLength, T(0));
972 if (v.inputDone && --v.drainLeft <= 0) { v.active =
false;
return; }
978 static constexpr int kMaxEvents = 1024;
981 static constexpr int kAccLength = 4 * kMaxHalfTaps + 256;
983 double sampleRate_ = 48000.0;
985 bool prepared_ =
false;
986 int stealFadeSamples_ = 144;
989 std::vector<Voice> voices_;
990 std::vector<T> weights_;
991 std::array<Event, kMaxEvents> events_ {};
993 bool sustainPedal_ =
false;
994 double pitchBend_ = 0.0;
996 ZoneSet* active_ =
nullptr;
997 ZoneSet* fading_ =
nullptr;
998 std::atomic<ZoneSet*> pending_ {
nullptr };
999 std::atomic<ZoneSet*> retired_ {
nullptr };
1001 std::atomic<double> attackMs_ { 1.0 };
1002 std::atomic<double> decayMs_ { 100.0 };
1003 std::atomic<double> sustain_ { 1.0 };
1004 std::atomic<double> releaseMs_ { 100.0 };
1005 std::atomic<double> velocityRangeDb_ { 24.0 };
1006 std::atomic<double> outputGain_ { 1.0 };
1007 std::atomic<int> activeVoices_ { 0 };