Skip to content

Commit

Permalink
Avoid a startup transient due to denorm noise
Browse files Browse the repository at this point in the history
Slight denormalization noise in the warmup phase of some
aurwindows (esp DeRez) would cause a pop. Avoid that by having
either the first 500 blocks (~1/3 second) or time until first note
whichever is smaller clear the output with zeros, then add the
noise after

Closes surge-synthesizer#4900
  • Loading branch information
baconpaul committed Dec 8, 2021
1 parent 91fa768 commit 44da07e
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 10 deletions.
47 changes: 37 additions & 10 deletions src/common/SurgeSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@ void SurgeSynthesizer::playNote(char channel, char key, char velocity, char detu
}
}

/*
* Force the denormalizer to clear with noise even if we are still waiting, since we have a
* voice
*/
blocks_since_unhalt = blocks_until_first_denorm_noise + 1;
midiNoteEvents++;

if (!storage.isStandardTuning)
Expand Down Expand Up @@ -3665,6 +3670,11 @@ void SurgeSynthesizer::process()
// spawn patch-loading thread
allNotesOff();
halt_engine = true;
/*
* Resetting this here means that the next time we process we will start with
* pure clear, as per #4900
*/
blocks_since_unhalt = 0;

#if MAC || LINUX
pthread_t thread;
Expand Down Expand Up @@ -3703,6 +3713,23 @@ void SurgeSynthesizer::process()
}
}

/*
* See the discussion in #4900. The AirWindows effect "derez" in its warmup stage deals
* very badly with the denormal noise we use to initialize our blocks. But those denormal
* noises are important for the operating of the synth when we have voices playing.
*
* The somewhat pragmatic (aka hacky) solution to that is to clear the blocks fully at the
* outset of a patch load for the first few blocks, then swap back to the denorm version. But
* also if we play a voice, we want to start the noise version (which is done in playNote
* above).
*/
auto clearfunc = clear_block_antidenormalnoise;
if (blocks_since_unhalt < blocks_until_first_denorm_noise)
{
clearfunc = clear_block;
blocks_since_unhalt++;
}

// process inputs (upsample & halfrate)
if (process_input)
{
Expand All @@ -3714,26 +3741,26 @@ void SurgeSynthesizer::process()
}
else
{
clear_block_antidenormalnoise(storage.audio_in[0], BLOCK_SIZE_OS_QUAD);
clear_block_antidenormalnoise(storage.audio_in[1], BLOCK_SIZE_OS_QUAD);
clear_block_antidenormalnoise(storage.audio_in_nonOS[1], BLOCK_SIZE_QUAD);
clear_block_antidenormalnoise(storage.audio_in_nonOS[1], BLOCK_SIZE_QUAD);
clearfunc(storage.audio_in[0], BLOCK_SIZE_OS_QUAD);
clearfunc(storage.audio_in[1], BLOCK_SIZE_OS_QUAD);
clearfunc(storage.audio_in_nonOS[1], BLOCK_SIZE_QUAD);
clearfunc(storage.audio_in_nonOS[1], BLOCK_SIZE_QUAD);
}

// TODO: FIX SCENE ASSUMPTION
float fxsendout alignas(16)[n_send_slots][2][BLOCK_SIZE];
bool play_scene[n_scenes];

{
clear_block_antidenormalnoise(sceneout[0][0], BLOCK_SIZE_OS_QUAD);
clear_block_antidenormalnoise(sceneout[0][1], BLOCK_SIZE_OS_QUAD);
clear_block_antidenormalnoise(sceneout[1][0], BLOCK_SIZE_OS_QUAD);
clear_block_antidenormalnoise(sceneout[1][1], BLOCK_SIZE_OS_QUAD);
clearfunc(sceneout[0][0], BLOCK_SIZE_OS_QUAD);
clearfunc(sceneout[0][1], BLOCK_SIZE_OS_QUAD);
clearfunc(sceneout[1][0], BLOCK_SIZE_OS_QUAD);
clearfunc(sceneout[1][1], BLOCK_SIZE_OS_QUAD);

for (int i = 0; i < n_send_slots; ++i)
{
clear_block_antidenormalnoise(fxsendout[i][0], BLOCK_SIZE_QUAD);
clear_block_antidenormalnoise(fxsendout[i][1], BLOCK_SIZE_QUAD);
clearfunc(fxsendout[i][0], BLOCK_SIZE_QUAD);
clearfunc(fxsendout[i][1], BLOCK_SIZE_QUAD);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/common/SurgeSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ class alignas(16) SurgeSynthesizer
std::list<SurgeVoice *> voices[n_scenes];
std::unique_ptr<Effect> fx[n_fx_slots];
std::atomic<bool> halt_engine;
int blocks_since_unhalt{0};
static constexpr int blocks_until_first_denorm_noise{500};
MidiChannelState channelState[16];
bool mpeEnabled = false;
int mpeVoices = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/common/SurgeSynthesizerIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ void SurgeSynthesizer::processEnqueuedPatchIfNeeded()
void SurgeSynthesizer::loadRaw(const void *data, int size, bool preset)
{
halt_engine = true;
// See other comments in SS.cpp
blocks_since_unhalt = 0;

allNotesOff();
for (int s = 0; s < n_scenes; s++)
for (int i = 0; i < n_customcontrollers; i++)
Expand Down

0 comments on commit 44da07e

Please sign in to comment.