Remove global oversampling (#7228)

Oversampling can have many different effects to the audio signal such as latency, phase issues, clipping, smearing, etc, so this should really be an option on a per-plugin basis, not globally across all of LMMS (which, in some places, shouldn't really need to oversample at all but were oversampled anyways).
This commit is contained in:
saker
2024-05-05 04:37:43 -04:00
committed by GitHub
parent 9bdc8adf33
commit 9b6e33aa5c
75 changed files with 155 additions and 373 deletions

View File

@@ -83,7 +83,7 @@ AudioEngine::AudioEngine( bool renderOnly ) :
m_workers(),
m_numWorkers( QThread::idealThreadCount()-1 ),
m_newPlayHandles( PlayHandle::MaxNumber ),
m_qualitySettings( qualitySettings::Mode::Draft ),
m_qualitySettings(qualitySettings::Interpolation::Linear),
m_masterGain( 1.0f ),
m_audioDev( nullptr ),
m_oldAudioDev( nullptr ),
@@ -277,17 +277,6 @@ sample_rate_t AudioEngine::inputSampleRate() const
baseSampleRate();
}
sample_rate_t AudioEngine::processingSampleRate() const
{
return outputSampleRate() * m_qualitySettings.sampleRateMultiplier();
}
bool AudioEngine::criticalXRuns() const
{
return cpuLoad() >= 99 && Engine::getSong()->isExporting() == false;
@@ -459,7 +448,7 @@ const surroundSampleFrame *AudioEngine::renderNextBuffer()
renderStageMix(); // STAGE 3: do master mix in mixer
s_renderingThread = false;
m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod);
m_profiler.finishPeriod(outputSampleRate(), m_framesPerPeriod);
return m_outputBufferRead;
}
@@ -597,7 +586,6 @@ void AudioEngine::changeQuality(const struct qualitySettings & qs)
stopProcessing();
m_qualitySettings = qs;
m_audioDev->applyQualitySettings();
emit sampleRateChanged();
emit qualitySettingsChanged();

View File

@@ -149,7 +149,7 @@ unsigned int Controller::runningFrames()
// Get position in seconds
float Controller::runningTime()
{
return runningFrames() / Engine::audioEngine()->processingSampleRate();
return runningFrames() / Engine::audioEngine()->outputSampleRate();
}

View File

@@ -146,7 +146,7 @@ float Engine::framesPerTick(sample_rate_t sampleRate)
void Engine::updateFramesPerTick()
{
s_framesPerTick = s_audioEngine->processingSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
s_framesPerTick = s_audioEngine->outputSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo();
}

View File

@@ -410,7 +410,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
QMutexLocker m(&m_paramMutex);
const float frames_per_env_seg = SECS_PER_ENV_SEGMENT *
Engine::audioEngine()->processingSampleRate();
Engine::audioEngine()->outputSampleRate();
// TODO: Remove the expKnobVals, time should be linear
const auto predelay_frames = static_cast<f_cnt_t>(frames_per_env_seg * expKnobVal(m_predelayModel.value()));
@@ -509,7 +509,7 @@ void EnvelopeAndLfoParameters::updateSampleVars()
const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION *
Engine::audioEngine()->processingSampleRate();
Engine::audioEngine()->outputSampleRate();
m_lfoPredelayFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *
expKnobVal( m_lfoPredelayModel.value() ) );
m_lfoAttackFrames = static_cast<f_cnt_t>( frames_per_lfo_oscillation *

View File

@@ -205,7 +205,7 @@ float Instrument::computeReleaseTimeMsByFrameCount(f_cnt_t frames) const
sample_rate_t Instrument::getSampleRate() const
{
return Engine::audioEngine()->processingSampleRate();
return Engine::audioEngine()->outputSampleRate();
}

View File

@@ -369,7 +369,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
const int total_range = range * cnphv.size();
// number of frames that every note should be played
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->processingSampleRate());
const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->outputSampleRate());
const auto gated_frames = (f_cnt_t)(m_arpGateModel.value() * arp_frames / 100.0f);
// used for calculating remaining frames for arp-note, we have to add

View File

@@ -158,7 +158,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer,
if( n->m_filter == nullptr )
{
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->processingSampleRate() );
n->m_filter = std::make_unique<BasicFilters<>>( Engine::audioEngine()->outputSampleRate() );
}
n->m_filter->setFilterType( static_cast<BasicFilters<>::FilterType>(m_filterModel.value()) );

View File

