Fix audio resampling functionality (#7858)
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
This commit is contained in:
@@ -81,7 +81,6 @@ AudioEngine::AudioEngine( bool renderOnly ) :
|
||||
m_workers(),
|
||||
m_numWorkers( QThread::idealThreadCount()-1 ),
|
||||
m_newPlayHandles( PlayHandle::MaxNumber ),
|
||||
m_qualitySettings(qualitySettings::Interpolation::Linear),
|
||||
m_masterGain( 1.0f ),
|
||||
m_audioDev( nullptr ),
|
||||
m_oldAudioDev( nullptr ),
|
||||
@@ -464,25 +463,6 @@ void AudioEngine::clearInternal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::changeQuality(const struct qualitySettings & qs)
|
||||
{
|
||||
// don't delete the audio-device
|
||||
stopProcessing();
|
||||
|
||||
m_qualitySettings = qs;
|
||||
|
||||
emit sampleRateChanged();
|
||||
emit qualitySettingsChanged();
|
||||
|
||||
startProcessing();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::doSetAudioDevice( AudioDevice * _dev )
|
||||
{
|
||||
// TODO: Use shared_ptr here in the future.
|
||||
@@ -503,18 +483,10 @@ void AudioEngine::doSetAudioDevice( AudioDevice * _dev )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AudioEngine::setAudioDevice(AudioDevice * _dev,
|
||||
const struct qualitySettings & _qs,
|
||||
bool _needs_fifo,
|
||||
bool startNow)
|
||||
void AudioEngine::setAudioDevice(AudioDevice* _dev, bool _needs_fifo, bool startNow)
|
||||
{
|
||||
stopProcessing();
|
||||
|
||||
m_qualitySettings = _qs;
|
||||
|
||||
doSetAudioDevice( _dev );
|
||||
|
||||
emit qualitySettingsChanged();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* AudioResampler.cpp - wrapper for libsamplerate
|
||||
* AudioResampler.cpp
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
* Copyright (c) 2025 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -26,44 +26,77 @@
|
||||
|
||||
#include <samplerate.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
AudioResampler::AudioResampler(int interpolationMode, int channels)
|
||||
: m_interpolationMode(interpolationMode)
|
||||
, m_channels(channels)
|
||||
, m_state(src_new(interpolationMode, channels, &m_error))
|
||||
namespace {
|
||||
|
||||
constexpr auto converterType(AudioResampler::Mode mode) -> int
|
||||
{
|
||||
if (!m_state)
|
||||
switch (mode)
|
||||
{
|
||||
const auto errorMessage = std::string{src_strerror(m_error)};
|
||||
const auto fullMessage = std::string{"Failed to create an AudioResampler: "} + errorMessage;
|
||||
throw std::runtime_error{fullMessage};
|
||||
case AudioResampler::Mode::ZOH:
|
||||
return SRC_ZERO_ORDER_HOLD;
|
||||
case AudioResampler::Mode::Linear:
|
||||
return SRC_LINEAR;
|
||||
case AudioResampler::Mode::SincFastest:
|
||||
return SRC_SINC_FASTEST;
|
||||
case AudioResampler::Mode::SincMedium:
|
||||
return SRC_SINC_MEDIUM_QUALITY;
|
||||
case AudioResampler::Mode::SincBest:
|
||||
return SRC_SINC_BEST_QUALITY;
|
||||
default:
|
||||
throw std::invalid_argument{"Invalid interpolation mode"};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
AudioResampler::AudioResampler(Mode mode, ch_cnt_t channels)
|
||||
: m_state{src_new(converterType(mode), channels, &m_error)}
|
||||
, m_mode{mode}
|
||||
, m_channels{channels}
|
||||
{
|
||||
if (channels <= 0) { throw std::logic_error{"Invalid channel count"}; }
|
||||
if (!m_state) { throw std::runtime_error{src_strerror(m_error)}; }
|
||||
}
|
||||
|
||||
auto AudioResampler::process(InterleavedBufferView<const float> input, InterleavedBufferView<float> output) -> Result
|
||||
{
|
||||
if (input.channels() != m_channels || output.channels() != m_channels)
|
||||
{
|
||||
throw std::invalid_argument{"Invalid channel count"};
|
||||
}
|
||||
|
||||
auto data = SRC_DATA{};
|
||||
|
||||
data.data_in = input.data();
|
||||
data.input_frames = input.frames();
|
||||
|
||||
data.data_out = output.data();
|
||||
data.output_frames = output.frames();
|
||||
|
||||
data.src_ratio = m_ratio;
|
||||
data.end_of_input = 0;
|
||||
|
||||
if ((m_error = src_process(static_cast<SRC_STATE*>(m_state.get()), &data)))
|
||||
{
|
||||
throw std::runtime_error{src_strerror(m_error)};
|
||||
}
|
||||
|
||||
return {static_cast<f_cnt_t>(data.input_frames_used), static_cast<f_cnt_t>(data.output_frames_gen)};
|
||||
}
|
||||
|
||||
void AudioResampler::reset()
|
||||
{
|
||||
if ((m_error = src_reset(static_cast<SRC_STATE*>(m_state.get()))))
|
||||
{
|
||||
throw std::runtime_error{src_strerror(m_error)};
|
||||
}
|
||||
}
|
||||
|
||||
AudioResampler::~AudioResampler()
|
||||
void AudioResampler::StateDeleter::operator()(void* state)
|
||||
{
|
||||
src_delete(m_state);
|
||||
}
|
||||
|
||||
auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio)
|
||||
-> ProcessResult
|
||||
{
|
||||
auto data = SRC_DATA{};
|
||||
data.data_in = in;
|
||||
data.input_frames = inputFrames;
|
||||
data.data_out = out;
|
||||
data.output_frames = outputFrames;
|
||||
data.src_ratio = ratio;
|
||||
data.end_of_input = 0;
|
||||
return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen};
|
||||
}
|
||||
|
||||
void AudioResampler::setRatio(double ratio)
|
||||
{
|
||||
src_set_ratio(m_state, ratio);
|
||||
src_delete(static_cast<SRC_STATE*>(state));
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -52,31 +52,11 @@ Effect::Effect( const Plugin::Descriptor * _desc,
|
||||
{
|
||||
m_wetDryModel.setCenterValue(0);
|
||||
|
||||
m_srcState[0] = m_srcState[1] = nullptr;
|
||||
reinitSRC();
|
||||
|
||||
// Call the virtual method onEnabledChanged so that effects can react to changes,
|
||||
// e.g. by resetting state.
|
||||
connect(&m_enabledModel, &BoolModel::dataChanged, [this] { onEnabledChanged(); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Effect::~Effect()
|
||||
{
|
||||
for (const auto& state : m_srcState)
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
src_delete(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Effect::saveSettings( QDomDocument & _doc, QDomElement & _this )
|
||||
{
|
||||
m_enabledModel.saveSettings( _doc, _this, "on" );
|
||||
@@ -222,50 +202,4 @@ gui::PluginView * Effect::instantiateView( QWidget * _parent )
|
||||
return new gui::EffectView( this, _parent );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Effect::reinitSRC()
|
||||
{
|
||||
for (auto& state : m_srcState)
|
||||
{
|
||||
if (state != nullptr)
|
||||
{
|
||||
src_delete(state);
|
||||
}
|
||||
int error;
|
||||
const int currentInterpolation = Engine::audioEngine()->currentQualitySettings().libsrcInterpolation();
|
||||
if((state = src_new(currentInterpolation, DEFAULT_CHANNELS, &error)) == nullptr)
|
||||
{
|
||||
qFatal( "Error: src_new() failed in effect.cpp!\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Effect::resample( int _i, const SampleFrame* _src_buf,
|
||||
sample_rate_t _src_sr,
|
||||
SampleFrame* _dst_buf, sample_rate_t _dst_sr,
|
||||
f_cnt_t _frames )
|
||||
{
|
||||
if( m_srcState[_i] == nullptr )
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_srcData[_i].input_frames = _frames;
|
||||
m_srcData[_i].output_frames = Engine::audioEngine()->framesPerPeriod();
|
||||
m_srcData[_i].data_in = const_cast<float*>(_src_buf[0].data());
|
||||
m_srcData[_i].data_out = _dst_buf[0].data ();
|
||||
m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr;
|
||||
m_srcData[_i].end_of_input = 0;
|
||||
|
||||
if (int error = src_process(m_srcState[_i], &m_srcData[_i]))
|
||||
{
|
||||
qFatal( "Effect::resample(): error while resampling: %s\n",
|
||||
src_strerror( error ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -75,18 +75,12 @@ const std::array<ProjectRenderer::FileEncodeDevice, 5> ProjectRenderer::fileEnco
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ExportFileFormat exportFileFormat,
|
||||
const QString & outputFilename ) :
|
||||
QThread( Engine::audioEngine() ),
|
||||
m_fileDev( nullptr ),
|
||||
m_qualitySettings( qualitySettings ),
|
||||
m_progress( 0 ),
|
||||
m_abort( false )
|
||||
ProjectRenderer::ProjectRenderer(
|
||||
const OutputSettings& outputSettings, ExportFileFormat exportFileFormat, const QString& outputFilename)
|
||||
: QThread(Engine::audioEngine())
|
||||
, m_fileDev(nullptr)
|
||||
, m_progress(0)
|
||||
, m_abort(false)
|
||||
{
|
||||
AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[static_cast<std::size_t>(exportFileFormat)].m_getDevInst;
|
||||
|
||||
@@ -145,7 +139,7 @@ void ProjectRenderer::startProcessing()
|
||||
{
|
||||
// Have to do audio engine stuff with GUI-thread affinity in order to
|
||||
// make slots connected to sampleRateChanged()-signals being called immediately.
|
||||
Engine::audioEngine()->setAudioDevice( m_fileDev, m_qualitySettings, false, false );
|
||||
Engine::audioEngine()->setAudioDevice(m_fileDev, false, false);
|
||||
|
||||
start(
|
||||
#ifndef LMMS_BUILD_WIN32
|
||||
|
||||
@@ -34,17 +34,11 @@
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
RenderManager::RenderManager(
|
||||
const AudioEngine::qualitySettings & qualitySettings,
|
||||
const OutputSettings & outputSettings,
|
||||
ProjectRenderer::ExportFileFormat fmt,
|
||||
QString outputPath) :
|
||||
m_qualitySettings(qualitySettings),
|
||||
m_oldQualitySettings( Engine::audioEngine()->currentQualitySettings() ),
|
||||
m_outputSettings(outputSettings),
|
||||
m_format(fmt),
|
||||
m_outputPath(outputPath)
|
||||
const OutputSettings& outputSettings, ProjectRenderer::ExportFileFormat fmt, QString outputPath)
|
||||
: m_outputSettings(outputSettings)
|
||||
, m_format(fmt)
|
||||
, m_outputPath(outputPath)
|
||||
{
|
||||
Engine::audioEngine()->storeAudioDevice();
|
||||
}
|
||||
@@ -52,7 +46,6 @@ RenderManager::RenderManager(
|
||||
RenderManager::~RenderManager()
|
||||
{
|
||||
Engine::audioEngine()->restoreAudioDevice(); // Also deletes audio dev.
|
||||
Engine::audioEngine()->changeQuality( m_oldQualitySettings );
|
||||
}
|
||||
|
||||
void RenderManager::abortProcessing()
|
||||
@@ -141,11 +134,7 @@ void RenderManager::renderProject()
|
||||
|
||||
void RenderManager::render(QString outputPath)
|
||||
{
|
||||
m_activeRenderer = std::make_unique<ProjectRenderer>(
|
||||
m_qualitySettings,
|
||||
m_outputSettings,
|
||||
m_format,
|
||||
outputPath);
|
||||
m_activeRenderer = std::make_unique<ProjectRenderer>(m_outputSettings, m_format, outputPath);
|
||||
|
||||
if( m_activeRenderer->isReady() )
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Sample.cpp - State for container-class SampleBuffer
|
||||
* Sample.cpp
|
||||
*
|
||||
* Copyright (c) 2023 saker <sakertooth@gmail.com>
|
||||
* Copyright (c) 2025 saker <sakertooth@gmail.com>
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
@@ -24,10 +24,6 @@
|
||||
|
||||
#include "Sample.h"
|
||||
|
||||
#include "lmms_math.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
Sample::Sample(const QString& audioFile)
|
||||
@@ -78,7 +74,7 @@ Sample::Sample(const Sample& other)
|
||||
{
|
||||
}
|
||||
|
||||
Sample::Sample(Sample&& other)
|
||||
Sample::Sample(Sample&& other) noexcept
|
||||
: m_buffer(std::move(other.m_buffer))
|
||||
, m_startFrame(other.startFrame())
|
||||
, m_endFrame(other.endFrame())
|
||||
@@ -104,7 +100,7 @@ auto Sample::operator=(const Sample& other) -> Sample&
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Sample::operator=(Sample&& other) -> Sample&
|
||||
auto Sample::operator=(Sample&& other) noexcept -> Sample&
|
||||
{
|
||||
m_buffer = std::move(other.m_buffer);
|
||||
m_startFrame = other.startFrame();
|
||||
@@ -118,43 +114,81 @@ auto Sample::operator=(Sample&& other) -> Sample&
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const
|
||||
bool Sample::play(SampleFrame* dst, PlaybackState* state, size_t numFrames, Loop loop, double ratio) const
|
||||
{
|
||||
assert(numFrames > 0);
|
||||
assert(desiredFrequency > 0);
|
||||
|
||||
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()->outputSampleRate() * m_frequency / desiredFrequency;
|
||||
const auto inputSampleRate = m_buffer->sampleRate();
|
||||
const auto resampleRatio = outputSampleRate / inputSampleRate;
|
||||
const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()];
|
||||
|
||||
state->m_frameIndex = std::max<int>(m_startFrame, state->m_frameIndex);
|
||||
|
||||
auto playBuffer = std::vector<SampleFrame>(numFrames / resampleRatio + marginSize);
|
||||
playRaw(playBuffer.data(), playBuffer.size(), state, loopMode);
|
||||
const auto sampleRateRatio = static_cast<double>(Engine::audioEngine()->outputSampleRate()) / m_buffer->sampleRate();
|
||||
const auto freqRatio = frequency() / DefaultBaseFreq;
|
||||
state->m_resampler.setRatio(sampleRateRatio * freqRatio * ratio);
|
||||
|
||||
state->resampler().setRatio(resampleRatio);
|
||||
|
||||
const auto resampleResult
|
||||
= state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio);
|
||||
advance(state, resampleResult.inputFramesUsed, loopMode);
|
||||
|
||||
const auto outputFrames = static_cast<f_cnt_t>(resampleResult.outputFramesGenerated);
|
||||
if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, SampleFrame{}); }
|
||||
|
||||
if (!approximatelyEqual(m_amplification, 1.0f))
|
||||
// TODO: These kind of playback pipelines/graphs are repeated within other parts of the codebase that work with
|
||||
// audio samples. We should find a way to unify this but the right abstraction is not so clear yet.
|
||||
while (numFrames > 0)
|
||||
{
|
||||
for (auto i = std::size_t{0}; i < numFrames; ++i)
|
||||
if (state->m_bufferView.empty())
|
||||
{
|
||||
dst[i][0] *= m_amplification;
|
||||
dst[i][1] *= m_amplification;
|
||||
const auto rendered = render(state->m_buffer.data(), state->m_buffer.size(), state, loop);
|
||||
state->m_bufferView = {state->m_buffer.data(), rendered};
|
||||
}
|
||||
|
||||
const auto [inputFramesUsed, outputFramesGenerated] = state->m_resampler.process(
|
||||
{&state->m_bufferView.data()[0][0], 2, state->m_bufferView.size()}, {&dst[0][0], 2, numFrames});
|
||||
|
||||
if (inputFramesUsed == 0 && outputFramesGenerated == 0)
|
||||
{
|
||||
std::fill_n(dst, numFrames, SampleFrame{});
|
||||
break;
|
||||
}
|
||||
|
||||
state->m_bufferView = state->m_bufferView.subspan(inputFramesUsed);
|
||||
dst += outputFramesGenerated;
|
||||
numFrames -= outputFramesGenerated;
|
||||
}
|
||||
|
||||
return true;
|
||||
return numFrames < Engine::audioEngine()->framesPerPeriod();
|
||||
}
|
||||
|
||||
f_cnt_t Sample::render(SampleFrame* dst, f_cnt_t size, PlaybackState* state, Loop loop) const
|
||||
{
|
||||
for (f_cnt_t frame = 0; frame < size; ++frame)
|
||||
{
|
||||
switch (loop)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (state->m_frameIndex < 0 || state->m_frameIndex >= m_endFrame) { return frame; }
|
||||
break;
|
||||
case Loop::On:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame) { state->m_frameIndex = m_loopStartFrame; }
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame;
|
||||
state->m_backwards = false;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1;
|
||||
state->m_backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto value
|
||||
= m_buffer->data()[m_reversed ? m_buffer->size() - state->m_frameIndex - 1 : state->m_frameIndex]
|
||||
* m_amplification;
|
||||
dst[frame] = value;
|
||||
state->m_backwards ? --state->m_frameIndex : ++state->m_frameIndex;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
auto Sample::sampleDuration() const -> std::chrono::milliseconds
|
||||
@@ -172,82 +206,4 @@ void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame,
|
||||
setLoopEndFrame(loopEndFrame);
|
||||
}
|
||||
|
||||
void Sample::playRaw(SampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const
|
||||
{
|
||||
if (m_buffer->size() < 1) { return; }
|
||||
|
||||
auto index = state->m_frameIndex;
|
||||
auto backwards = state->m_backwards;
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i)
|
||||
{
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::Off:
|
||||
if (index < 0 || index >= m_endFrame) { return; }
|
||||
break;
|
||||
case Loop::On:
|
||||
if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; }
|
||||
else if (index >= m_loopEndFrame) { index = m_loopStartFrame; }
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (index < m_loopStartFrame && backwards)
|
||||
{
|
||||
index = m_loopStartFrame;
|
||||
backwards = false;
|
||||
}
|
||||
else if (index >= m_loopEndFrame)
|
||||
{
|
||||
index = m_loopEndFrame - 1;
|
||||
backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index];
|
||||
backwards ? --index : ++index;
|
||||
}
|
||||
}
|
||||
|
||||
void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const
|
||||
{
|
||||
state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount;
|
||||
if (loopMode == Loop::Off) { return; }
|
||||
|
||||
const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame);
|
||||
const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame);
|
||||
const auto loopSize = m_loopEndFrame - m_loopStartFrame;
|
||||
if (loopSize == 0) { return; }
|
||||
|
||||
switch (loopMode)
|
||||
{
|
||||
case Loop::On:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % loopSize;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % loopSize;
|
||||
}
|
||||
break;
|
||||
case Loop::PingPong:
|
||||
if (state->m_frameIndex < m_loopStartFrame && state->m_backwards)
|
||||
{
|
||||
state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize;
|
||||
state->m_backwards = false;
|
||||
}
|
||||
else if (state->m_frameIndex >= m_loopEndFrame)
|
||||
{
|
||||
state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize;
|
||||
state->m_backwards = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
@@ -114,7 +114,7 @@ void SamplePlayHandle::play( SampleFrame* buffer )
|
||||
m_volumeModel->value() / DefaultVolume } };*/
|
||||
// SamplePlayHandle always plays the sample at its original pitch;
|
||||
// it is used only for previews, SampleTracks and the metronome.
|
||||
if (!m_sample->play(workingBuffer, &m_state, frames, DefaultBaseFreq))
|
||||
if (!m_sample->play(workingBuffer, &m_state, frames))
|
||||
{
|
||||
zeroSampleFrames(workingBuffer, frames);
|
||||
}
|
||||
|
||||
@@ -190,12 +190,6 @@ void printHelp()
|
||||
" Default: 160.\n"
|
||||
" -f, --format <format> Specify format of render-output where\n"
|
||||
" Format is either 'wav', 'flac', 'ogg' or 'mp3'.\n"
|
||||
" -i, --interpolation <method> Specify interpolation method\n"
|
||||
" Possible values:\n"
|
||||
" - linear\n"
|
||||
" - sincfastest (default)\n"
|
||||
" - sincmedium\n"
|
||||
" - sincbest\n"
|
||||
" -l, --loop Render as a loop\n"
|
||||
" -m, --mode Stereo mode used for MP3 export\n"
|
||||
" Possible values: s, j, m\n"
|
||||
@@ -375,7 +369,6 @@ int main( int argc, char * * argv )
|
||||
new QCoreApplication( argc, argv ) :
|
||||
new gui::MainApplication(argc, argv);
|
||||
|
||||
AudioEngine::qualitySettings qs(AudioEngine::qualitySettings::Interpolation::Linear);
|
||||
OutputSettings os(44100, 160, OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo);
|
||||
ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave;
|
||||
|
||||
@@ -615,39 +608,6 @@ int main( int argc, char * * argv )
|
||||
{
|
||||
os.setBitDepth(OutputSettings::BitDepth::Depth32Bit);
|
||||
}
|
||||
else if( arg == "--interpolation" || arg == "-i" )
|
||||
{
|
||||
++i;
|
||||
|
||||
if( i == argc )
|
||||
{
|
||||
return usageError( "No interpolation method specified" );
|
||||
}
|
||||
|
||||
|
||||
const QString ip = QString( argv[i] );
|
||||
|
||||
if( ip == "linear" )
|
||||
{
|
||||
qs.interpolation = AudioEngine::qualitySettings::Interpolation::Linear;
|
||||
}
|
||||
else if( ip == "sincfastest" )
|
||||
{
|
||||
qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincFastest;
|
||||
}
|
||||
else if( ip == "sincmedium" )
|
||||
{
|
||||
qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincMedium;
|
||||
}
|
||||
else if( ip == "sincbest" )
|
||||
{
|
||||
qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincBest;
|
||||
}
|
||||
else
|
||||
{
|
||||
return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) );
|
||||
}
|
||||
}
|
||||
else if( arg == "--import" )
|
||||
{
|
||||
++i;
|
||||
@@ -776,7 +736,7 @@ int main( int argc, char * * argv )
|
||||
}
|
||||
|
||||
// create renderer
|
||||
auto r = new RenderManager(qs, os, eff, renderOut);
|
||||
auto r = new RenderManager(os, eff, renderOut);
|
||||
QCoreApplication::instance()->connect( r,
|
||||
SIGNAL(finished()), SLOT(quit()));
|
||||
|
||||
|
||||
@@ -162,8 +162,6 @@ OutputSettings::StereoMode mapToStereoMode(int index)
|
||||
|
||||
void ExportProjectDialog::startExport()
|
||||
{
|
||||
auto qs = AudioEngine::qualitySettings(
|
||||
static_cast<AudioEngine::qualitySettings::Interpolation>(interpolationCB->currentIndex()));
|
||||
const auto bitrates = std::array{64, 128, 160, 192, 256, 320};
|
||||
|
||||
OutputSettings os = OutputSettings(samplerateCB->currentData().toInt(), bitrates[bitrateCB->currentIndex()],
|
||||
@@ -183,7 +181,8 @@ void ExportProjectDialog::startExport()
|
||||
{
|
||||
output_name+=m_fileExtension;
|
||||
}
|
||||
m_renderManager.reset(new RenderManager( qs, os, m_ft, output_name ));
|
||||
|
||||
m_renderManager.reset(new RenderManager(os, m_ft, output_name));
|
||||
|
||||
Engine::getSong()->setExportLoop( exportLoopCB->isChecked() );
|
||||
Engine::getSong()->setRenderBetweenMarkers( renderMarkersCB->isChecked() );
|
||||
|
||||
@@ -331,62 +331,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="qualityGroupBox">
|
||||
<property name="title">
|
||||
<string>Quality settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Interpolation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="interpolationCB">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Zero order hold</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sinc worst (fastest)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sinc medium (recommended)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sinc best (slowest)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
Reference in New Issue
Block a user