Skip to content

Commit 8bbc98a

Browse files
committed
perf: switch voice mixing to block render paths
1 parent 917acb8 commit 8bbc98a

3 files changed

Lines changed: 118 additions & 37 deletions

File tree

src/PluginProcessor.cpp

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -532,10 +532,10 @@ bool IntersectProcessor::isBusesLayoutSupported (const BusesLayout& layouts) con
532532
return true;
533533
}
534534

535-
void IntersectProcessor::prepareToPlay (double sampleRate, int /*samplesPerBlock*/)
535+
void IntersectProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
536536
{
537537
currentSampleRate = sampleRate;
538-
voicePool.setSampleRate (sampleRate);
538+
voicePool.prepareToPlay (sampleRate, samplesPerBlock);
539539
std::fill (std::begin (heldNotes), std::end (heldNotes), false);
540540

541541
auto sampleSnap = sampleData.getSnapshot();
@@ -2050,53 +2050,34 @@ void IntersectProcessor::processBlock (juce::AudioBuffer<float>& buffer,
20502050

20512051
buffer.clear();
20522052

2053+
const int numSamples = buffer.getNumSamples();
2054+
20532055
if (numActiveBuses <= 1)
20542056
{
2055-
// Fast path: single stereo output
2056-
for (int i = 0; i < buffer.getNumSamples(); ++i)
2057-
{
2058-
float sL = 0.0f, sR = 0.0f;
2059-
voicePool.processSample (sampleData, currentSampleRate, sL, sR);
2060-
if (busL[0]) busL[0][i] = sanitiseSample (sL);
2061-
if (busR[0]) busR[0][i] = sanitiseSample (sR);
2062-
}
2057+
// Fast path: single stereo output — voice-first block render
2058+
voicePool.renderMainBusBlock (sampleData, busL[0], busR[0], numSamples);
2059+
2060+
// Sanitise once after all voices have been mixed
2061+
if (busL[0])
2062+
for (int i = 0; i < numSamples; ++i)
2063+
busL[0][i] = sanitiseSample (busL[0][i]);
2064+
if (busR[0])
2065+
for (int i = 0; i < numSamples; ++i)
2066+
busR[0][i] = sanitiseSample (busR[0][i]);
20632067
}
20642068
else
20652069
{
2066-
// Multi-out: route each voice to its assigned bus
2067-
constexpr int previewIdx = VoicePool::kPreviewVoiceIndex;
2068-
for (int i = 0; i < buffer.getNumSamples(); ++i)
2069-
{
2070-
for (int vi = 0; vi < voicePool.getMaxActiveVoices(); ++vi)
2071-
{
2072-
float vL = 0.0f, vR = 0.0f;
2073-
voicePool.processVoiceSample (vi, sampleData, currentSampleRate, vL, vR);
2070+
// Multi-out: voice-first block render with per-voice bus routing
2071+
voicePool.renderRoutedBlock (sampleData, busL.data(), busR.data(), numActiveBuses, numSamples);
20742072

2075-
int bus = voicePool.getVoice (vi).outputBus;
2076-
if (bus < 0 || bus >= numActiveBuses || busL[bus] == nullptr) bus = 0;
2077-
if (busL[bus]) busL[bus][i] += vL;
2078-
if (busR[bus]) busR[bus][i] += vR;
2079-
}
2080-
2081-
// Always process preview voice (LazyChopEngine) on main bus
2082-
if (previewIdx >= voicePool.getMaxActiveVoices()
2083-
&& juce::isPositiveAndBelow (previewIdx, VoicePool::kMaxVoices)
2084-
&& voicePool.getVoice (previewIdx).active)
2085-
{
2086-
float vL = 0.0f, vR = 0.0f;
2087-
voicePool.processVoiceSample (previewIdx, sampleData, currentSampleRate, vL, vR);
2088-
if (busL[0]) busL[0][i] += vL;
2089-
if (busR[0]) busR[0][i] += vR;
2090-
}
2091-
}
20922073
// Clamp / NaN-guard every active bus after accumulation
20932074
for (int b = 0; b < numActiveBuses; ++b)
20942075
{
20952076
if (busL[b])
2096-
for (int i = 0; i < buffer.getNumSamples(); ++i)
2077+
for (int i = 0; i < numSamples; ++i)
20972078
busL[b][i] = sanitiseSample (busL[b][i]);
20982079
if (busR[b])
2099-
for (int i = 0; i < buffer.getNumSamples(); ++i)
2080+
for (int i = 0; i < numSamples; ++i)
21002081
busR[b][i] = sanitiseSample (busR[b][i]);
21012082
}
21022083
}

src/audio/VoicePool.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ VoicePool::VoicePool()
210210
updateBungeePingPongFadeLength();
211211
}
212212

213+
void VoicePool::prepareToPlay (double sr, int maxBlockSize)
214+
{
215+
setSampleRate (sr);
216+
const int scratchSize = juce::jmax (1, maxBlockSize);
217+
scratchL.resize ((size_t) scratchSize, 0.0f);
218+
scratchR.resize ((size_t) scratchSize, 0.0f);
219+
}
220+
213221
void VoicePool::setSampleRate (double sr)
214222
{
215223
sampleRate = sr;
@@ -1110,6 +1118,87 @@ void VoicePool::processSample (const SampleData& sample, double sr,
11101118
}
11111119
}
11121120

1121+
void VoicePool::renderMainBusBlock (const SampleData& sample,
1122+
float* destL, float* destR, int numSamples)
1123+
{
1124+
// Zero destination
1125+
if (destL) std::fill_n (destL, numSamples, 0.0f);
1126+
if (destR) std::fill_n (destR, numSamples, 0.0f);
1127+
1128+
auto renderVoiceBlock = [&] (int vi)
1129+
{
1130+
for (int s = 0; s < numSamples; ++s)
1131+
{
1132+
float vL = 0.0f, vR = 0.0f;
1133+
processVoiceSample (vi, sample, sampleRate, vL, vR);
1134+
if (destL) destL[s] += vL;
1135+
if (destR) destR[s] += vR;
1136+
}
1137+
};
1138+
1139+
for (int vi = 0; vi < maxActive; ++vi)
1140+
{
1141+
if (voices[vi].active)
1142+
renderVoiceBlock (vi);
1143+
}
1144+
1145+
// Preview voice (LazyChopEngine / shift preview) — always on main bus
1146+
constexpr int previewIdx = kPreviewVoiceIndex;
1147+
if (previewIdx >= maxActive && voices[previewIdx].active)
1148+
renderVoiceBlock (previewIdx);
1149+
}
1150+
1151+
void VoicePool::renderRoutedBlock (const SampleData& sample,
1152+
float* busL[], float* busR[], int numBuses, int numSamples)
1153+
{
1154+
const int scratchSize = (int) std::min (scratchL.size(), scratchR.size());
1155+
jassert (scratchSize > 0);
1156+
if (scratchSize <= 0 || numSamples <= 0)
1157+
return;
1158+
1159+
auto renderVoiceToScratch = [&] (int vi, int chunkSamples)
1160+
{
1161+
for (int s = 0; s < chunkSamples; ++s)
1162+
processVoiceSample (vi, sample, sampleRate, scratchL[s], scratchR[s]);
1163+
};
1164+
1165+
auto accumulateScratchToBus = [&] (float* dstL, float* dstR, int chunkStart, int chunkSamples)
1166+
{
1167+
for (int s = 0; s < chunkSamples; ++s)
1168+
{
1169+
if (dstL) dstL[chunkStart + s] += scratchL[s];
1170+
if (dstR) dstR[chunkStart + s] += scratchR[s];
1171+
}
1172+
};
1173+
1174+
for (int chunkStart = 0; chunkStart < numSamples; chunkStart += scratchSize)
1175+
{
1176+
const int chunkSamples = std::min (scratchSize, numSamples - chunkStart);
1177+
1178+
for (int vi = 0; vi < maxActive; ++vi)
1179+
{
1180+
if (! voices[vi].active)
1181+
continue;
1182+
1183+
renderVoiceToScratch (vi, chunkSamples);
1184+
1185+
int bus = voices[vi].outputBus;
1186+
if (bus < 0 || bus >= numBuses || busL[bus] == nullptr)
1187+
bus = 0;
1188+
1189+
accumulateScratchToBus (busL[bus], busR[bus], chunkStart, chunkSamples);
1190+
}
1191+
1192+
// Preview voice — always to bus 0
1193+
constexpr int previewIdx = kPreviewVoiceIndex;
1194+
if (previewIdx >= maxActive && voices[previewIdx].active)
1195+
{
1196+
renderVoiceToScratch (previewIdx, chunkSamples);
1197+
accumulateScratchToBus (busL[0], busR[0], chunkStart, chunkSamples);
1198+
}
1199+
}
1200+
}
1201+
11131202
void VoicePool::startShiftPreview (int startSample, int bufferSize,
11141203
const PreviewStretchParams& p)
11151204
{

src/audio/VoicePool.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ class VoicePool
8888
void processSample (const SampleData& sample, double sampleRate,
8989
float& outL, float& outR);
9090

91+
// Block-based render APIs — preferred entry points from processBlock()
92+
void renderMainBusBlock (const SampleData& sample,
93+
float* destL, float* destR, int numSamples);
94+
void renderRoutedBlock (const SampleData& sample,
95+
float* busL[], float* busR[], int numBuses, int numSamples);
96+
97+
void prepareToPlay (double sampleRate, int maxBlockSize);
9198
void setSampleRate (double sr);
9299
double getSampleRate() const { return sampleRate; }
93100

@@ -134,4 +141,8 @@ class VoicePool
134141
std::array<Voice, kMaxVoices> voices;
135142
int maxActive = 16; // playable voices, excluding preview voice
136143
double sampleRate = 44100.0;
144+
145+
// Preallocated scratch buffers for block rendering (sized to maxBlockSize)
146+
std::vector<float> scratchL;
147+
std::vector<float> scratchR;
137148
};

0 commit comments

Comments
 (0)