diff --git a/src/engine/bufferscalers/enginebufferscale.h b/src/engine/bufferscalers/enginebufferscale.h index 4f50a97e6c7a..12ddd9c2b258 100644 --- a/src/engine/bufferscalers/enginebufferscale.h +++ b/src/engine/bufferscalers/enginebufferscale.h @@ -42,6 +42,10 @@ class EngineBufferScale : public QObject { m_dPitchRatio = *pPitchRatio; } + virtual void setRepeat(bool repeat) { + m_repeat = repeat; + } + // Set the desired output sample rate. void setSampleRate( mixxx::audio::SampleRate sampleRate); @@ -73,4 +77,5 @@ class EngineBufferScale : public QObject { bool m_bSpeedAffectsPitch; double m_dTempoRatio; double m_dPitchRatio; + bool m_repeat; }; diff --git a/src/engine/bufferscalers/enginebufferscalelinear.cpp b/src/engine/bufferscalers/enginebufferscalelinear.cpp index 78aca3ecb469..cadccc957f3d 100644 --- a/src/engine/bufferscalers/enginebufferscalelinear.cpp +++ b/src/engine/bufferscalers/enginebufferscalelinear.cpp @@ -102,7 +102,7 @@ double EngineBufferScaleLinear::scaleBuffer( //qDebug() << "extra samples" << extra_samples; SINT next_samples_read = m_pReadAheadManager->getNextSamples( - rate_add_new, m_bufferInt, extra_samples); + rate_add_new, m_bufferInt, extra_samples, m_repeat); frames_read += getOutputSignal().samples2frames(next_samples_read); } // force a buffer read: @@ -145,8 +145,8 @@ SINT EngineBufferScaleLinear::do_copy(CSAMPLE* buf, SINT buf_size) { // to call getNextSamples until you receive the number of samples you // wanted. while (samples_needed > 0) { - SINT read_size = m_pReadAheadManager->getNextSamples(m_dRate, write_buf, - samples_needed); + SINT read_size = m_pReadAheadManager->getNextSamples( + m_dRate, write_buf, samples_needed, m_repeat); if (read_size == 0) { if (++read_failed_count > 1) { break; @@ -287,7 +287,9 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { m_bufferIntSize = m_pReadAheadManager->getNextSamples( rate_new == 0 ? rate_old : rate_new, - m_bufferInt, samples_to_read); + m_bufferInt, + samples_to_read, + m_repeat); if (m_bufferIntSize == 0) { if (++read_failed_count > 1) { diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index 6f2e60d74fe0..415861872a92 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -204,11 +204,12 @@ double EngineBufferScaleRubberBand::scaleBuffer( if (remaining_frames > 0 && iLenFramesRequired > 0) { SINT iAvailSamples = m_pReadAheadManager->getNextSamples( - // The value doesn't matter here. All that matters is we - // are going forward or backward. - (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, - m_buffer_back, - getOutputSignal().frames2samples(iLenFramesRequired)); + // The value doesn't matter here. All that matters is we + // are going forward or backward. + (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, + m_buffer_back, + getOutputSignal().frames2samples(iLenFramesRequired), + m_repeat); SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples); if (iAvailFrames > 0) { diff --git a/src/engine/bufferscalers/enginebufferscalest.cpp b/src/engine/bufferscalers/enginebufferscalest.cpp index cff3c4a9c275..ce9a3cbaca50 100644 --- a/src/engine/bufferscalers/enginebufferscalest.cpp +++ b/src/engine/bufferscalers/enginebufferscalest.cpp @@ -142,11 +142,12 @@ double EngineBufferScaleST::scaleBuffer( if (remaining_frames > 0) { SINT iAvailSamples = m_pReadAheadManager->getNextSamples( - // The value doesn't matter here. All that matters is we - // are going forward or backward. - (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, - buffer_back.data(), - buffer_back.size()); + // The value doesn't matter here. All that matters is we + // are going forward or backward. + (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, + buffer_back.data(), + buffer_back.size(), + m_repeat); SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples); if (iAvailFrames > 0) { diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index e055c0257c11..a3a39ee15823 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -345,7 +345,8 @@ void LoopingControl::process(const double dRate, double LoopingControl::nextTrigger(bool reverse, const double currentSample, - double *pTarget) { + double* pTarget, + bool repeat) { *pTarget = kNoTrigger; LoopSamples loopSamples = m_loopSamples.getValue(); @@ -423,6 +424,19 @@ double LoopingControl::nextTrigger(bool reverse, return loopSamples.end; } } + + // TEST hack: Abuse LoopingControl for wrapping around at track end / start (reverse) + // if repeat is enabled + if (repeat) { + if (reverse) { + *pTarget = m_pTrackSamples->get(); + return 0.0; + } else { + *pTarget = 0.0; + return m_pTrackSamples->get(); + } + } + return kNoTrigger; } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index b9f1028299a2..f9b7f04a88b8 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -37,8 +37,9 @@ class LoopingControl : public EngineControl { // nextTrigger returns the sample at which the engine will be triggered to // take a loop, given the value of currentSample and dRate. virtual double nextTrigger(bool reverse, - const double currentSample, - double *pTarget); + const double currentSample, + double* pTarget, + bool repeat); // hintReader will add to hintList hints both the loop in and loop out // sample, if set. diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index b70e4e9bed7f..001ef8c0b867 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -948,6 +948,12 @@ void EngineBuffer::processTrackLocked( m_rate_old = rate; + bool repeat = m_pRepeat->toBool(); + if (m_repeat_old != repeat) { + m_pScale->setRepeat(repeat); + m_repeat_old = repeat; + } + // If the buffer is not paused, then scale the audio. if (!bCurBufferPaused) { // Perform scaling of Reader buffer into buffer. @@ -973,6 +979,8 @@ void EngineBuffer::processTrackLocked( // Note: The last buffer of a track is padded with silence. // This silence is played together with the last samples in the last // callback and the m_filepos_play is advanced behind the end of the track. + // This does not apply when repeat is enabled, sclaer->scaleBuffer() + // wraps around at end/start. if (m_bCrossfadeReady) { // Bring pOutput with the new parameters in and fade out the old one, @@ -1005,20 +1013,19 @@ void EngineBuffer::processTrackLocked( m_scratching_old = is_scratching; // Handle repeat mode - bool at_start = m_filepos_play <= 0; + // bool at_start = m_filepos_play <= 0; at_end = m_filepos_play >= m_trackSamplesOld; - bool repeat_enabled = m_pRepeat->toBool(); - bool end_of_track = //(at_start && backwards) || (at_end && !backwards); // If playbutton is pressed, check if we are at start or end of track if ((m_playButton->toBool() || (m_fwdButton->toBool() || m_backButton->toBool())) && end_of_track) { - if (repeat_enabled) { - double fractionalPos = at_start ? 1.0 : 0; - doSeekFractional(fractionalPos, SEEK_STANDARD); + // TODO Remove this, ReadAheadManager should already have seeked to start/end + if (repeat) { + // double fractionalPos = at_start ? 1.0 : 0; + // doSeekFractional(fractionalPos, SEEK_STANDARD); } else { m_playButton->set(0.); } diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index cfe65ffc0931..205749b1e333 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -291,6 +291,9 @@ class EngineBuffer : public EngineObject { // True if the previous callback was reverse. bool m_reverse_old; + // True if repeat was enabled in the previous callback. + bool m_repeat_old; + // The previous callback's pitch. Used to check if the scaler parameters // need updating. double m_pitch_old; diff --git a/src/engine/readaheadmanager.cpp b/src/engine/readaheadmanager.cpp index 5c3d15e2ba1d..9da9576bb36f 100644 --- a/src/engine/readaheadmanager.cpp +++ b/src/engine/readaheadmanager.cpp @@ -35,8 +35,8 @@ ReadAheadManager::~ReadAheadManager() { SampleUtil::free(m_pCrossFadeBuffer); } -SINT ReadAheadManager::getNextSamples(double dRate, CSAMPLE* pOutput, - SINT requested_samples) { +SINT ReadAheadManager::getNextSamples( + double dRate, CSAMPLE* pOutput, SINT requested_samples, bool repeat) { // TODO(XXX): Remove implicit assumption of 2 channels if (!even(requested_samples)) { qDebug() << "ERROR: Non-even requested_samples to ReadAheadManager::getNextSamples"; @@ -49,7 +49,7 @@ SINT ReadAheadManager::getNextSamples(double dRate, CSAMPLE* pOutput, double target; // A loop will only limit the amount we can read in one shot. const double loop_trigger = m_pLoopingControl->nextTrigger( - in_reverse, m_currentPosition, &target); + in_reverse, m_currentPosition, &target, repeat); SINT preloop_samples = 0; double samplesToLoopTrigger = 0.0; diff --git a/src/engine/readaheadmanager.h b/src/engine/readaheadmanager.h index 49f5b98c64ff..7396deb36a69 100644 --- a/src/engine/readaheadmanager.h +++ b/src/engine/readaheadmanager.h @@ -32,7 +32,7 @@ class ReadAheadManager { /// direction the audio is progressing in. Returns the total number of /// samples read into buffer. Note that it is very common that the total /// samples read is less than the requested number of samples. - virtual SINT getNextSamples(double dRate, CSAMPLE* buffer, SINT requested_samples); + virtual SINT getNextSamples(double dRate, CSAMPLE* buffer, SINT requested_samples, bool repeat); /// Used to add a new EngineControls that ReadAheadManager will use to decide /// which samples to return. diff --git a/src/test/enginebufferscalelineartest.cpp b/src/test/enginebufferscalelineartest.cpp index 1cb4399392ab..7d4890123058 100644 --- a/src/test/enginebufferscalelineartest.cpp +++ b/src/test/enginebufferscalelineartest.cpp @@ -25,11 +25,13 @@ class ReadAheadManagerMock : public ReadAheadManager { m_pBuffer(NULL), m_iBufferSize(0), m_iReadPosition(0), - m_iSamplesRead(0) { + m_iSamplesRead(0), + m_repeat(false) { } - SINT getNextSamplesFake(double dRate, CSAMPLE* buffer, SINT requested_samples) { + SINT getNextSamplesFake(double dRate, CSAMPLE* buffer, SINT requested_samples, bool repeat) { Q_UNUSED(dRate); + Q_UNUSED(repeat); bool hasBuffer = m_pBuffer != NULL; // You forgot to set the mock read buffer. EXPECT_TRUE(hasBuffer); @@ -51,12 +53,17 @@ class ReadAheadManagerMock : public ReadAheadManager { return m_iSamplesRead; } - MOCK_METHOD3(getNextSamples, SINT(double dRate, CSAMPLE* buffer, SINT requested_samples)); + MOCK_METHOD4(getNextSamples, + SINT(double dRate, + CSAMPLE* buffer, + SINT requested_samples, + bool repeat)); CSAMPLE* m_pBuffer; SINT m_iBufferSize; SINT m_iReadPosition; SINT m_iSamplesRead; + bool m_repeat; }; class EngineBufferScaleLinearTest : public MixxxTest { @@ -137,7 +144,7 @@ TEST_F(EngineBufferScaleLinearTest, ScaleConstant) { m_pReadAheadMock->setReadBuffer(readBuffer, 1); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); CSAMPLE* pOutput = SampleUtil::alloc(kiLinearScaleReadAheadLength); @@ -157,7 +164,7 @@ TEST_F(EngineBufferScaleLinearTest, UnityRateIsSamplePerfect) { SetRateNoLerp(1.0); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); QVector readBuffer; @@ -189,7 +196,7 @@ TEST_F(EngineBufferScaleLinearTest, TestRateLERPMonotonicallyProgresses) { m_pReadAheadMock->setReadBuffer(readBuffer, 1); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); CSAMPLE* pOutput = SampleUtil::alloc(kiLinearScaleReadAheadLength); @@ -214,7 +221,7 @@ TEST_F(EngineBufferScaleLinearTest, TestDoubleSpeedSmoothlyHalvesSamples) { m_pReadAheadMock->setReadBuffer(readBuffer, 8); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); CSAMPLE* pOutput = SampleUtil::alloc(kiLinearScaleReadAheadLength); @@ -243,7 +250,7 @@ TEST_F(EngineBufferScaleLinearTest, TestHalfSpeedSmoothlyDoublesSamples) { m_pReadAheadMock->setReadBuffer(readBuffer, 4); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); CSAMPLE* pOutput = SampleUtil::alloc(kiLinearScaleReadAheadLength); @@ -275,7 +282,7 @@ TEST_F(EngineBufferScaleLinearTest, TestRepeatedScaleCalls) { m_pReadAheadMock->setReadBuffer(readBuffer, 4); // Tell the RAMAN mock to invoke getNextSamplesFake - EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _)) + EXPECT_CALL(*m_pReadAheadMock, getNextSamples(_, _, _, _)) .WillRepeatedly(Invoke(m_pReadAheadMock, &ReadAheadManagerMock::getNextSamplesFake)); CSAMPLE expectedResult[] = { -101.0, 101.0, diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index 7f0dcb1fde85..e9b7b17c1f9c 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -407,7 +407,7 @@ TEST_F(LoopingControlTest, LoopScale_HalvesLoop) { // the current sample should reseek based on the new loop size. double target; double trigger = m_pChannel1->getEngineBuffer()->m_pLoopingControl->nextTrigger( - false, 1800, &target); + false, 1800, &target, /* repeat */ false); EXPECT_EQ(300, target); EXPECT_EQ(1800, trigger); } diff --git a/src/test/readaheadmanager_test.cpp b/src/test/readaheadmanager_test.cpp index 64e6c4aec242..180fd4a09df9 100644 --- a/src/test/readaheadmanager_test.cpp +++ b/src/test/readaheadmanager_test.cpp @@ -46,11 +46,13 @@ class StubLoopControl : public LoopingControl { } double nextTrigger(bool reverse, - const double currentSample, - double* pTarget) override { + const double currentSample, + double* pTarget, + bool repeat) override { Q_UNUSED(reverse); Q_UNUSED(currentSample); Q_UNUSED(pTarget); + Q_UNUSED(repeat); RELEASE_ASSERT(!m_targetReturnValues.isEmpty()); *pTarget = m_targetReturnValues.takeFirst(); RELEASE_ASSERT(!m_triggerReturnValues.isEmpty()); @@ -129,17 +131,17 @@ TEST_F(ReadAheadManagerTest, FractionalFrameLoop) { m_pLoopControl->pushTargetReturnValue(3.3); m_pLoopControl->pushTargetReturnValue(kNoTrigger); // read from start to loop trigger, overshoot 0.3 - EXPECT_EQ(20, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 100)); + EXPECT_EQ(20, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 100, false)); // read loop - EXPECT_EQ(18, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 80)); + EXPECT_EQ(18, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 80, false)); // read loop - EXPECT_EQ(16, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 62)); + EXPECT_EQ(16, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 62, false)); // read loop - EXPECT_EQ(18, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 46)); + EXPECT_EQ(18, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 46, false)); // read loop - EXPECT_EQ(16, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 28)); + EXPECT_EQ(16, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 28, false)); // read loop - EXPECT_EQ(12, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 12)); + EXPECT_EQ(12, m_pReadAheadManager->getNextSamples(1.0, m_pBuffer, 12, false)); // start 0.5 to 20.2 = 19.7 // loop 3.3 to 20.2 = 16.9