Skip to content

Commit 627a4d5

Browse files
committed
#38 Don't kill active voices when retuning.
1 parent 9c8d520 commit 627a4d5

File tree

3 files changed

+89
-46
lines changed

3 files changed

+89
-46
lines changed

Source/aeolus/engine.cpp

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -210,25 +210,9 @@ void EngineGlobal::rebuildRankwaves()
210210
rw->retunePipes(_scale, _tuningFrequency);
211211
}
212212

213-
// Kill all the active voices
214-
int numActiveVoices = 0;
215-
216-
do {
217-
for (auto* processor : _processors) {
218-
const auto numVoices{ processor->getNumberOfActiveVoices() };
219-
220-
if (numVoices > 0) {
221-
processor->killAllVoices();
222-
numActiveVoices += numVoices;
223-
}
224-
}
225-
226-
// Wait for the voices to release
227-
if (numActiveVoices > 0) {
228-
Thread::sleep(10);
229-
}
230-
231-
} while (numActiveVoices > 0);
213+
// @note We don't kill active voices - they will be using pipes from a parallel set.
214+
// However, switching tuning very fast (while keeping the voice sustained)
215+
// may result in voice to be killed.
232216

233217
updateStops(_sampleRate);
234218
}

Source/aeolus/rankwave.cpp

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,36 @@ Pipewave::Pipewave(Addsynth& model, int note, float freq)
3434
{
3535
}
3636

37+
Pipewave::Pipewave(Pipewave& other)
38+
: _model{ other._model }
39+
, _note{ other._note }
40+
, _freq{ other._freq }
41+
, _sampleRate{ other._sampleRate }
42+
, _needsToBeRebuilt{ other._needsToBeRebuilt.load() }
43+
, _attackLength{ other._attackLength }
44+
, _loopLength{ other._loopLength }
45+
, _sampleStep{ other._sampleStep }
46+
, _releaseLength{ other._releaseLength }
47+
, _releaseMultiplier{ other._releaseMultiplier }
48+
, _releaseDetune{ other._releaseDetune }
49+
, _instability{ other._instability }
50+
, _wavetable{ other._wavetable }
51+
, _attackStartPtr{}
52+
, _loopStartPtr{}
53+
, _loopEndPtr{}
54+
{
55+
// Adjust pointers to the copied wavetable.
56+
float* wavetable{ other._wavetable.data() };
57+
58+
const auto attackOffset = other._attackStartPtr - wavetable;
59+
const auto loopStartOffset = other._loopStartPtr - wavetable;
60+
const auto loopEndOffset = other._loopEndPtr - wavetable;
61+
62+
_attackStartPtr = _wavetable.data() + attackOffset;
63+
_loopStartPtr = _wavetable.data() + loopStartOffset;
64+
_loopEndPtr = _wavetable.data() + loopEndOffset;
65+
}
66+
3767
Pipewave::~Pipewave() = default;
3868

3969
float Pipewave::getPipeFrequency() const noexcept
@@ -79,6 +109,16 @@ void Pipewave::play(Pipewave::State& state, float* out)
79109
jassert(_loopStartPtr != nullptr);
80110
jassert(_loopEndPtr != nullptr);
81111

112+
if (_needsToBeRebuilt.load()) {
113+
// Drastic measures - pipe has been retuned while playing.
114+
// Data pointers may be invalid at this point - terminate the voice immediately.
115+
memset(out, 0, sizeof(float) * SUB_FRAME_LENGTH);
116+
state.env = Pipewave::Over;
117+
state.playPtr = nullptr;
118+
state.releasePtr = nullptr;
119+
return;
120+
}
121+
82122
float* p = state.playPtr;
83123
float* r = state.releasePtr;
84124

@@ -208,7 +248,7 @@ void Pipewave::genwave()
208248
float m = _model.getNoteAttack(_note);
209249