@@ -155,7 +155,7 @@ void LfoController::updatePhase()
void LfoController::updateDuration()
{
float newDurationF = Engine::audioEngine()->processingSampleRate() * m_speedModel.value();
float newDurationF = Engine::audioEngine()->outputSampleRate() * m_speedModel.value();
switch(m_multiplierModel.value() )
{

View File

@@ -79,7 +79,7 @@ Oscillator::Oscillator(const IntModel *wave_shape_model,
void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator)
{
if (m_freq >= Engine::audioEngine()->processingSampleRate() / 2)
if (m_freq >= Engine::audioEngine()->outputSampleRate() / 2)
{
BufferManager::clear(ab, frames);
return;
@@ -681,7 +681,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
m_subOsc->update( _ab, _frames, _chnl, true );
recalcPhase();
const float osc_coeff = m_freq * m_detuning_div_samplerate;
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->processingSampleRate();
const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->outputSampleRate();
for( fpp_t frame = 0; frame < _frames; ++frame )
{
@@ -697,7 +697,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames,
template<>
inline sample_t Oscillator::getSample<Oscillator::WaveShape::Sine>(const float sample)
{
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate();
const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->outputSampleRate();
if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ)
{

View File

@@ -80,7 +80,7 @@ void PeakController::updateValueBuffer()
{
if( m_coeffNeedsUpdate )
{
const float ratio = 44100.0f / Engine::audioEngine()->processingSampleRate();
const float ratio = 44100.0f / Engine::audioEngine()->outputSampleRate();
m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio );
m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio );
m_coeffNeedsUpdate = false;

View File

@@ -535,7 +535,7 @@ bool RemotePlugin::processMessage( const message & _m )
case IdSampleRateInformation:
reply = true;
reply_message.addInt( Engine::audioEngine()->processingSampleRate() );
reply_message.addInt( Engine::audioEngine()->outputSampleRate() );
break;
case IdBufferSizeInformation:

View File

@@ -34,7 +34,7 @@ namespace lmms
RingBuffer::RingBuffer( f_cnt_t size ) :
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
m_samplerate( Engine::audioEngine()->processingSampleRate() ),
m_samplerate( Engine::audioEngine()->outputSampleRate() ),
m_size( size + m_fpp )
{
m_buffer = new sampleFrame[ m_size ];
@@ -45,7 +45,7 @@ RingBuffer::RingBuffer( f_cnt_t size ) :
RingBuffer::RingBuffer( float size ) :
m_fpp( Engine::audioEngine()->framesPerPeriod() ),
m_samplerate( Engine::audioEngine()->processingSampleRate() )
m_samplerate( Engine::audioEngine()->outputSampleRate() )
{
m_size = msToFrames( size ) + m_fpp;
m_buffer = new sampleFrame[ m_size ];
@@ -307,9 +307,9 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset,
void RingBuffer::updateSamplerate()
{
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->processingSampleRate() ) / m_samplerate;
float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::audioEngine()->outputSampleRate() ) / m_samplerate;
m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
m_samplerate = Engine::audioEngine()->processingSampleRate();
m_samplerate = Engine::audioEngine()->outputSampleRate();
delete[] m_buffer;
m_buffer = new sampleFrame[ m_size ];
memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );

View File

@@ -124,7 +124,7 @@ bool Sample::play(sampleFrame* dst, PlaybackState* state, size_t numFrames, floa
const auto pastBounds = state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards);
if (loopMode == Loop::Off && pastBounds) { return false; }
const auto outputSampleRate = Engine::audioEngine()->processingSampleRate() * m_frequency / desiredFrequency;
const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / desiredFrequency;
const auto inputSampleRate = m_buffer->sampleRate();
const auto resampleRatio = outputSampleRate / inputSampleRate;
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];

View File

@@ -307,7 +307,7 @@ void SampleClip::loadSettings( const QDomElement & _this )
if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) )
{
auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() :
Engine::audioEngine()->processingSampleRate();
Engine::audioEngine()->outputSampleRate();
auto buffer = gui::SampleLoader::createBufferFromBase64(_this.attribute("data"), sampleRate);
m_sample = Sample(std::move(buffer));

View File

@@ -101,7 +101,7 @@ auto decodeSampleDS(const QString& audioFile) -> std::optional<SampleDecoder::Re
int_sample_t* dataPtr = nullptr;
auto ds = DrumSynth{};
const auto engineRate = Engine::audioEngine()->processingSampleRate();
const auto engineRate = Engine::audioEngine()->outputSampleRate();
const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate);
const auto data = std::unique_ptr<int_sample_t[]>{dataPtr}; // NOLINT, we have to use a C-style array here

View File

@@ -145,7 +145,7 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const
f_cnt_t SamplePlayHandle::totalFrames() const
{
return (m_sample->endFrame() - m_sample->startFrame()) *
(static_cast<float>(Engine::audioEngine()->processingSampleRate()) / m_sample->sampleRate());
(static_cast<float>(Engine::audioEngine()->outputSampleRate()) / m_sample->sampleRate());
}

View File

@@ -155,7 +155,7 @@ void VstSyncController::updateSampleRate()
{
if (!m_syncData) { return; }
m_syncData->m_sampleRate = Engine::audioEngine()->processingSampleRate();
m_syncData->m_sampleRate = Engine::audioEngine()->outputSampleRate();
#ifdef VST_SNC_LATENCY
m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 );

View File

@@ -34,18 +34,11 @@ namespace lmms
AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) :
m_supportsCapture( false ),
m_sampleRate( _audioEngine->processingSampleRate() ),
m_sampleRate( _audioEngine->outputSampleRate() ),
m_channels( _channels ),
m_audioEngine( _audioEngine ),
m_buffer( new surroundSampleFrame[audioEngine()->framesPerPeriod()] )
{
int error;
if( ( m_srcState = src_new(
audioEngine()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == nullptr )
{
printf( "Error: src_new() failed in audio_device.cpp!\n" );
}
}
@@ -53,9 +46,7 @@ AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine )
AudioDevice::~AudioDevice()
{
src_delete( m_srcState );
delete[] m_buffer;
m_devMutex.tryLock();
unlock();
}
@@ -73,39 +64,16 @@ void AudioDevice::processNextBuffer()
}
}
fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab )
fpp_t AudioDevice::getNextBuffer(surroundSampleFrame* _ab)
{
fpp_t frames = audioEngine()->framesPerPeriod();
const surroundSampleFrame * b = audioEngine()->nextBuffer();
if( !b )
{
return 0;
}
// make sure, no other thread is accessing device
lock();
const surroundSampleFrame* b = audioEngine()->nextBuffer();
if (!b) { return 0; }
// resample if necessary
if( audioEngine()->processingSampleRate() != m_sampleRate )
{
frames = resample( b, frames, _ab, audioEngine()->processingSampleRate(), m_sampleRate );
}
else
{
memcpy( _ab, b, frames * sizeof( surroundSampleFrame ) );
}
// release lock
unlock();
if( audioEngine()->hasFifoWriter() )
{
delete[] b;
}
memcpy(_ab, b, frames * sizeof(surroundSampleFrame));
if (audioEngine()->hasFifoWriter()) { delete[] b; }
return frames;
}
@@ -141,23 +109,6 @@ void AudioDevice::stopProcessingThread( QThread * thread )
void AudioDevice::applyQualitySettings()
{
src_delete( m_srcState );
int error;
if( ( m_srcState = src_new(
audioEngine()->currentQualitySettings().libsrcInterpolation(),
SURROUND_CHANNELS, &error ) ) == nullptr )
{
printf( "Error: src_new() failed in audio_device.cpp!\n" );
}
}
void AudioDevice::registerPort( AudioPort * )
{
}
@@ -176,35 +127,6 @@ void AudioDevice::renamePort( AudioPort * )
{
}
fpp_t AudioDevice::resample( const surroundSampleFrame * _src,
const fpp_t _frames,
surroundSampleFrame * _dst,
const sample_rate_t _src_sr,
const sample_rate_t _dst_sr )
{
if( m_srcState == nullptr )
{
return _frames;
}
m_srcData.input_frames = _frames;
m_srcData.output_frames = _frames;
m_srcData.data_in = const_cast<float*>(_src[0].data());
m_srcData.data_out = _dst[0].data ();
m_srcData.src_ratio = (double) _dst_sr / _src_sr;
m_srcData.end_of_input = 0;
if (int error = src_process(m_srcState, &m_srcData))
{
printf( "AudioDevice::resample(): error while resampling: %s\n",
src_strerror( error ) );
}
return static_cast<fpp_t>(m_srcData.output_frames_gen);
}
int AudioDevice::convertToS16( const surroundSampleFrame * _ab,
const fpp_t _frames,
int_sample_t * _output_buffer,

View File

@@ -439,7 +439,7 @@ void Lv2Proc::initPlugin()
m_features.createFeatureVectors();
m_instance = lilv_plugin_instantiate(m_plugin,
Engine::audioEngine()->processingSampleRate(),
Engine::audioEngine()->outputSampleRate(),
m_features.featurePointers());
if (m_instance)
@@ -507,7 +507,7 @@ void Lv2Proc::initMOptions()
re-initialize, and this code section will be
executed again, creating a new option vector.
*/
float sampleRate = Engine::audioEngine()->processingSampleRate();
float sampleRate = Engine::audioEngine()->outputSampleRate();
int32_t blockLength = Engine::audioEngine()->framesPerPeriod();
int32_t sequenceSize = defaultEvbufSize();
@@ -568,7 +568,7 @@ void Lv2Proc::createPort(std::size_t portNum)
{
AutoLilvNode node(lilv_port_get_name(m_plugin, lilvPort));
QString dispName = lilv_node_as_string(node.get());
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
if(meta.def() < meta.min(sr) || meta.def() > meta.max(sr))
{
qWarning() << "Warning: Plugin"
@@ -871,7 +871,7 @@ void Lv2Proc::dumpPort(std::size_t num)
qDebug() << " visualization: " << Lv2Ports::toStr(port.m_vis);
if (port.m_type == Lv2Ports::Type::Control || port.m_type == Lv2Ports::Type::Cv)
{
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
qDebug() << " default:" << port.def();
qDebug() << " min:" << port.min(sr);
qDebug() << " max:" << port.max(sr);

View File

@@ -210,7 +210,6 @@ void printHelp()
" -p, --profile <out> Dump profiling information to file <out>\n"
" -s, --samplerate <samplerate> Specify output samplerate in Hz\n"
" Range: 44100 (default) to 192000\n"
" -x, --oversampling <value> Specify oversampling\n"
" Possible values: 1, 2, 4, 8\n"
" Default: 2\n\n",
LMMS_VERSION, LMMS_PROJECT_COPYRIGHT );
@@ -361,7 +360,7 @@ int main( int argc, char * * argv )
new QCoreApplication( argc, argv ) :
new gui::MainApplication(argc, argv);
AudioEngine::qualitySettings qs( AudioEngine::qualitySettings::Mode::HighQuality );
AudioEngine::qualitySettings qs(AudioEngine::qualitySettings::Interpolation::Linear);
OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo );
ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave;
@@ -646,36 +645,6 @@ int main( int argc, char * * argv )
return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) );
}
}
else if( arg == "--oversampling" || arg == "-x" )
{
++i;
if( i == argc )
{
return usageError( "No oversampling specified" );
}
int o = QString( argv[i] ).toUInt();
switch( o )
{
case 1:
qs.oversampling = AudioEngine::qualitySettings::Oversampling::None;
break;
case 2:
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X2;
break;
case 4:
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X4;
break;
case 8:
qs.oversampling = AudioEngine::qualitySettings::Oversampling::X8;
break;
default:
return usageError( QString( "Invalid oversampling %1" ).arg( argv[i] ) );
}
}
else if( arg == "--import" )
{
++i;

View File

@@ -74,7 +74,7 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) :
break;
case PortVis::Integer:
{
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
sample_rate_t sr = Engine::audioEngine()->outputSampleRate();
auto pMin = port.min(sr);
auto pMax = port.max(sr);
int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax));

