diff --git a/src/Config.h b/src/Config.h index edcd5560..2d238b01 100644 --- a/src/Config.h +++ b/src/Config.h @@ -30,7 +30,7 @@ #define VER_LAMEXP_MINOR_LO 4 #define VER_LAMEXP_TYPE Alpha #define VER_LAMEXP_PATCH 11 -#define VER_LAMEXP_BUILD 825 +#define VER_LAMEXP_BUILD 827 /////////////////////////////////////////////////////////////////////////////// // Tool versions (minimum expected versions!) diff --git a/src/Filter_Resample.cpp b/src/Filter_Resample.cpp index a36e42ef..929768e2 100644 --- a/src/Filter_Resample.cpp +++ b/src/Filter_Resample.cpp @@ -27,7 +27,12 @@ #include #include -ResampleFilter::ResampleFilter(int samplingRate) +static __inline int multipleOf(int value, int base) +{ + return qRound(static_cast(value) / static_cast(base)) * base; +} + +ResampleFilter::ResampleFilter(int samplingRate, int bitDepth) : m_binary(lamexp_lookup_tool("sox.exe")) { @@ -36,7 +41,13 @@ ResampleFilter::ResampleFilter(int samplingRate) throw "Error initializing SoX filter. Tool 'sox.exe' is not registred!"; } - m_samplingRate = qMin(192000, qMax(8000, samplingRate)); + m_samplingRate = (samplingRate > 0) ? qBound(8000, samplingRate, 192000) : 0; + m_bitDepth = (bitDepth > 0) ? qBound(8, multipleOf(bitDepth, 8), 32) : 0; + + if((m_samplingRate == 0) && (m_bitDepth == 0)) + { + qWarning("ResampleFilter: Nothing to do, filter will be NOP!"); + } } ResampleFilter::~ResampleFilter(void) @@ -53,9 +64,26 @@ bool ResampleFilter::apply(const QString &sourceFile, const QString &outputFile, args << "-V3" << "-S"; args << "--guard" << "--temp" << "."; args << QDir::toNativeSeparators(sourceFile); + + if(m_bitDepth) + { + args << "-b" << QString::number(m_bitDepth); + } + args << QDir::toNativeSeparators(outputFile); - args << "rate"; - args << "-h" << QString::number(m_samplingRate); + + if(m_samplingRate) + { + args << "rate"; + args << ((m_bitDepth > 16) ? "-v" : "-h"); //if resampling at/to > 16 bit depth (i.e. most commonly 24-bit), use VHQ (-v), otherwise, use HQ (-h) + args << ((m_samplingRate > 40000) ? "-L" : "-I"); //if resampling to < 40k, use intermediate phase (-I), otherwise use linear phase (-L) + args << QString::number(m_samplingRate); + } + + if((m_bitDepth || m_samplingRate) && (m_bitDepth <= 16)) + { + args << "dither" << "-s"; //if you're mastering to 16-bit, you also need to add 'dither' (and in most cases noise-shaping) after the rate + } if(!startProcess(process, m_binary, args)) { diff --git a/src/Filter_Resample.h b/src/Filter_Resample.h index d805fd37..87a82cb8 100644 --- a/src/Filter_Resample.h +++ b/src/Filter_Resample.h @@ -26,7 +26,7 @@ class ResampleFilter : public AbstractFilter { public: - ResampleFilter(int samplingRate = 44100); + ResampleFilter(int samplingRate = 0, int bitDepth = 0); ~ResampleFilter(void); virtual bool apply(const QString &sourceFile, const QString &outputFile, volatile bool *abortFlag); @@ -34,4 +34,5 @@ public: private: const QString m_binary; int m_samplingRate; + int m_bitDepth; }; diff --git a/src/Thread_Process.cpp b/src/Thread_Process.cpp index e7459115..dcc7166f 100644 --- a/src/Thread_Process.cpp +++ b/src/Thread_Process.cpp @@ -45,6 +45,7 @@ #include #define DIFF(X,Y) ((X > Y) ? (X-Y) : (Y-X)) +#define IS_WAVE(X) ((X.formatContainerType().compare("Wave", Qt::CaseInsensitive) == 0) && (X.formatAudioType().compare("PCM", Qt::CaseInsensitive) == 0)) #define STRDEF(STR,DEF) ((!STR.isEmpty()) ? STR : DEF) QMutex *ProcessThread::m_mutex_genFileName = NULL; @@ -158,6 +159,8 @@ void ProcessThread::processFile() { sourceFile = tempFile; handleMessage("\n-------------------------------\n"); + m_audioFile.setFormatContainerType(QString::fromLatin1("Wave")); + m_audioFile.setFormatAudioType(QString::fromLatin1("PCM")); } } else @@ -167,36 +170,32 @@ void ProcessThread::processFile() emit processStateFinished(m_jobId, outFileName, false); return; } + } - //Update audio properties after decode - if(bSuccess) + //------------------------------------ + //Update audio properties after decode + //------------------------------------ + if(bSuccess && IS_WAVE(m_audioFile)) + { + if(m_encoder->supportedSamplerates() || m_encoder->supportedBitdepths() || m_encoder->requiresDownmix()) { - if(m_encoder->supportedSamplerates() || m_encoder->supportedBitdepths() || m_encoder->requiresDownmix()) + m_currentStep = AnalyzeStep; + bSuccess = m_propDetect->detect(sourceFile, &m_audioFile, &m_aborted); + + if(bSuccess) { - m_currentStep = AnalyzeStep; - bSuccess = m_propDetect->detect(sourceFile, &m_audioFile, &m_aborted); + handleMessage("\n-------------------------------\n"); - if(bSuccess) + //Do we need to take care if Stereo downmix? + if(m_encoder->requiresDownmix()) { - handleMessage("\n-------------------------------\n"); + insertDownmixFilter(); + } - //Do we need to take care of downsampling the input? - if(m_encoder->supportedSamplerates()) - { - insertDownsampleFilter(); - } - - //Do we need to change the bits per sample of the input? - if(m_encoder->supportedBitdepths()) - { - insertBitdepthFilter(); - } - - //Do we need Stereo downmix? - if(m_encoder->requiresDownmix()) - { - insertDownmixFilter(); - } + //Do we need to take care of downsampling the input? + if(m_encoder->supportedSamplerates() || m_encoder->supportedBitdepths()) + { + insertDownsampleFilter(); } } } @@ -391,47 +390,81 @@ QString ProcessThread::generateTempFileName(void) void ProcessThread::insertDownsampleFilter(void) { - bool applyDownsampling = true; - - //Check if downsampling filter is already in the chain - for(int i = 0; i < m_filters.count(); i++) + int targetSampleRate = 0; + int targetBitDepth = 0; + + /* Adjust sample rate */ + if(m_encoder->supportedSamplerates() && m_audioFile.formatAudioSamplerate()) { - if(dynamic_cast(m_filters.at(i))) + bool applyDownsampling = true; + + //Check if downsampling filter is already in the chain + for(int i = 0; i < m_filters.count(); i++) { - qWarning("Encoder requires downsampling, but user has already set resamling filter!"); - applyDownsampling = false; + if(dynamic_cast(m_filters.at(i))) + { + qWarning("Encoder requires downsampling, but user has already set resamling filter!"); + handleMessage("WARNING: Encoder may need resampling, but already using resample filter. Encoding *may* fail!\n"); + applyDownsampling = false; + } + } + + //Now determine the target sample rate, if required + if(applyDownsampling) + { + const unsigned int *supportedRates = m_encoder->supportedSamplerates(); + const unsigned int inputRate = m_audioFile.formatAudioSamplerate(); + unsigned int currentDiff = UINT_MAX, minimumDiff = UINT_MAX, bestRate = UINT_MAX; + + //Find the most suitable supported sampling rate + for(int i = 0; supportedRates[i]; i++) + { + currentDiff = DIFF(inputRate, supportedRates[i]); + if(currentDiff < minimumDiff) + { + bestRate = supportedRates[i]; + minimumDiff = currentDiff; + if(!(minimumDiff > 0)) break; + } + } + + if(bestRate != inputRate) + { + targetSampleRate = (bestRate != UINT_MAX) ? bestRate : supportedRates[0]; + } } } - - //Now add the downsampling filter, if needed - if(applyDownsampling) - { - const unsigned int *supportedRates = m_encoder->supportedSamplerates(); - const unsigned int inputRate = m_audioFile.formatAudioSamplerate(); - unsigned int currentDiff = UINT_MAX, minimumDiff = UINT_MAX, bestRate = UINT_MAX; - //Find the most suitable supported sampling rate - for(int i = 0; supportedRates[i]; i++) + /* Adjust bit depth (word size) */ + if(m_encoder->supportedBitdepths() && m_audioFile.formatAudioBitdepth()) + { + const unsigned int *supportedBPS = m_encoder->supportedBitdepths(); + const unsigned int inputBPS = m_audioFile.formatAudioBitdepth(); + unsigned int currentDiff = UINT_MAX, minimumDiff = UINT_MAX, bestBPS = UINT_MAX; + + //Find the most suitable supported bit depth + for(int i = 0; supportedBPS[i]; i++) { - currentDiff = DIFF(inputRate, supportedRates[i]); + currentDiff = DIFF(inputBPS, supportedBPS[i]); if(currentDiff < minimumDiff) { - bestRate = supportedRates[i]; + bestBPS = supportedBPS[i]; minimumDiff = currentDiff; if(!(minimumDiff > 0)) break; } } - - if(bestRate != inputRate) + + if(bestBPS != inputBPS) { - m_filters.prepend(new ResampleFilter((bestRate != UINT_MAX) ? bestRate : supportedRates[0])); + targetBitDepth = (bestBPS != UINT_MAX) ? bestBPS : supportedBPS[0]; } } -} -void ProcessThread::insertBitdepthFilter(void) -{ - qFatal("ProcessThread::insertBitdepthFilter not implemented yet!"); + /* Insert the filter */ + if(targetSampleRate || targetBitDepth) + { + m_filters.append(new ResampleFilter(targetSampleRate, targetBitDepth)); + } } void ProcessThread::insertDownmixFilter(void) @@ -454,7 +487,7 @@ void ProcessThread::insertDownmixFilter(void) unsigned int channels = m_audioFile.formatAudioChannels(); if((channels == 0) || (channels > 2)) { - m_filters.prepend(new DownmixFilter()); + m_filters.append(new DownmixFilter()); } } } diff --git a/src/Thread_Process.h b/src/Thread_Process.h index 2af7dd96..9253331b 100644 --- a/src/Thread_Process.h +++ b/src/Thread_Process.h @@ -72,7 +72,6 @@ private: QString generateTempFileName(void); void insertDownsampleFilter(void); void insertDownmixFilter(void); - void insertBitdepthFilter(void); const QUuid m_jobId; AudioFileModel m_audioFile;