210250
for (int h = 0; h < N_HARM; ++h) {
211-
float t = _model.getHarmonicAttack(h, _note);
251+
const float t = _model.getHarmonicAttack(h, _note);
212252

213253
if (t > m)
214254
m = t;
@@ -254,38 +294,38 @@ void Pipewave::genwave()
254294
int wavetableLength = _attackLength + _loopLength + _sampleStep * (SUB_FRAME_LENGTH + 4);
255295
_wavetable.resize(wavetableLength);
256296

257-
std::vector<float> _arg(wavetableLength);
258-
std::vector<float> _att(wavetableLength);
297+
std::vector<float> arg(wavetableLength);
298+
std::vector<float> att(wavetableLength);
259299

260300
_attackStartPtr = _wavetable.data();
261301
_loopStartPtr = _attackStartPtr + _attackLength;
262302
_loopEndPtr = _loopStartPtr + _loopLength;
263303

264304
memset(_attackStartPtr, 0, sizeof(float) * _wavetable.size());
265305

266-
_releaseLength = (int)(ceilf (_model.getNoteRelease(_note) * _sampleRate / SUB_FRAME_LENGTH) + 1);
267-
_releaseMultiplier = 1.0f - powf (0.1f, 1.0f / _releaseLength);
268-
_releaseDetune = _sampleStep * (math::exp2ap (_model.getNoteReleaseDetune(_note) / 1200.0f) - 1.0f);
306+
_releaseLength = (int)(ceilf(_model.getNoteRelease(_note) * _sampleRate / SUB_FRAME_LENGTH) + 1);
307+
_releaseMultiplier = 1.0f - powf(0.1f, 1.0f / _releaseLength);
308+
_releaseDetune = _sampleStep * (math::exp2ap(_model.getNoteReleaseDetune(_note) / 1200.0f) - 1.0f);
269309
_instability = _model.getNoteInstability(_note);
270310

271311
int k = (int)(_sampleRate * _model.getNoteAttack(_note) + 0.5);
272312

273-
// _arg[i] will contain phase steps along the generated wavetable
313+
// arg[i] will contain phase steps along the generated wavetable
274314

275315
{
276316
float t = 0.0f;
277317

278318
// Interpolate from frequency f1 to f0 during the attack
279319
for (int i = 0; i <= _attackLength; ++i) {
280-
_arg [i] = t - floorf(t + 0.5f);
320+
arg [i] = t - floorf(t + 0.5f);
281321
t += (i < k) ? (((k - i) * f0 + i * f1) / k) : f1;
282322
}
283323
}
284324

285325
// Generate phase steps of the sustained loop
286326
for (int i = 1; i < _loopLength; ++i) {
287-
float t = _arg[_attackLength] + (float)i * nc / _loopLength;
288-
_arg[i + _attackLength] = t - floorf(t + 0.5f);
327+
float t = arg[_attackLength] + (float)i * nc / _loopLength;
328+
arg[i + _attackLength] = t - floorf(t + 0.5f);
289329
}
290330

291331
float v0 = math::exp2ap(0.1661f * _model.getNoteVolume(_note));
@@ -302,18 +342,18 @@ void Pipewave::genwave()
302342
v = v0 * math::exp2ap(0.1661f * (v + _model.getHarmonicRandomisation(h, _note) * (2.0f * rnd.nextFloat() - 1.0f)));
303343
k = (int)(_sampleRate * _model.getHarmonicAttack(h, _note) + 0.5f);
304344

305-
if (k > _att.size())
306-
_att.resize(k);
345+
if (k > att.size())
346+
att.resize(k);
307347

308-
attgain(_att.data(), k, _model.getHarmonicAttackProfile(h, _note));
348+
attgain(att.data(), k, _model.getHarmonicAttackProfile(h, _note));
309349

310350
for (int i = 0; i < _attackLength + _loopLength; ++i) {
311-
float t = _arg[i] * (h + 1);
351+
float t = arg[i] * (h + 1);
312352
t -= floorf(t);
313353
m = v * sinf(MathConstants<float>::twoPi * t);
314354

315355
if (i < k)
316-
m *= _att[i];
356+
m *= att[i];
317357

318358
_attackStartPtr[i] += m;
319359
}
@@ -408,8 +448,11 @@ Rankwave::Rankwave(Addsynth& model)
408448
}
409449