View File

@@ -154,11 +154,8 @@ OutputSettings::StereoMode mapToStereoMode(int index)
void ExportProjectDialog::startExport()
{
AudioEngine::qualitySettings qs =
AudioEngine::qualitySettings(
static_cast<AudioEngine::qualitySettings::Interpolation>(interpolationCB->currentIndex()),
static_cast<AudioEngine::qualitySettings::Oversampling>(oversamplingCB->currentIndex()) );
auto qs = AudioEngine::qualitySettings(
static_cast<AudioEngine::qualitySettings::Interpolation>(interpolationCB->currentIndex()));
const auto samplerates = std::array{44100, 48000, 88200, 96000, 192000};
const auto bitrates = std::array{64, 128, 160, 192, 256, 320};

View File

@@ -1223,7 +1223,7 @@ void SetupDialog::setBufferSize(int value)
m_bufferSize = value * BUFFERSIZE_RESOLUTION;
m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg(
1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1));
1000.0f * m_bufferSize / Engine::audioEngine()->outputSampleRate(), 0, 'f', 1));
updateBufferSizeWarning(m_bufferSize);
}

View File

@@ -404,37 +404,6 @@
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Oversampling:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="oversamplingCB">
<item>
<property name="text">
<string>1x (None)</string>
</property>
</item>
<item>
<property name="text">
<string>2x</string>
</property>
</item>
<item>
<property name="text">
<string>4x</string>
</property>
</item>
<item>
<property name="text">
<string>8x</string>
</property>
</item>
</widget>
</item>
<item>
<spacer>
<property name="orientation">