diff --git a/src/analyserqueue.cpp b/src/analyserqueue.cpp index 08d45c940f15..6b1c3e47931d 100644 --- a/src/analyserqueue.cpp +++ b/src/analyserqueue.cpp @@ -177,8 +177,10 @@ bool AnalyserQueue::doAnalysis(TrackPointer tio, SoundSourceProxy* pSoundSource) dieflag = true; } + // Normalize the samples from [SHRT_MIN, SHRT_MAX] to [-1.0, 1.0]. + // TODO(rryan): Change the SoundSource API to do this for us. for (int i = 0; i < read; ++i) { - samples[i] = ((float)data16[i])/32767.0f; + samples[i] = static_cast(data16[i]) / SHRT_MAX; } QListIterator it(m_aq); diff --git a/src/cachingreaderworker.cpp b/src/cachingreaderworker.cpp index 83aad6c0c92f..4fe5bfae5bee 100644 --- a/src/cachingreaderworker.cpp +++ b/src/cachingreaderworker.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -85,6 +86,13 @@ void CachingReaderWorker::processChunkReadRequest(ChunkReadRequest* request, CSAMPLE* buffer = request->chunk->data; //qDebug() << "Reading into " << buffer; SampleUtil::convert(buffer, m_pSample, samples_read); + + // Normalize the samples from [SHRT_MIN, SHRT_MAX] to [-1.0, 1.0]. + // TODO(rryan): Change the SoundSource API to do this for us. + for (int i = 0; i < samples_read; ++i) { + buffer[i] /= SHRT_MAX; + } + update->status = CHUNK_READ_SUCCESS; update->chunk->length = samples_read; } diff --git a/src/encoder/encoderffmpegcore.cpp b/src/encoder/encoderffmpegcore.cpp index 85db4b1129de..69bd5e2b8a3c 100644 --- a/src/encoder/encoderffmpegcore.cpp +++ b/src/encoder/encoderffmpegcore.cpp @@ -137,12 +137,13 @@ void EncoderFfmpegCore::encodeBuffer(const CSAMPLE *samples, const int size) { unsigned int l_iBufPos = 0; unsigned int l_iPos = 0; + // TODO(XXX): Get rid of repeated malloc here! float *l_fNormalizedSamples = (float *)malloc(size * sizeof(float)); - // Because of normalization to SHORT_MAX = 0x7FFF we have to make this not to clip! - // Comments also: https://bugs.launchpad.net/mixxx/+bug/1204039 + // We use normalized floats in the engine [-1.0, 1.0] and FFMPEG expects + // samples in the range [-1.0, 1.0] so no conversion is required. for (j = 0; j < size; j++) { - l_fNormalizedSamples[j] = samples[j] / 0x7FFF; + l_fNormalizedSamples[j] = samples[j]; } // In MP3 this writes Header same In ogg diff --git a/src/encoder/encodermp3.cpp b/src/encoder/encodermp3.cpp index 4b3722cf6ea8..f983230de06a 100644 --- a/src/encoder/encodermp3.cpp +++ b/src/encoder/encodermp3.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "encoder/encodermp3.h" #include "encoder/encodercallback.h" @@ -282,18 +283,17 @@ void EncoderMp3::encodeBuffer(const CSAMPLE *samples, const int size) { return; int outsize = 0; int rc = 0; - int i = 0; outsize = (int)((1.25 * size + 7200) + 1); bufferOutGrow(outsize); bufferInGrow(size); - // Deinterleave samples - for (i = 0; i < size/2; ++i) - { - m_bufferIn[0][i] = samples[i*2]; - m_bufferIn[1][i] = samples[i*2+1]; + // Deinterleave samples. We use normalized floats in the engine [-1.0, 1.0] + // but LAME expects samples in the range [SHRT_MIN, SHRT_MAX]. + for (int i = 0; i < size/2; ++i) { + m_bufferIn[0][i] = samples[i*2] * SHRT_MAX; + m_bufferIn[1][i] = samples[i*2+1] * SHRT_MAX; } rc = lame_encode_buffer_float(m_lameFlags, m_bufferIn[0], m_bufferIn[1], @@ -362,4 +362,3 @@ void EncoderMp3::updateMetaData(char* artist, char* title, char* album){ m_metaDataArtist = artist; m_metaDataAlbum = album; } - diff --git a/src/encoder/encodervorbis.cpp b/src/encoder/encodervorbis.cpp index a2de180ce230..42c8f18d47b8 100644 --- a/src/encoder/encodervorbis.cpp +++ b/src/encoder/encodervorbis.cpp @@ -122,11 +122,12 @@ void EncoderVorbis::writePage() { void EncoderVorbis::encodeBuffer(const CSAMPLE *samples, const int size) { float **buffer = vorbis_analysis_buffer(&m_vdsp, size); - // Deinterleave samples - for (int i = 0; i < size/2; ++i) - { - buffer[0][i] = samples[i*2]/32768.f; - buffer[1][i] = samples[i*2+1]/32768.f; + // Deinterleave samples. We use normalized floats in the engine [-1.0, 1.0] + // and libvorbis expects samples in the range [-1.0, 1.0] so no conversion + // is required. + for (int i = 0; i < size/2; ++i) { + buffer[0][i] = samples[i*2]; + buffer[1][i] = samples[i*2+1]; } /** encodes audio **/ vorbis_analysis_wrote(&m_vdsp, size/2); diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 14fba3034d7e..5eb0db8220b9 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -102,9 +102,12 @@ EngineBuffer::EngineBuffer(const char* _group, ConfigObject* _confi m_iCrossFadeSamples(0), m_iLastBufferSize(0) { - // Generate dither values + // Generate dither values. When engine samples used to be within [SHRT_MIN, + // SHRT_MAX] dithering values were in the range [-0.5, 0.5]. Now that we + // normalize engine samples to the range [-1.0, 1.0] we divide by SHRT_MAX + // to preserve the previous behavior. for (int i = 0; i < MAX_BUFFER_LEN; ++i) { - m_pDitherBuffer[i] = static_cast(rand() % 32768) / 32768.0 - 0.5; + m_pDitherBuffer[i] = (static_cast(rand() % RAND_MAX) / RAND_MAX - 0.5) / SHRT_MAX; } // zero out crossfade buffer diff --git a/src/engine/engineclipping.cpp b/src/engine/engineclipping.cpp index 9725c29d31e1..d34f101bdf28 100644 --- a/src/engine/engineclipping.cpp +++ b/src/engine/engineclipping.cpp @@ -31,10 +31,8 @@ EngineClipping::~EngineClipping() delete m_ctrlClipping; } -void EngineClipping::process(const CSAMPLE* pIn, CSAMPLE* pOutput, const int iBufferSize) -{ - static const FLOAT_TYPE kfMaxAmp = 32767.; - // static const FLOAT_TYPE kfClip = 0.8*kfMaxAmp; +void EngineClipping::process(const CSAMPLE* pIn, CSAMPLE* pOutput, const int iBufferSize) { + const CSAMPLE kfMaxAmp = 1.0; // SampleUtil clamps the buffer and if pIn and pOut are aliased will not copy. clipped = SampleUtil::copyClampBuffer(kfMaxAmp, -kfMaxAmp, diff --git a/src/engine/enginedeck.cpp b/src/engine/enginedeck.cpp index 4327871fc82a..3f29327d61dd 100644 --- a/src/engine/enginedeck.cpp +++ b/src/engine/enginedeck.cpp @@ -129,7 +129,7 @@ bool EngineDeck::isActive() { return (m_pBuffer->isTrackLoaded() || isPassthroughActive()); } -void EngineDeck::receiveBuffer(AudioInput input, const short* pBuffer, unsigned int nFrames) { +void EngineDeck::receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, unsigned int nFrames) { // Skip receiving audio input if passthrough is not active if (!m_bPassthroughIsActive) { return; @@ -150,29 +150,36 @@ void EngineDeck::receiveBuffer(AudioInput input, const short* pBuffer, unsigned nFrames = MAX_BUFFER_LEN / iChannels; } + const CSAMPLE* pWriteBuffer = NULL; + unsigned int samplesToWrite = 0; + if (iChannels == 1) { - // Do mono -> stereo conversion. There isn't a suitable SampleUtil - // method that can do mono->stereo and short->float in one pass. + // Do mono -> stereo conversion. for (unsigned int i = 0; i < nFrames; ++i) { m_pConversionBuffer[i*2 + 0] = pBuffer[i]; m_pConversionBuffer[i*2 + 1] = pBuffer[i]; } + pWriteBuffer = m_pConversionBuffer; + samplesToWrite = nFrames * 2; } else if (iChannels == 2) { - // Use the conversion buffer to both convert from short and double. - SampleUtil::convert(m_pConversionBuffer, pBuffer, nFrames*iChannels); + // Already in stereo. Use pBuffer as-is. + pWriteBuffer = pBuffer; + samplesToWrite = nFrames * iChannels; } else { qWarning() << "EnginePassthrough got greater than stereo input. Not currently handled."; } - const unsigned int samplesToWrite = nFrames * iChannels; - - // TODO(rryan) do we need to verify the input is the one we asked for? Oh well. - unsigned int samplesWritten = m_sampleBuffer.write(m_pConversionBuffer, samplesToWrite); - if (samplesWritten < samplesToWrite) { - // Buffer overflow. We aren't processing samples fast enough. This - // shouldn't happen since the deck spits out samples just as fast as they - // come in, right? - qWarning() << "ERROR: Buffer overflow in EngineMicrophone. Dropping samples on the floor."; + if (pWriteBuffer != NULL) { + // TODO(rryan) do we need to verify the input is the one we asked for? + // Oh well. + unsigned int samplesWritten = m_sampleBuffer.write(pWriteBuffer, + samplesToWrite); + if (samplesWritten < samplesToWrite) { + // Buffer overflow. We aren't processing samples fast enough. This + // shouldn't happen since the deck spits out samples just as fast as + // they come in, right? + qWarning() << "ERROR: Buffer overflow in EngineMicrophone. Dropping samples on the floor."; + } } } diff --git a/src/engine/enginedeck.h b/src/engine/enginedeck.h index 5f2f84c712e7..783d0ae50fc5 100644 --- a/src/engine/enginedeck.h +++ b/src/engine/enginedeck.h @@ -54,7 +54,8 @@ class EngineDeck : public EngineChannel, public AudioDestination { // This is called by SoundManager whenever there are new samples from the // deck to be processed. - virtual void receiveBuffer(AudioInput input, const short *pBuffer, unsigned int nFrames); + virtual void receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int nFrames); // Called by SoundManager whenever the passthrough input is connected to a // soundcard input. diff --git a/src/engine/enginemicrophone.cpp b/src/engine/enginemicrophone.cpp index 8a9c79978905..abc6c9118c0f 100644 --- a/src/engine/enginemicrophone.cpp +++ b/src/engine/enginemicrophone.cpp @@ -64,43 +64,53 @@ void EngineMicrophone::onInputDisconnected(AudioInput input) { m_pEnabled->set(0.0f); } -void EngineMicrophone::receiveBuffer(AudioInput input, const short* pBuffer, unsigned int nFrames) { - - if (input.getType() != AudioPath::MICROPHONE || - AudioInput::channelsNeededForType(input.getType()) != 1) { +void EngineMicrophone::receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int nFrames) { + if (input.getType() != AudioPath::MICROPHONE) { // This is an error! qWarning() << "EngineMicrophone receieved an AudioInput for a non-Microphone type or a non-mono buffer!"; return; } - // Use the conversion buffer to both convert from short and double into - // stereo. + const unsigned int iChannels = AudioInput::channelsNeededForType(input.getType()); // Check that the number of mono frames doesn't exceed MAX_BUFFER_LEN/2 // because thats our conversion buffer size. - if (nFrames > MAX_BUFFER_LEN / 2) { + if (nFrames > MAX_BUFFER_LEN / iChannels) { qWarning() << "Dropping microphone samples because the input buffer is too large."; - nFrames = MAX_BUFFER_LEN / 2; + nFrames = MAX_BUFFER_LEN / iChannels; } - // There isn't a suitable SampleUtil method that can do mono->stereo and - // short->float in one pass. - // SampleUtil::convert(m_pConversionBuffer, pBuffer, iNumSamples); - for (unsigned int i = 0; i < nFrames; ++i) { - m_pConversionBuffer[i*2 + 0] = pBuffer[i]; - m_pConversionBuffer[i*2 + 1] = pBuffer[i]; - } + const CSAMPLE* pWriteBuffer = NULL; + unsigned int samplesToWrite = 0; - // m_pConversionBuffer is now stereo, so double the number of samples - const unsigned int iNumSamples = nFrames * 2; + if (iChannels == 1) { + // Do mono -> stereo conversion. + for (unsigned int i = 0; i < nFrames; ++i) { + m_pConversionBuffer[i*2 + 0] = pBuffer[i]; + m_pConversionBuffer[i*2 + 1] = pBuffer[i]; + } + pWriteBuffer = m_pConversionBuffer; + samplesToWrite = nFrames * 2; + } else if (iChannels == 2) { + // Already in stereo. Use pBuffer as-is. + pWriteBuffer = pBuffer; + samplesToWrite = nFrames * iChannels; + } else { + qWarning() << "EngineMicrophone got greater than stereo input. Not currently handled."; + } - // TODO(rryan) do we need to verify the input is the one we asked for? Oh well. - unsigned int samplesWritten = m_sampleBuffer.write(m_pConversionBuffer, iNumSamples); - if (samplesWritten < iNumSamples) { - // Buffer overflow. We aren't processing samples fast enough. This - // shouldn't happen since the mic spits out samples just as fast as they - // come in, right? - qWarning() << "ERROR: Buffer overflow in EngineMicrophone. Dropping samples on the floor."; + if (pWriteBuffer != NULL) { + // TODO(rryan) do we need to verify the input is the one we asked for? + // Oh well. + unsigned int samplesWritten = m_sampleBuffer.write(pWriteBuffer, + samplesToWrite); + if (samplesWritten < samplesToWrite) { + // Buffer overflow. We aren't processing samples fast enough. This + // shouldn't happen since the mic spits out samples just as fast as they + // come in, right? + qWarning() << "ERROR: Buffer overflow in EngineMicrophone. Dropping samples on the floor."; + } } } diff --git a/src/engine/enginemicrophone.h b/src/engine/enginemicrophone.h index 07feb4ba1974..9f25cd24aa37 100644 --- a/src/engine/enginemicrophone.h +++ b/src/engine/enginemicrophone.h @@ -28,7 +28,8 @@ class EngineMicrophone : public EngineChannel, public AudioDestination { // This is called by SoundManager whenever there are new samples from the // microphone to be processed - virtual void receiveBuffer(AudioInput input, const short* pBuffer, unsigned int iNumSamples); + virtual void receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int iNumSamples); // Called by SoundManager whenever the microphone input is connected to a // soundcard input. diff --git a/src/engine/enginepassthrough.cpp b/src/engine/enginepassthrough.cpp index d4e7889d9486..bc08475757db 100644 --- a/src/engine/enginepassthrough.cpp +++ b/src/engine/enginepassthrough.cpp @@ -62,34 +62,54 @@ void EnginePassthrough::onInputDisconnected(AudioInput input) { m_pEnabled->set(0.0f); } -void EnginePassthrough::receiveBuffer(AudioInput input, const short* pBuffer, unsigned int nFrames) { - +void EnginePassthrough::receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int nFrames) { if (input.getType() != AudioPath::EXTPASSTHROUGH) { // This is an error! qDebug() << "WARNING: EnginePassthrough receieved an AudioInput for a non-passthrough type!"; return; } - // Use the conversion buffer to both convert from short and double into - // stereo. + const unsigned int iChannels = AudioInput::channelsNeededForType(input.getType()); // Check that the number of mono frames doesn't exceed MAX_BUFFER_LEN/2 // because thats our conversion buffer size. - if (nFrames > MAX_BUFFER_LEN / 2) { + if (nFrames > MAX_BUFFER_LEN / iChannels) { qDebug() << "WARNING: Dropping passthrough samples because the input buffer is too large."; - nFrames = MAX_BUFFER_LEN / 2; + nFrames = MAX_BUFFER_LEN / iChannels; } - const unsigned int iNumSamples = nFrames * 2; - SampleUtil::convert(m_pConversionBuffer, pBuffer, iNumSamples); + const CSAMPLE* pWriteBuffer = NULL; + unsigned int samplesToWrite = 0; - // TODO(rryan) (or bkgood?) do we need to verify the input is the one we asked for? Oh well. - unsigned int samplesWritten = m_sampleBuffer.write(m_pConversionBuffer, iNumSamples); - if (samplesWritten < iNumSamples) { - // Buffer overflow. We aren't processing samples fast enough. This - // shouldn't happen since the deck spits out samples just as fast as they - // come in, right? - qWarning() << "ERROR: Buffer overflow in EnginePassthrough. Dropping samples on the floor."; + if (iChannels == 1) { + // Do mono -> stereo conversion. + for (unsigned int i = 0; i < nFrames; ++i) { + m_pConversionBuffer[i*2 + 0] = pBuffer[i]; + m_pConversionBuffer[i*2 + 1] = pBuffer[i]; + } + pWriteBuffer = m_pConversionBuffer; + samplesToWrite = nFrames * 2; + } else if (iChannels == 2) { + // Already in stereo. Use pBuffer as-is. + pWriteBuffer = pBuffer; + samplesToWrite = nFrames * iChannels; + } else { + qWarning() << "EnginePassthrough got greater than stereo input. Not currently handled."; + } + + + if (pWriteBuffer != NULL) { + // TODO(rryan) do we need to verify the input is the one we asked for? + // Oh well. + unsigned int samplesWritten = m_sampleBuffer.write(m_pConversionBuffer, + samplesToWrite); + if (samplesWritten < samplesToWrite) { + // Buffer overflow. We aren't processing samples fast enough. This + // shouldn't happen since the deck spits out samples just as fast as they + // come in, right? + qWarning() << "ERROR: Buffer overflow in EnginePassthrough. Dropping samples on the floor."; + } } } diff --git a/src/engine/enginepassthrough.h b/src/engine/enginepassthrough.h index 9fd81cf2977e..230f1ad92845 100644 --- a/src/engine/enginepassthrough.h +++ b/src/engine/enginepassthrough.h @@ -29,7 +29,8 @@ class EnginePassthrough : public EngineChannel, public AudioDestination { // This is called by SoundManager whenever there are new samples from the // deck to be processed - virtual void receiveBuffer(AudioInput input, const short *pBuffer, unsigned int nFrames); + virtual void receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int nFrames); // Called by SoundManager whenever the passthrough input is connected to a // soundcard input. diff --git a/src/engine/enginevinylsoundemu.cpp b/src/engine/enginevinylsoundemu.cpp index 16d00037243f..89275360dea8 100644 --- a/src/engine/enginevinylsoundemu.cpp +++ b/src/engine/enginevinylsoundemu.cpp @@ -27,55 +27,53 @@ * these slow speeds. */ -EngineVinylSoundEmu::EngineVinylSoundEmu(ConfigObject* pConfig, const char* group) -{ - m_pConfig = pConfig; - m_pRateEngine = ControlObject::getControl(ConfigKey(group, "rateEngine")); - m_fSpeed = m_fOldSpeed = 0.0f; - m_fGainFactor = 1.0f; - m_iNoisePos = 0; - - for (int i=0; i* pConfig, + const char* group) + : m_pConfig(pConfig), + m_pRateEngine(ControlObject::getControl( + ConfigKey(group, "rateEngine"))), + m_dSpeed(0.0), + m_dOldSpeed(0.0), + m_iNoisePos(0) { + // Generate dither values. When engine samples used to be within [SHRT_MIN, + // SHRT_MAX] dithering values were in the range [-0.5, 0.5]. Now that we + // normalize engine samples to the range [-1.0, 1.0] we divide by SHRT_MAX + // to preserve the previous behavior. + for (int i = 0; i < NOISE_BUFFER_SIZE; ++i) { + m_fNoise[i] = (static_cast(rand() % RAND_MAX) / RAND_MAX - 0.5) / SHRT_MAX; } } -EngineVinylSoundEmu::~EngineVinylSoundEmu() -{ - +EngineVinylSoundEmu::~EngineVinylSoundEmu() { } void EngineVinylSoundEmu::process(const CSAMPLE* pIn, CSAMPLE* pOutput, const int iBufferSize) { - m_fSpeed = (float)m_pRateEngine->get(); - float rateFrac = 2 * (m_fSpeed - m_fOldSpeed) / (float)iBufferSize; - float curRate = m_fOldSpeed; + m_dSpeed = m_pRateEngine->get(); + double rateFrac = 2.0 * (m_dSpeed - m_dOldSpeed) / iBufferSize; + double curRate = m_dOldSpeed; - const float thresholdSpeed = 0.070f; //Scale volume if playback speed is below 7%. - const float ditherSpeed = 0.85f; //Dither if playback speed is below 85%. + const double thresholdSpeed = 0.070; //Scale volume if playback speed is below 7%. + const double ditherSpeed = 0.85; //Dither if playback speed is below 85%. //iterate over old rate to new rate to prevent audible pops - for (int i=0; i* m_pConfig; ControlObject* m_pRateEngine; - float m_fSpeed, m_fOldSpeed; - float m_fGainFactor; - - float m_fNoise[NOISE_BUFFER_SIZE]; + double m_dSpeed; + double m_dOldSpeed; + CSAMPLE m_fNoise[NOISE_BUFFER_SIZE]; int m_iNoisePos; }; - #endif diff --git a/src/engine/enginevumeter.cpp b/src/engine/enginevumeter.cpp index eade65f06f03..1acfe07fb851 100644 --- a/src/engine/enginevumeter.cpp +++ b/src/engine/enginevumeter.cpp @@ -58,10 +58,9 @@ void EngineVuMeter::process(const CSAMPLE* pIn, CSAMPLE*, const int iBufferSize) m_iSamplesCalculated += iBufferSize/2; // Are we ready to update the VU meter?: - if (m_iSamplesCalculated > (44100/2/UPDATE_RATE) ) - { - doSmooth(m_fRMSvolumeL, log10(m_fRMSvolumeSumL/(m_iSamplesCalculated*1000)+1)); - doSmooth(m_fRMSvolumeR, log10(m_fRMSvolumeSumR/(m_iSamplesCalculated*1000)+1)); + if (m_iSamplesCalculated > (44100/2/UPDATE_RATE)) { + doSmooth(m_fRMSvolumeL, log10(SHRT_MAX * m_fRMSvolumeSumL/(m_iSamplesCalculated*1000)+1)); + doSmooth(m_fRMSvolumeR, log10(SHRT_MAX * m_fRMSvolumeSumR/(m_iSamplesCalculated*1000)+1)); const double epsilon = .0001; diff --git a/src/sounddeviceportaudio.cpp b/src/sounddeviceportaudio.cpp index cd62a2060581..329ce56f6cab 100644 --- a/src/sounddeviceportaudio.cpp +++ b/src/sounddeviceportaudio.cpp @@ -153,7 +153,7 @@ int SoundDevicePortAudio::open() { m_outputParams.hostApiSpecificStreamInfo = NULL; m_inputParams.device = m_devId; - m_inputParams.sampleFormat = paInt16; //This is how our vinyl control stuff like samples. + m_inputParams.sampleFormat = paFloat32; m_inputParams.suggestedLatency = bufferMSec / 1000.0; m_inputParams.hostApiSpecificStreamInfo = NULL; @@ -282,8 +282,9 @@ QString SoundDevicePortAudio::getError() const { -------- ------------------------------------------------------ */ int SoundDevicePortAudio::callbackProcess(unsigned long framesPerBuffer, - float *output, short *in, const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) { + float *output, float *in, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags) { ScopedTimer t("SoundDevicePortAudio::callbackProcess " + getInternalName()); //qDebug() << "SoundDevicePortAudio::callbackProcess:" << getInternalName(); @@ -317,20 +318,6 @@ int SoundDevicePortAudio::callbackProcess(unsigned long framesPerBuffer, // Send audio from the soundcard's input off to the SoundManager... if (in && framesPerBuffer > 0) { ScopedTimer t("SoundDevicePortAudio::callbackProcess input " + getInternalName()); - - //Apply software preamp - //Super big warning: Need to use channel_count here instead of iFrameSize because iFrameSize is - //only for output buffers... - // TODO(bkgood) move this to vcproxy or something, once we have other - // inputs we don't want every input getting the vc gain - static ControlObject* pControlObjectVinylControlGain = - ControlObject::getControl(ConfigKey(VINYL_PREF_KEY, "gain")); - int iVCGain = pControlObjectVinylControlGain->get(); - if (iVCGain > 1) { - for (unsigned int i = 0; i < framesPerBuffer * m_inputParams.channelCount; ++i) - in[i] *= iVCGain; - } - m_pSoundManager->pushBuffer(m_audioInputs, in, framesPerBuffer, m_inputParams.channelCount, this); } @@ -366,5 +353,5 @@ int paV19Callback(const void *inputBuffer, void *outputBuffer, PaStreamCallbackFlags statusFlags, void *soundDevice) { return ((SoundDevicePortAudio*) soundDevice)->callbackProcess(framesPerBuffer, - (float*) outputBuffer, (short*) inputBuffer, timeInfo, statusFlags); + (float*) outputBuffer, (float*) inputBuffer, timeInfo, statusFlags); } diff --git a/src/sounddeviceportaudio.h b/src/sounddeviceportaudio.h index 54058383612a..e608d6cc2f5c 100644 --- a/src/sounddeviceportaudio.h +++ b/src/sounddeviceportaudio.h @@ -42,7 +42,7 @@ class SoundDevicePortAudio : public SoundDevice { int close(); QString getError() const; int callbackProcess(unsigned long framesPerBuffer, - float *output, short *in, + float *output, float* in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags); virtual unsigned int getDefaultSampleRate() const { @@ -70,10 +70,10 @@ class SoundDevicePortAudio : public SoundDevice { int m_underflowUpdateCount; }; -int paV19Callback(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *soundDevice); +int paV19Callback(const void* inputBuffer, void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* soundDevice); #endif diff --git a/src/soundmanager.cpp b/src/soundmanager.cpp index efdf94138ea1..7cbc83404577 100644 --- a/src/soundmanager.cpp +++ b/src/soundmanager.cpp @@ -31,6 +31,7 @@ #include "soundmanagerutil.h" #include "controlobject.h" #include "vinylcontrol/defs_vinylcontrol.h" +#include "sampleutil.h" #ifdef __PORTAUDIO__ typedef PaError (*SetJackClientName)(const char *name); @@ -150,10 +151,10 @@ void SoundManager::closeDevices() { it.value()->onInputDisconnected(in); } - SAMPLE *buffer = m_inputBuffers.value(in); + CSAMPLE* buffer = m_inputBuffers.value(in); if (buffer != NULL) { - delete [] buffer; m_inputBuffers.insert(in, NULL); + SampleUtil::free(buffer); } } m_inputBuffers.clear(); @@ -310,7 +311,7 @@ int SoundManager::setupDevices() { isInput = true; // TODO(bkgood) look into allocating this with the frames per // buffer value from SMConfig - AudioInputBuffer aib(in, new SAMPLE[MAX_BUFFER_LEN]); + AudioInputBuffer aib(in, SampleUtil::alloc(MAX_BUFFER_LEN)); err = device->addInput(aib); if (err != OK) { delete [] aib.getBuffer(); @@ -476,8 +477,6 @@ void SoundManager::requestBuffer( // source list to find out what goes in the buffer data is interlaced in // the order of the list - static const float SHRT_CONVERSION_FACTOR = 1.0f/SHRT_MAX; - for (QList::const_iterator i = outputs.begin(), e = outputs.end(); i != e; ++i) { const AudioOutputBuffer& out = *i; @@ -488,7 +487,7 @@ void SoundManager::requestBuffer( const int iChannelCount = outChans.getChannelCount(); const int iChannelBase = outChans.getChannelBase(); - for (unsigned int iFrameNo=0; iFrameNo < iFramesPerBuffer; ++iFrameNo) { + for (unsigned int iFrameNo = 0; iFrameNo < iFramesPerBuffer; ++iFrameNo) { // iFrameBase is the "base sample" in a frame (ie. the first // sample in a frame) const unsigned int iFrameBase = iFrameNo * iFrameSize; @@ -497,18 +496,18 @@ void SoundManager::requestBuffer( // this will make sure a sample from each channel is copied for (int iChannel = 0; iChannel < iChannelCount; ++iChannel) { outputBuffer[iFrameBase + iChannelBase + iChannel] = - pAudioOutputBuffer[iLocalFrameBase + iChannel] * SHRT_CONVERSION_FACTOR; + pAudioOutputBuffer[iLocalFrameBase + iChannel]; // Input audio pass-through (useful for debugging) //if (in) // output[iFrameBase + src.channelBase + iChannel] = - // in[iFrameBase + src.channelBase + iChannel] * SHRT_CONVERSION_FACTOR; + // in[iFrameBase + src.channelBase + iChannel]; } } } } -void SoundManager::pushBuffer(const QList& inputs, short* inputBuffer, +void SoundManager::pushBuffer(const QList& inputs, CSAMPLE* inputBuffer, const unsigned long iFramesPerBuffer, const unsigned int iFrameSize, SoundDevice* pDevice) { Q_UNUSED(pDevice); @@ -545,7 +544,7 @@ void SoundManager::pushBuffer(const QList& inputs, short* inpu for (QList::const_iterator i = inputs.begin(), e = inputs.end(); i != e; ++i) { const AudioInputBuffer& in = *i; - short* pInputBuffer = in.getBuffer(); + CSAMPLE* pInputBuffer = in.getBuffer(); ChannelGroup chanGroup = in.getChannelGroup(); int iChannelCount = chanGroup.getChannelCount(); int iChannelBase = chanGroup.getChannelBase(); @@ -572,7 +571,7 @@ void SoundManager::pushBuffer(const QList& inputs, short* inpu e = inputs.end(); i != e; ++i) { const AudioInputBuffer& in = *i; - short* pInputBuffer = in.getBuffer(); + CSAMPLE* pInputBuffer = in.getBuffer(); for (QHash::const_iterator it = m_registeredDestinations.find(in); diff --git a/src/soundmanager.h b/src/soundmanager.h index a6226ab44d3d..c30543c16afb 100644 --- a/src/soundmanager.h +++ b/src/soundmanager.h @@ -96,7 +96,7 @@ class SoundManager : public QObject { // Used by SoundDevices to "push" any audio from their inputs that they have // into the mixing engine. - void pushBuffer(const QList& inputs, short* inputBuffer, + void pushBuffer(const QList& inputs, CSAMPLE* inputBuffer, const unsigned long iFramesPerBuffer, const unsigned int iFrameSize, SoundDevice* pDevice); @@ -124,7 +124,7 @@ class SoundManager : public QObject { #endif QList m_devices; QList m_samplerates; - QHash m_inputBuffers; + QHash m_inputBuffers; // Clock reference, used to make sure the same device triggers buffer // refresh every $latency-ms period SoundDevice* m_pClkRefDevice; diff --git a/src/soundmanagerutil.h b/src/soundmanagerutil.h index a482cbe6e9ee..c7c30b759a1a 100644 --- a/src/soundmanagerutil.h +++ b/src/soundmanagerutil.h @@ -136,14 +136,14 @@ class AudioInput : public AudioPath { // This class is required to add the buffer, without changing the hash used as ID class AudioInputBuffer : public AudioInput { public: - AudioInputBuffer(const AudioInput& id, SAMPLE* pBuffer) + AudioInputBuffer(const AudioInput& id, CSAMPLE* pBuffer) : AudioInput(id), m_pBuffer(pBuffer) { }; - inline SAMPLE* getBuffer() const { return m_pBuffer; }; + inline CSAMPLE* getBuffer() const { return m_pBuffer; }; private: - SAMPLE* m_pBuffer; + CSAMPLE* m_pBuffer; }; @@ -156,7 +156,8 @@ class AudioSource { class AudioDestination { public: - virtual void receiveBuffer(AudioInput input, const short* pBuffer, unsigned int iNumFrames) = 0; + virtual void receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, + unsigned int iNumFrames) = 0; virtual void onInputConnected(AudioInput input) { Q_UNUSED(input); }; virtual void onInputDisconnected(AudioInput input) { Q_UNUSED(input); }; }; diff --git a/src/test/enginemastertest.cpp b/src/test/enginemastertest.cpp index fa3e2b408aed..d207acba0f76 100644 --- a/src/test/enginemastertest.cpp +++ b/src/test/enginemastertest.cpp @@ -72,7 +72,7 @@ TEST_F(EngineMasterTest, SingleChannelOutputWorks) { // Pretend that the channel processed the buffer by stuffing it with 1.0's CSAMPLE* pChannelBuffer = const_cast(m_pMaster->getChannelBuffer("[Test1]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannelBuffer, 1.0f, MAX_BUFFER_LEN); + FillBuffer(pChannelBuffer, 0.1f, MAX_BUFFER_LEN); // Instruct the mock to claim it is active, master and not PFL. EXPECT_CALL(*pChannel, isActive()) @@ -94,7 +94,7 @@ TEST_F(EngineMasterTest, SingleChannelOutputWorks) { // Check that the master output contains the channel data. const CSAMPLE* pMasterBuffer = m_pMaster->getMasterBuffer(); - AssertWholeBufferEquals(pMasterBuffer, 1.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pMasterBuffer, 0.1f, MAX_BUFFER_LEN); // Check that the headphone output does not contain the channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); @@ -112,8 +112,8 @@ TEST_F(EngineMasterTest, TwoChannelOutputWorks) { CSAMPLE* pChannel2Buffer = const_cast(m_pMaster->getChannelBuffer("[Test2]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannel1Buffer, 1.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel2Buffer, 2.0f, MAX_BUFFER_LEN); + FillBuffer(pChannel1Buffer, 0.1f, MAX_BUFFER_LEN); + FillBuffer(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. EXPECT_CALL(*pChannel1, isActive()) @@ -149,7 +149,7 @@ TEST_F(EngineMasterTest, TwoChannelOutputWorks) { // Check that the master output contains the sum of the channel data. const CSAMPLE* pMasterBuffer = m_pMaster->getMasterBuffer(); - AssertWholeBufferEquals(pMasterBuffer, 3.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pMasterBuffer, 0.3f, MAX_BUFFER_LEN); // Check that the headphone output does not contain any channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); @@ -167,8 +167,8 @@ TEST_F(EngineMasterTest, TwoChannelPFLOutputWorks) { CSAMPLE* pChannel2Buffer = const_cast(m_pMaster->getChannelBuffer("[Test2]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannel1Buffer, 1.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel2Buffer, 2.0f, MAX_BUFFER_LEN); + FillBuffer(pChannel1Buffer, 0.1f, MAX_BUFFER_LEN); + FillBuffer(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. EXPECT_CALL(*pChannel1, isActive()) @@ -204,11 +204,11 @@ TEST_F(EngineMasterTest, TwoChannelPFLOutputWorks) { // Check that the master output contains the sum of the channel data. const CSAMPLE* pMasterBuffer = m_pMaster->getMasterBuffer(); - AssertWholeBufferEquals(pMasterBuffer, 3.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pMasterBuffer, 0.3f, MAX_BUFFER_LEN); // Check that the headphone output does not contain any channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); - AssertWholeBufferEquals(pHeadphoneBuffer, 3.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pHeadphoneBuffer, 0.3f, MAX_BUFFER_LEN); } TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { @@ -225,9 +225,9 @@ TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { CSAMPLE* pChannel3Buffer = const_cast(m_pMaster->getChannelBuffer("[Test3]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannel1Buffer, 1.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel2Buffer, 2.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel3Buffer, 3.0f, MAX_BUFFER_LEN); + FillBuffer(pChannel1Buffer, 0.1f, MAX_BUFFER_LEN); + FillBuffer(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); + FillBuffer(pChannel3Buffer, 0.3f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. EXPECT_CALL(*pChannel1, isActive()) @@ -277,7 +277,7 @@ TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { // Check that the master output contains the sum of the channel data. const CSAMPLE* pMasterBuffer = m_pMaster->getMasterBuffer(); - AssertWholeBufferEquals(pMasterBuffer, 6.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pMasterBuffer, 0.6f, MAX_BUFFER_LEN); // Check that the headphone output does not contain any channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); @@ -298,9 +298,9 @@ TEST_F(EngineMasterTest, ThreeChannelPFLOutputWorks) { CSAMPLE* pChannel3Buffer = const_cast(m_pMaster->getChannelBuffer("[Test3]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannel1Buffer, 1.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel2Buffer, 2.0f, MAX_BUFFER_LEN); - FillBuffer(pChannel3Buffer, 3.0f, MAX_BUFFER_LEN); + FillBuffer(pChannel1Buffer, 0.1f, MAX_BUFFER_LEN); + FillBuffer(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); + FillBuffer(pChannel3Buffer, 0.3f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. EXPECT_CALL(*pChannel1, isActive()) @@ -350,11 +350,11 @@ TEST_F(EngineMasterTest, ThreeChannelPFLOutputWorks) { // Check that the master output contains the sum of the channel data. const CSAMPLE* pMasterBuffer = m_pMaster->getMasterBuffer(); - AssertWholeBufferEquals(pMasterBuffer, 6.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pMasterBuffer, 0.6f, MAX_BUFFER_LEN); // Check that the headphone output does not contain any channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); - AssertWholeBufferEquals(pHeadphoneBuffer, 6.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pHeadphoneBuffer, 0.6f, MAX_BUFFER_LEN); } TEST_F(EngineMasterTest, SingleChannelPFLOutputWorks) { @@ -364,7 +364,7 @@ TEST_F(EngineMasterTest, SingleChannelPFLOutputWorks) { // Pretend that the channel processed the buffer by stuffing it with 1.0's CSAMPLE* pChannelBuffer = const_cast(m_pMaster->getChannelBuffer("[Test1]")); // We assume it uses MAX_BUFFER_LEN. This should probably be fixed. - FillBuffer(pChannelBuffer, 1.0f, MAX_BUFFER_LEN); + FillBuffer(pChannelBuffer, 0.1f, MAX_BUFFER_LEN); // Instruct the mock to claim it is active, not master and PFL EXPECT_CALL(*pChannel, isActive()) @@ -390,7 +390,7 @@ TEST_F(EngineMasterTest, SingleChannelPFLOutputWorks) { // Check that the headphone output contains the channel data. const CSAMPLE* pHeadphoneBuffer = m_pMaster->getHeadphoneBuffer(); - AssertWholeBufferEquals(pHeadphoneBuffer, 1.0f, MAX_BUFFER_LEN); + AssertWholeBufferEquals(pHeadphoneBuffer, 0.1f, MAX_BUFFER_LEN); } } // namespace diff --git a/src/test/enginemicrophonetest.cpp b/src/test/enginemicrophonetest.cpp index e7b0774c502c..bc87b63a028d 100644 --- a/src/test/enginemicrophonetest.cpp +++ b/src/test/enginemicrophonetest.cpp @@ -16,7 +16,7 @@ class EngineMicrophoneTest : public testing::Test { virtual void SetUp() { inputLength = MAX_BUFFER_LEN/2; outputLength = MAX_BUFFER_LEN; - input = new short[inputLength]; + input = SampleUtil::alloc(inputLength); output = SampleUtil::alloc(outputLength); test = SampleUtil::alloc(outputLength); @@ -25,7 +25,7 @@ class EngineMicrophoneTest : public testing::Test { } virtual void TearDown() { - delete [] input; + SampleUtil::free(input); SampleUtil::free(output); SampleUtil::free(test); delete m_pMicrophone; @@ -45,7 +45,7 @@ class EngineMicrophoneTest : public testing::Test { template void FillSequentialWithStride(T* pBuffer, T initial, T increment, T max, unsigned int stride, unsigned int length) { - Q_ASSERT(length % stride == 0); + ASSERT_EQ(0, length % stride); T value = initial; for (unsigned int i = 0; i < length/stride; ++i) { for (unsigned int j = 0; j < stride; ++j) { @@ -59,20 +59,20 @@ class EngineMicrophoneTest : public testing::Test { void AssertWholeBufferEquals(const CSAMPLE* pBuffer, CSAMPLE value, int iBufferLen) { for (int i = 0; i < iBufferLen; ++i) { - EXPECT_FLOAT_EQ(value, pBuffer[i]); + ASSERT_FLOAT_EQ(value, pBuffer[i]); } } template void AssertBuffersEqual(const T* pBuffer, const T* pExpected, int iBufferLen) { for (int i = 0; i < iBufferLen; ++i) { - EXPECT_FLOAT_EQ(pExpected[i], pBuffer[i]); + ASSERT_FLOAT_EQ(pExpected[i], pBuffer[i]); } } unsigned int inputLength; unsigned int outputLength; - short* input; + CSAMPLE* input; CSAMPLE* output; CSAMPLE* test; EngineMicrophone* m_pMicrophone; @@ -80,25 +80,25 @@ class EngineMicrophoneTest : public testing::Test { }; TEST_F(EngineMicrophoneTest, TestInputMatchesOutput) { - FillBuffer(input, 1, inputLength); + FillBuffer(input, 0.1f, inputLength); SampleUtil::applyGain(output, 0.0f, outputLength); AudioInput micInput = AudioInput(AudioPath::MICROPHONE, 0, 0); // What should channelbase be? - m_pTalkover->set(1.0f); + m_pTalkover->set(1.0); m_pMicrophone->receiveBuffer(micInput, input, inputLength); m_pMicrophone->process(output, output, outputLength); // Check that the output matches the input data. - AssertWholeBufferEquals(output, 1.0f, outputLength); + AssertWholeBufferEquals(output, 0.1f, outputLength); } TEST_F(EngineMicrophoneTest, TestTalkoverDisablesOutput) { SampleUtil::applyGain(output, 0.0f, outputLength); AudioInput micInput = AudioInput(AudioPath::MICROPHONE, 0, 0); // What should channelbase be? - m_pTalkover->set(0.0f); - FillBuffer(input, 1, inputLength); + m_pTalkover->set(0.0); + FillBuffer(input, 0.1f, inputLength); m_pMicrophone->receiveBuffer(micInput, input, inputLength); m_pMicrophone->process(output, output, outputLength); // Check that the output matches the input data. @@ -106,24 +106,22 @@ TEST_F(EngineMicrophoneTest, TestTalkoverDisablesOutput) { // Now fill the buffer with 2's and turn talkover on. Verify that 2's come // out. - m_pTalkover->set(1.0f); - FillBuffer(input, 2, inputLength); + m_pTalkover->set(1.0); + FillBuffer(input, 0.2f, inputLength); m_pMicrophone->receiveBuffer(micInput, input, inputLength); m_pMicrophone->process(output, output, outputLength); // Check that the output matches the input data. - AssertWholeBufferEquals(output, 2.0f, outputLength); + AssertWholeBufferEquals(output, 0.2f, outputLength); } TEST_F(EngineMicrophoneTest, TestRepeatedInputMatchesOutput) { SampleUtil::applyGain(output, 0.0f, outputLength); AudioInput micInput = AudioInput(AudioPath::MICROPHONE, 0, 0); // What should channelbase be? - m_pTalkover->set(1.0f); + m_pTalkover->set(1.0); for (int i = 0; i < 10; i++) { - // We have to limit it since short wraps around at 32768 and float - // doesn't. - FillSequentialWithStride(input, 0, 1, 30000, 1, inputLength); - FillSequentialWithStride(test, 0, 1.0f, 30000.0f, 2, outputLength); + FillSequentialWithStride(input, 0, 0.001f, 1.0f, 1, inputLength); + FillSequentialWithStride(test, 0, 0.001f, 1.0f, 2, outputLength); m_pMicrophone->receiveBuffer(micInput, input, inputLength); m_pMicrophone->process(output, output, outputLength); diff --git a/src/vinylcontrol/vinylcontrol.cpp b/src/vinylcontrol/vinylcontrol.cpp index d9b76188028b..112e0b6c1594 100644 --- a/src/vinylcontrol/vinylcontrol.cpp +++ b/src/vinylcontrol/vinylcontrol.cpp @@ -10,6 +10,13 @@ VinylControl::VinylControl(ConfigObject * pConfig, QString group) m_dVinylPosition(0.0), m_fTimecodeQuality(0.0f) { // Get Control objects + m_pVinylControlInputGain = new ControlObjectThread(VINYL_PREF_KEY, "gain"); + + bool gainOk = false; + double gain = m_pConfig->getValueString(ConfigKey(VINYL_PREF_KEY, "gain")) + .toDouble(&gainOk); + m_pVinylControlInputGain->set(gainOk ? gain : 1.0); + playPos = new ControlObjectThread(group, "playposition"); //Range: -.14 to 1.14 trackSamples = new ControlObjectThread(group, "track_samples"); trackSampleRate = new ControlObjectThread(group, "track_samplerate"); @@ -67,6 +74,7 @@ VinylControl::~VinylControl() { wantenabled->slotSet(true); } + delete m_pVinylControlInputGain; delete playPos; delete trackSamples; delete trackSampleRate; diff --git a/src/vinylcontrol/vinylcontrol.h b/src/vinylcontrol/vinylcontrol.h index d03ed332c594..f8beed8234c5 100644 --- a/src/vinylcontrol/vinylcontrol.h +++ b/src/vinylcontrol/vinylcontrol.h @@ -3,6 +3,7 @@ #include +#include "defs.h" #include "configobject.h" #include "vinylcontrol/vinylsignalquality.h" @@ -15,7 +16,7 @@ class VinylControl : public QObject { virtual void toggleVinylControl(bool enable); virtual bool isEnabled(); - virtual void analyzeSamples(const short* samples, size_t nFrames) = 0; + virtual void analyzeSamples(CSAMPLE* pSamples, size_t nFrames) = 0; virtual bool writeQualityReport(VinylSignalQualityReport* qualityReportFifo) = 0; protected: @@ -24,6 +25,9 @@ class VinylControl : public QObject { ConfigObject* m_pConfig; QString m_group; + // The VC input gain preference. + ControlObjectThread* m_pVinylControlInputGain; + ControlObjectThread *playButton; //The ControlObject used to start/stop playback of the song. ControlObjectThread *playPos; //The ControlObject used to read the playback position in the song. ControlObjectThread *trackSamples; diff --git a/src/vinylcontrol/vinylcontrolprocessor.cpp b/src/vinylcontrol/vinylcontrolprocessor.cpp index 67f4663953f0..d5d4feb53c81 100644 --- a/src/vinylcontrol/vinylcontrolprocessor.cpp +++ b/src/vinylcontrol/vinylcontrolprocessor.cpp @@ -8,6 +8,7 @@ #include "controlpushbutton.h" #include "defs.h" #include "util/timer.h" +#include "sampleutil.h" #define SIGNAL_QUALITY_FIFO_SIZE 256 #define SAMPLE_PIPE_FIFO_SIZE 65536 @@ -16,7 +17,7 @@ VinylControlProcessor::VinylControlProcessor(QObject* pParent, ConfigObject(SAMPLE_PIPE_FIFO_SIZE); + m_samplePipes[i] = new FIFO(SAMPLE_PIPE_FIFO_SIZE); } start(); @@ -40,7 +41,7 @@ VinylControlProcessor::~VinylControlProcessor() { wait(); delete m_pToggle; - delete [] m_pWorkBuffer; + SampleUtil::free(m_pWorkBuffer); { QMutexLocker locker(&m_processorsLock); @@ -87,7 +88,7 @@ void VinylControlProcessor::run() { QMutexLocker locker(&m_processorsLock); VinylControl* pProcessor = m_processors[i]; locker.unlock(); - FIFO* pSamplePipe = m_samplePipes[i]; + FIFO* pSamplePipe = m_samplePipes[i]; if (pSamplePipe->readAvailable() > 0) { int samplesRead = pSamplePipe->read(m_pWorkBuffer, MAX_BUFFER_LEN); @@ -197,7 +198,7 @@ void VinylControlProcessor::onInputDisconnected(AudioInput input) { void VinylControlProcessor::receiveBuffer(AudioInput input, - const short *pBuffer, + const CSAMPLE* pBuffer, unsigned int nFrames) { ScopedTimer t("VinylControlProcessor::receiveBuffer"); if (input.getType() != AudioInput::VINYLCONTROL) { @@ -205,7 +206,6 @@ void VinylControlProcessor::receiveBuffer(AudioInput input, return; } - unsigned char vcIndex = input.getIndex(); if (vcIndex >= kMaximumVinylControlInputs) { @@ -213,14 +213,15 @@ void VinylControlProcessor::receiveBuffer(AudioInput input, return; } - FIFO* pSamplePipe = m_samplePipes[vcIndex]; + FIFO* pSamplePipe = m_samplePipes[vcIndex]; if (pSamplePipe == NULL) { // Should not be possible. return; } - const int nSamples = nFrames * 2; + const int kChannels = 2; + const int nSamples = nFrames * kChannels; int samplesWritten = pSamplePipe->write(pBuffer, nSamples); if (samplesWritten < nSamples) { diff --git a/src/vinylcontrol/vinylcontrolprocessor.h b/src/vinylcontrol/vinylcontrolprocessor.h index fadcbe7d8fbe..8b50510c3250 100644 --- a/src/vinylcontrol/vinylcontrolprocessor.h +++ b/src/vinylcontrol/vinylcontrolprocessor.h @@ -44,7 +44,7 @@ class VinylControlProcessor : public QThread, public AudioDestination { // Called by the engine callback. Must not touch any state in // VinylControlProcessor except for m_samplePipes. - void receiveBuffer(AudioInput input, const short* pBuffer, + void receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, unsigned int iNumFrames); protected: @@ -61,8 +61,8 @@ class VinylControlProcessor : public QThread, public AudioDestination { // A pre-allocated array of FIFOs for writing samples from the engine // callback to the processor thread. There is a maximum of // kMaximumVinylControlInputs pipes. - FIFO* m_samplePipes[kMaximumVinylControlInputs]; - short* m_pWorkBuffer; + FIFO* m_samplePipes[kMaximumVinylControlInputs]; + CSAMPLE* m_pWorkBuffer; QWaitCondition m_samplesAvailableSignal; QMutex m_waitForSampleMutex; QMutex m_processorsLock; diff --git a/src/vinylcontrol/vinylcontrolxwax.cpp b/src/vinylcontrol/vinylcontrolxwax.cpp index 25cd46457eac..bedd6f032ded 100644 --- a/src/vinylcontrol/vinylcontrolxwax.cpp +++ b/src/vinylcontrol/vinylcontrolxwax.cpp @@ -38,12 +38,17 @@ ********************/ +// Sample threshold below which we consider there to be no signal. +const double kMinSignal = 75.0 / SHRT_MAX; + bool VinylControlXwax::s_bLUTInitialized = false; QMutex VinylControlXwax::s_xwaxLUTMutex; VinylControlXwax::VinylControlXwax(ConfigObject* pConfig, QString group) : VinylControl(pConfig, group), m_dVinylPositionOld(0.0), + m_pWorkBuffer(new short[MAX_BUFFER_LEN]), + m_workBufferSize(MAX_BUFFER_LEN), m_iQualPos(0), m_iQualFilled(0), m_iPosition(-1), @@ -85,6 +90,7 @@ VinylControlXwax::VinylControlXwax(ConfigObject* pConfig, QString g // don't have to deal with freeing the strings later char* timecode = NULL; + if (strVinylType == MIXXX_VINYL_SERATOCV02VINYLSIDEA) { timecode = (char*)"serato_2a"; } @@ -158,6 +164,7 @@ VinylControlXwax::~VinylControlXwax() { delete m_pSteadySubtle; delete m_pSteadyGross; delete [] m_pPitchRing; + delete [] m_pWorkBuffer; // Cleanup xwax nicely timecoder_monitor_clear(&timecoder); @@ -192,14 +199,42 @@ bool VinylControlXwax::writeQualityReport(VinylSignalQualityReport* pReport) { } -void VinylControlXwax::analyzeSamples(const short *samples, size_t nFrames) { +void VinylControlXwax::analyzeSamples(CSAMPLE* pSamples, size_t nFrames) { ScopedTimer t("VinylControlXwax::analyzeSamples"); + CSAMPLE gain = m_pVinylControlInputGain->get(); + const int kChannels = 2; + + // We only support amplifying with the VC pre-amp. + if (gain < 1.0f) { + gain = 1.0f; + } + + size_t samplesSize = nFrames * kChannels; + + if (samplesSize > m_workBufferSize) { + delete [] m_pWorkBuffer; + m_pWorkBuffer = new short[samplesSize]; + m_workBufferSize = samplesSize; + } + + // Convert CSAMPLE samples to shorts, preventing overflow. + for (int i = 0; i < static_cast(samplesSize); ++i) { + CSAMPLE sample = pSamples[i] * gain * SHRT_MAX; + + if (sample > SHRT_MAX) { + m_pWorkBuffer[i] = SHRT_MAX; + } else if (sample < SHRT_MIN) { + m_pWorkBuffer[i] = SHRT_MIN; + } else { + m_pWorkBuffer[i] = static_cast(sample); + } + } // Submit the samples to the xwax timecode processor. The size argument is // in stereo frames. - timecoder_submit(&timecoder, samples, nFrames); + timecoder_submit(&timecoder, m_pWorkBuffer, nFrames); - bool bHaveSignal = fabs((float)samples[0]) + fabs((float)samples[1]) > MIN_SIGNAL; + bool bHaveSignal = fabs(pSamples[0]) + fabs(pSamples[1]) > kMinSignal; //qDebug() << "signal?" << bHaveSignal; //TODO: Move all these config object get*() calls to an "updatePrefs()" function, diff --git a/src/vinylcontrol/vinylcontrolxwax.h b/src/vinylcontrol/vinylcontrolxwax.h index 9e09cb79818b..8ca644ff44b5 100644 --- a/src/vinylcontrol/vinylcontrolxwax.h +++ b/src/vinylcontrol/vinylcontrolxwax.h @@ -18,7 +18,6 @@ extern "C" { #define XWAX_DEVICE_FRAME 32 #define XWAX_SMOOTHING (128 / XWAX_DEVICE_FRAME) /* result value is in frames */ #define QUALITY_RING_SIZE 100 -#define MIN_SIGNAL 75 class VinylControlXwax : public VinylControl { public: @@ -26,7 +25,7 @@ class VinylControlXwax : public VinylControl { virtual ~VinylControlXwax(); static void freeLUTs(); - void analyzeSamples(const short* samples, size_t nFrames); + void analyzeSamples(CSAMPLE* pSamples, size_t nFrames); virtual bool writeQualityReport(VinylSignalQualityReport* qualityReportFifo); @@ -53,6 +52,10 @@ class VinylControlXwax : public VinylControl { // The position read last time it was polled. double m_dVinylPositionOld; + // Scratch buffer for CSAMPLE -> short conversions. + short* m_pWorkBuffer; + size_t m_workBufferSize; + // Signal quality ring buffer. // TODO(XXX): Replace with CircularBuffer instead of handling the ring logic // in VinylControlXwax.