410450
void Rankwave::createPipes(const Scale& scale, float tuningFrequency)
411-
{
412-
_pipes.clear();
451+
{
452+
for (auto& p : _pipes)
453+
p.clear();
454+
455+
_pipeSetIndex = 0;
413456

414457
const auto fn = _model.getFn();
415458
const auto fd = _model.getFd();
@@ -418,9 +461,10 @@ void Rankwave::createPipes(const Scale& scale, float tuningFrequency)
418461
float fbase = tuningFrequency * fn / fd;
419462

420463
for (int i = _noteMin; i <= _noteMax; ++i) {
421-
auto pipe = std::make_unique<Pipewave>(_model, i - _noteMin, scale.getFrequencyForMidoNote(i, fbase));
422-
423-
_pipes.add(pipe.release());
464+
for (size_t j = 0; j < _pipes.size(); ++j) {
465+
auto pipe = std::make_unique<Pipewave>(_model, i - _noteMin, scale.getFrequencyForMidoNote(i, fbase));
466+
_pipes[j].add(pipe.release());
467+
}
424468
}
425469
}
426470

@@ -431,10 +475,13 @@ void Rankwave::retunePipes(const Scale& scale, float tuningFrequency)
431475
const float fnd = (float)_model.getFn() / (float)_model.getFd();
432476
jassert(fnd > 0.0f);
433477

478+
int pipeSetIndex{ _pipeSetIndex.load() };
479+
int nextPipeSetIndex{ (pipeSetIndex + 1) % (int)_pipes.size() };
480+
434481
if (g->isMTSEnabled()) {
435482
// Use MTS provided tuning
436483
for (int i = _noteMin; i <= _noteMax; ++i) {
437-
Pipewave* pipe = _pipes[i - _noteMin];
484+
Pipewave* pipe = _pipes[nextPipeSetIndex][i - _noteMin];
438485
const float f{ g->getMTSNoteToFrequency(i, 0) * fnd };
439486

440487
if (pipe->getPipeFrequency() != f) {
@@ -448,7 +495,7 @@ void Rankwave::retunePipes(const Scale& scale, float tuningFrequency)
448495
float fbase = tuningFrequency * fnd;
449496

450497
for (int i = _noteMin; i <= _noteMax; ++i) {
451-
Pipewave* pipe = _pipes[i - _noteMin];
498+
Pipewave* pipe = _pipes[nextPipeSetIndex][i - _noteMin];
452499
pipe->setFrequency(scale.getFrequencyForMidoNote(i, fbase));
453500
pipe->setNeedsToBeRebuilt(true);
454501
}
@@ -457,9 +504,14 @@ void Rankwave::retunePipes(const Scale& scale, float tuningFrequency)
457504

458505
void Rankwave::prepareToPlay(float sampleRate)
459506
{
460-
for (auto* pipe : _pipes) {
507+
int pipeSetIndex{ _pipeSetIndex.load() };
508+
int nextPipeSetIndex{ (pipeSetIndex + 1) % (int)_pipes.size() };
509+
510+
for (auto* pipe : _pipes[nextPipeSetIndex]) {
461511
pipe->prepateToPlay(sampleRate);
462512
}
513+
514+
_pipeSetIndex.store(nextPipeSetIndex);
463515
}
464516

465517
Pipewave::State Rankwave::trigger(int note)
@@ -469,12 +521,14 @@ Pipewave::State Rankwave::trigger(int note)
469521

470522
const int index = note - _noteMin;
471523

472-
if (!isPositiveAndBelow(index, _pipes.size())) {
524+
int pipeSetIndex{ _pipeSetIndex.load() };
525+
526+
if (!isPositiveAndBelow(index, _pipes[pipeSetIndex].size())) {
473527
jassertfalse;
474528
return {};
475529
}
476530

477-
Pipewave* pipe = _pipes[note - _noteMin];
531+
Pipewave* pipe = _pipes[pipeSetIndex][note - _noteMin];
478532
return pipe->trigger();
479533
}
480534

Source/aeolus/rankwave.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Pipewave final
7272

7373
Pipewave() = delete;
7474
Pipewave(Addsynth& model, int note, float freq);
75+
Pipewave(Pipewave& other);
7576
~Pipewave();
7677

7778
const Addsynth& getModel() const noexcept { return _model; }
@@ -138,7 +139,8 @@ class Rankwave
138139

139140
void createPipes(const Scale& scale, float tuningFreq);
140141

141-
// Recalculate pipes tuning based on the current global scale and A4 frequency.
142+
// Recalculate pipes tuning based on the current global scale and A4 frequency,
143+
// or global MTS tuning if enabed.
142144
void retunePipes(const Scale& scale, float tuningFreq);
143145

144146
juce::String getStopName() const { return _model.getStopName(); }
@@ -155,7 +157,10 @@ class Rankwave
155157
int _noteMin;
156158
int _noteMax;
157159

158-
juce::OwnedArray<Pipewave> _pipes;
160+
// Two sets of pipes to be able to switch between tunings
161+
// without releasing all the voices.
162+
std::array<juce::OwnedArray<Pipewave>, 2> _pipes;
163+
std::atomic<int> _pipeSetIndex{ 0 };
159164
};
160165

161166
AEOLUS_NAMESPACE_END

0 commit comments

Comments
 (0)