summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/controllers/bulk/bulkcontroller.cpp2
-rw-r--r--src/engine/bufferscalers/enginebufferscalelinear.cpp17
-rw-r--r--src/engine/bufferscalers/enginebufferscalerubberband.cpp39
-rw-r--r--src/engine/bufferscalers/enginebufferscalerubberband.h2
-rw-r--r--src/engine/bufferscalers/enginebufferscalest.cpp13
-rw-r--r--src/engine/controls/loopingcontrol.cpp228
-rw-r--r--src/engine/controls/loopingcontrol.h4
-rw-r--r--src/engine/controls/quantizecontrol.cpp2
-rw-r--r--src/engine/readaheadmanager.cpp16
-rw-r--r--src/library/autodj/autodjprocessor.cpp40
-rw-r--r--src/library/banshee/bansheefeature.h12
-rw-r--r--src/library/baseexternallibraryfeature.cpp7
-rw-r--r--src/library/baseexternallibraryfeature.h7
-rw-r--r--src/library/browse/browsefeature.cpp4
-rw-r--r--src/library/browse/browsefeature.h4
-rw-r--r--src/library/browse/browsetablemodel.cpp17
-rw-r--r--src/library/browse/browsetablemodel.h2
-rw-r--r--src/library/library.cpp12
-rw-r--r--src/library/library.h2
-rw-r--r--src/library/rhythmbox/rhythmboxfeature.cpp5
-rw-r--r--src/library/rhythmbox/rhythmboxfeature.h15
-rw-r--r--src/sources/soundsourcem4a.cpp24
-rw-r--r--src/sources/soundsourcem4a.h6
-rw-r--r--src/test/controller_mapping_validation_test.cpp75
-rw-r--r--src/test/controller_mapping_validation_test.h39
-rw-r--r--src/test/looping_control_test.cpp4
26 files changed, 377 insertions, 221 deletions
diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp
index 3bfe3f0db8..4dd814b7e5 100644
--- a/src/controllers/bulk/bulkcontroller.cpp
+++ b/src/controllers/bulk/bulkcontroller.cpp
@@ -52,7 +52,7 @@ void BulkReader::run() {
qDebug() << "Stopped Reader";
}
-static QString get_string(libusb_device_handle *handle, u_int8_t id) {
+static QString get_string(libusb_device_handle* handle, uint8_t id) {
unsigned char buf[128] = { 0 };
if (id) {
diff --git a/src/engine/bufferscalers/enginebufferscalelinear.cpp b/src/engine/bufferscalers/enginebufferscalelinear.cpp
index 4f91e3a93e..33fda453e0 100644
--- a/src/engine/bufferscalers/enginebufferscalelinear.cpp
+++ b/src/engine/bufferscalers/enginebufferscalelinear.cpp
@@ -220,7 +220,6 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) {
SINT unscaled_frames_needed = static_cast<SINT>(frames +
m_dNextFrame - floor(m_dNextFrame));
- int read_failed_count = 0;
CSAMPLE floor_sample[2];
CSAMPLE ceil_sample[2];
@@ -289,14 +288,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);
-
- if (m_bufferIntSize == 0) {
- if (++read_failed_count > 1) {
- break;
- } else {
- continue;
- }
- }
+ // Note we may get 0 samples once if we just hit a loop trigger,
+ // e.g. when reloop_toggle jumps back to loop_in, or when
+ // moving a loop causes the play position to be moved along.
frames_read += getOutputSignal().samples2frames(m_bufferIntSize);
unscaled_frames_needed -= getOutputSignal().samples2frames(m_bufferIntSize);
@@ -306,11 +300,6 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) {
currentFrameFloor = static_cast<SINT>(floor(m_dCurrentFrame));
} while (getOutputSignal().frames2samples(currentFrameFloor) + 3 >= m_bufferIntSize);
- // I guess?
- if (read_failed_count > 1) {
- break;
- }
-
// Now that the buffer is up to date, we can get the value of the sample
// at the floor of our position.
if (currentFrameFloor >= 0) {
diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp
index 66f75b0e59..b502bad384 100644
--- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp
+++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp
@@ -163,15 +163,19 @@ SINT EngineBufferScaleRubberBand::retrieveAndDeinterleave(
}
void EngineBufferScaleRubberBand::deinterleaveAndProcess(
- const CSAMPLE* pBuffer, SINT frames, bool flush) {
+ const CSAMPLE* pBuffer,
+ SINT frames) {
DEBUG_ASSERT(frames <= static_cast<SINT>(m_buffers[0].size()));
SampleUtil::deinterleaveBuffer(
- m_buffers[0].data(), m_buffers[1].data(), pBuffer, frames);
+ m_buffers[0].data(),
+ m_buffers[1].data(),
+ pBuffer,
+ frames);
m_pRubberBand->process(m_bufferPtrs.data(),
frames,
- flush);
+ false);
}
double EngineBufferScaleRubberBand::scaleBuffer(
@@ -189,7 +193,6 @@ double EngineBufferScaleRubberBand::scaleBuffer(
SINT remaining_frames = getOutputSignal().samples2frames(iOutputBufferSize);
CSAMPLE* read = pOutputBuffer;
bool last_read_failed = false;
- bool break_out_after_retrieve_and_reset_rubberband = false;
while (remaining_frames > 0) {
// ReadAheadManager will eventually read the requested frames with
// enough calls to retrieveAndDeinterleave because CachingReader returns
@@ -204,14 +207,6 @@ double EngineBufferScaleRubberBand::scaleBuffer(
total_received_frames += received_frames;
read += getOutputSignal().frames2samples(received_frames);
- if (break_out_after_retrieve_and_reset_rubberband) {
- // qDebug() << "break_out_after_retrieve_and_reset_rubberband";
- // If we break out early then we have flushed RubberBand and need to
- // reset it.
- reset();
- break;
- }
-
const SINT next_block_frames_required =
static_cast<SINT>(m_pRubberBand->getSamplesRequired());
if (remaining_frames > 0 && next_block_frames_required > 0) {
@@ -225,14 +220,22 @@ double EngineBufferScaleRubberBand::scaleBuffer(
if (available_frames > 0) {
last_read_failed = false;
- deinterleaveAndProcess(m_interleavedReadBuffer.data(), available_frames, false);
+ deinterleaveAndProcess(m_interleavedReadBuffer.data(), available_frames);
} else {
+ // We may get 0 samples once if we just hit a loop trigger, e.g.
+ // when reloop_toggle jumps back to loop_in, or when moving a
+ // loop causes the play position to be moved along.
if (last_read_failed) {
- // Flush and break out after the next retrieval. If we are
- // at EOF this serves to get the last samples out of
- // RubberBand.
- deinterleaveAndProcess(m_interleavedReadBuffer.data(), 0, true);
- break_out_after_retrieve_and_reset_rubberband = true;
+ // If we get 0 samples repeatedly, flush and break out after
+ // the next retrieval. If we are at EOF this serves to get
+ // the last samples out of RubberBand.
+ qDebug() << "ReadAheadManager::getNextSamples() returned "
+ "zero samples repeatedly. Padding with silence.";
+ SampleUtil::clear(
+ m_interleavedReadBuffer.data(),
+ getOutputSignal().frames2samples(next_block_frames_required));
+ deinterleaveAndProcess(m_interleavedReadBuffer.data(),
+ next_block_frames_required);
}
last_read_failed = true;
}
diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.h b/src/engine/bufferscalers/enginebufferscalerubberband.h
index 4837410937..bfd42bc58a 100644
--- a/src/engine/bufferscalers/enginebufferscalerubberband.h
+++ b/src/engine/bufferscalers/enginebufferscalerubberband.h
@@ -56,7 +56,7 @@ class EngineBufferScaleRubberBand final : public EngineBufferScale {
/// `m_pRubberBand->reset()` directly.
void reset();
- void deinterleaveAndProcess(const CSAMPLE* pBuffer, SINT frames, bool flush);
+ void deinterleaveAndProcess(const CSAMPLE* pBuffer, SINT frames);
SINT retrieveAndDeinterleave(CSAMPLE* pBuffer, SINT frames);
// The read-ahead manager that we use to fetch samples
diff --git a/src/engine/bufferscalers/enginebufferscalest.cpp b/src/engine/bufferscalers/enginebufferscalest.cpp
index 5c8b5f8c70..8a09309ed4 100644
--- a/src/engine/bufferscalers/enginebufferscalest.cpp
+++ b/src/engine/bufferscalers/enginebufferscalest.cpp
@@ -153,9 +153,18 @@ double EngineBufferScaleST::scaleBuffer(
last_read_failed = false;
m_pSoundTouch->putSamples(buffer_back.data(), iAvailFrames);
} else {
+ // We may get 0 samples once if we just hit a loop trigger, e.g.
+ // when reloop_toggle jumps back to loop_in, or when moving a
+ // loop causes the play position to be moved along.
if (last_read_failed) {
- m_pSoundTouch->flush();
- break; // exit loop after failure
+ // If we get 0 samples repeatedly, add silence that allows
+ // to flush the last samples out of Soundtouch.
+ // m_pSoundTouch->flush() must not be used, because it allocates
+ // a temporary buffer in the heap which maybe locking
+ qDebug() << "ReadAheadManager::getNextSamples() returned "
+ "zero samples repeatedly. Padding with silence.";
+ SampleUtil::clear(buffer_back.data(), buffer_back.size());
+ m_pSoundTouch->putSamples(buffer_back.data(), buffer_back.size());
}
last_read_failed = true;
}
diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp
index 4015076143..03e9a4c281 100644
--- a/src/engine/controls/loopingcontrol.cpp
+++ b/src/engine/controls/loopingcontrol.cpp
@@ -124,12 +124,6 @@ LoopingControl::LoopingControl(const QString& group,
Qt::DirectConnection);
m_pQuantizeEnabled = ControlObject::getControl(ConfigKey(group, "quantize"));
- m_pNextBeat = ControlObject::getControl(ConfigKey(group, "beat_next"));
- m_pNextBeat->setKbdRepeatable(true);
- m_pPreviousBeat = ControlObject::getControl(ConfigKey(group, "beat_prev"));
- m_pPreviousBeat->setKbdRepeatable(true);
- m_pClosestBeat = ControlObject::getControl(ConfigKey(group, "beat_closest"));
- m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples"));
m_pSlipEnabled = ControlObject::getControl(ConfigKey(group, "slip_enabled"));
// DEPRECATED: Use beatloop_size and beatloop_set instead.
@@ -301,9 +295,8 @@ void LoopingControl::slotLoopScale(double scaleFactor) {
const mixxx::audio::FrameDiff_t loopLength =
(loopInfo.endPosition - loopInfo.startPosition) * scaleFactor;
- const auto trackEndPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pTrackSamples->get());
+ const FrameInfo info = frameInfo();
+ const auto trackEndPosition = info.trackEndPosition;
if (!trackEndPosition.isValid()) {
return;
}
@@ -444,64 +437,84 @@ mixxx::audio::FramePos LoopingControl::nextTrigger(bool reverse,
}
if (m_bLoopingEnabled &&
- !m_bAdjustingLoopIn && !m_bAdjustingLoopOut &&
loopInfo.startPosition.isValid() &&
loopInfo.endPosition.isValid()) {
- if (loopInfo.startPosition != m_oldLoopInfo.startPosition ||
- loopInfo.endPosition != m_oldLoopInfo.endPosition) {
- // bool seek is only valid after the loop has changed
- switch (loopInfo.seekMode) {
- case LoopSeekMode::Changed:
- // here the loop has changed and the play position
- // should be moved with it
- *pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition,
- reverse,
- m_oldLoopInfo.startPosition,
- m_oldLoopInfo.endPosition,
- loopInfo.startPosition,
- loopInfo.endPosition);
- break;
- case LoopSeekMode::MovedOut: {
- bool movedOut = false;
- // Check if we have moved out of the loop, before we could enable it
- if (reverse) {
- if (loopInfo.startPosition > currentPosition) {
- movedOut = true;
- }
- } else {
- if (loopInfo.endPosition < currentPosition) {
- movedOut = true;
- }
- }
- if (movedOut) {
+ if (!m_bAdjustingLoopIn && !m_bAdjustingLoopOut) {
+ if (loopInfo.startPosition != m_oldLoopInfo.startPosition ||
+ loopInfo.endPosition != m_oldLoopInfo.endPosition) {
+ // bool seek is only valid after the loop has changed
+ switch (loopInfo.seekMode) {
+ case LoopSeekMode::Changed:
+ // here the loop has changed and the play position
+ // should be moved with it
*pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition,
reverse,
- loopInfo.startPosition,
- loopInfo.endPosition,
+ m_oldLoopInfo.startPosition,
+ m_oldLoopInfo.endPosition,
loopInfo.startPosition,
loopInfo.endPosition);
+ break;
+ case LoopSeekMode::MovedOut: {
+ bool movedOut = false;
+ // Check if we have moved out of the loop, before we could enable it
+ if (reverse) {
+ if (loopInfo.startPosition > currentPosition) {
+ movedOut = true;
+ }
+ } else {
+ if (loopInfo.endPosition < currentPosition) {
+ movedOut = true;
+ }
+ }
+ if (movedOut) {
+ *pTargetPosition = adjustedPositionInsideAdjustedLoop(currentPosition,
+ reverse,
+ loopInfo.startPosition,
+ loopInfo.endPosition,
+ loopInfo.startPosition,
+ loopInfo.endPosition);
+ }
+ break;
+ }
+ case LoopSeekMode::None:
+ // Nothing to do here. This is used for enabling saved loops
+ // which we want to do without jumping to the loop start
+ // position.
+ break;
+ }
+ m_oldLoopInfo = loopInfo;
+ if (pTargetPosition->isValid()) {
+ // jump immediately
+ return currentPosition;
}
- break;
- }
- case LoopSeekMode::None:
- // Nothing to do here. This is used for enabling saved loops
- // which we want to do without jumping to the loop start
- // position.
- break;
}
- m_oldLoopInfo = loopInfo;
- if (pTargetPosition->isValid()) {
- // jump immediately
- return currentPosition;
+ if (reverse) {
+ *pTargetPosition = loopInfo.endPosition;
+ return loopInfo.startPosition;
+ } else {
+ *pTargetPosition = loopInfo.startPosition;
+ return loopInfo.endPosition;
}
- }
-
- if (reverse) {
- *pTargetPosition = loopInfo.endPosition;
- return loopInfo.startPosition;
} else {
- *pTargetPosition = loopInfo.startPosition;
- return loopInfo.endPosition;
+ // LOOP in or out button is pressed for adjusting.
+ // Jump back to loop start, when reaching the track end this
+ // prevents that the track stops outside the adjusted loop.
+ if (!reverse) {
+ if (m_bAdjustingLoopIn) {
+ // Just in case the user does not release loop-in in time.
+ *pTargetPosition = m_oldLoopInfo.startPosition;
+ return loopInfo.endPosition;
+ }
+ const FrameInfo info = frameInfo();
+ *pTargetPosition = loopInfo.startPosition;
+ return info.trackEndPosition;
+ } else {
+ if (m_bAdjustingLoopOut) {
+ // Just in case the user does not release loop-out in time.
+ *pTargetPosition = m_oldLoopInfo.endPosition;
+ return loopInfo.startPosition;
+ }
+ }
}
}
return mixxx::audio::kInvalidFramePos;
@@ -658,23 +671,33 @@ void LoopingControl::setLoopInToCurrentPosition() {
const mixxx::BeatsPointer pBeats = m_pBeats;
LoopInfo loopInfo = m_loopInfo.getValue();
mixxx::audio::FramePos quantizedBeatPosition;
- mixxx::audio::FramePos position = m_currentPosition.getValue();
+ const FrameInfo info = frameInfo();
+ // Note: currentPos can be past the end of the track, in the padded
+ // silence of the last buffer. This position might be not reachable in
+ // a future runs, depending on the buffering.
+ mixxx::audio::FramePos position = math_min(info.currentPosition, info.trackEndPosition);
if (m_pQuantizeEnabled->toBool() && pBeats) {
- const auto closestBeatPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pClosestBeat->get());
- if (m_bAdjustingLoopIn) {
- if (closestBeatPosition == m_currentPosition.getValue()) {
- quantizedBeatPosition = closestBeatPosition;
+ mixxx::audio::FramePos prevBeatPosition;
+ mixxx::audio::FramePos nextBeatPosition;
+ if (pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false)) {
+ // both beat positions are valid
+ mixxx::audio::FramePos closestBeatPosition =
+ (nextBeatPosition - position > position - prevBeatPosition)
+ ? prevBeatPosition
+ : nextBeatPosition;
+ if (m_bAdjustingLoopIn) {
+ if (closestBeatPosition == position) {
+ quantizedBeatPosition = closestBeatPosition;
+ } else {
+ quantizedBeatPosition = prevBeatPosition;
+ }
} else {
- quantizedBeatPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pPreviousBeat->get());
+ if (closestBeatPosition > info.trackEndPosition) {
+ quantizedBeatPosition = prevBeatPosition;
+ } else {
+ quantizedBeatPosition = closestBeatPosition;
+ }
}
- } else {
- quantizedBeatPosition = closestBeatPosition;
- }
- if (quantizedBeatPosition.isValid()) {
position = quantizedBeatPosition;
}
}
@@ -684,6 +707,9 @@ void LoopingControl::setLoopInToCurrentPosition() {
if (loopInfo.endPosition.isValid() && loopInfo.endPosition < position) {
loopInfo.endPosition = mixxx::audio::kInvalidFramePos;
m_pCOLoopEndPosition->set(loopInfo.endPosition.toEngineSamplePosMaybeInvalid());
+ if (m_bLoopingEnabled) {
+ setLoopingEnabled(false);
+ }
}
// If we're looping and the loop-in and out points are now so close
@@ -787,23 +813,37 @@ void LoopingControl::setLoopOutToCurrentPosition() {
mixxx::BeatsPointer pBeats = m_pBeats;
LoopInfo loopInfo = m_loopInfo.getValue();
mixxx::audio::FramePos quantizedBeatPosition;
- mixxx::audio::FramePos position = m_currentPosition.getValue();
+ FrameInfo info = frameInfo();
+ // Note: currentPos can be past the end of the track, in the padded
+ // silence of the last buffer. This position might be not reachable in
+ // a future runs, depending on the buffering.
+ mixxx::audio::FramePos position = math_min(info.currentPosition, info.trackEndPosition);
if (m_pQuantizeEnabled->toBool() && pBeats) {
- const auto closestBeatPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pClosestBeat->get());
- if (m_bAdjustingLoopOut) {
- if (closestBeatPosition == m_currentPosition.getValue()) {
- quantizedBeatPosition = closestBeatPosition;
+ mixxx::audio::FramePos prevBeatPosition;
+ mixxx::audio::FramePos nextBeatPosition;
+ if (pBeats->findPrevNextBeats(position, &prevBeatPosition, &nextBeatPosition, false)) {
+ // both beat positions are valid
+ const mixxx::audio::FramePos closestBeatPosition =
+ (nextBeatPosition - position > position - prevBeatPosition)
+ ? prevBeatPosition
+ : nextBeatPosition;
+ if (m_bAdjustingLoopOut) {
+ if (closestBeatPosition == position) {
+ quantizedBeatPosition = closestBeatPosition;
+ } else {
+ if (nextBeatPosition > info.trackEndPosition) {
+ quantizedBeatPosition = prevBeatPosition;
+ } else {
+ quantizedBeatPosition = nextBeatPosition;
+ }
+ }
} else {
- quantizedBeatPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pNextBeat->get());
+ if (closestBeatPosition > info.trackEndPosition) {
+ quantizedBeatPosition = prevBeatPosition;
+ } else {
+ quantizedBeatPosition = closestBeatPosition;
+ }
}
- } else {
- quantizedBeatPosition = closestBeatPosition;
- }
- if (quantizedBeatPosition.isValid()) {
position = quantizedBeatPosition;
}
}
@@ -1351,9 +1391,8 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
beats = minBeatSize;
}
- const auto trackEndPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pTrackSamples->get());
+ FrameInfo info = frameInfo();
+ const auto trackEndPosition = info.trackEndPosition;
const mixxx::BeatsPointer pBeats = m_pBeats;
if (!trackEndPosition.isValid() || !pBeats) {
clearActiveBeatLoop();
@@ -1367,15 +1406,15 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable
mixxx::audio::kInvalidFramePos,
LoopSeekMode::MovedOut};
LoopInfo loopInfo = m_loopInfo.getValue();
- mixxx::audio::FramePos currentPosition = m_currentPosition.getValue();
-
+ mixxx::audio::FramePos currentPosition = info.currentPosition;
// Start from the current position/closest beat and
// create the loop around X beats from there.
if (keepStartPoint) {
if (loopInfo.startPosition.isValid()) {
newloopInfo.startPosition = loopInfo.startPosition;
} else {
- newloopInfo.startPosition = currentPosition;
+ newloopInfo.startPosition =
+ math_min(info.currentPosition, info.trackEndPosition);
}
} else {
// If running reverse, move the loop one loop size to the left.
@@ -1587,8 +1626,9 @@ void LoopingControl::slotLoopMove(double beats) {
return;
}
+ FrameInfo info = frameInfo();
if (BpmControl::getBeatContext(pBeats,
- m_currentPosition.getValue(),
+ info.currentPosition,
nullptr,
nullptr,
nullptr,
@@ -1602,9 +1642,7 @@ void LoopingControl::slotLoopMove(double beats) {
// The track would stop as soon as the playhead crosses track end,
// so we don't allow moving a loop beyond end.
// https://github.com/mixxxdj/mixxx/issues/9478
- const auto trackEndPosition =
- mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
- m_pTrackSamples->get());
+ const auto trackEndPosition = info.trackEndPosition;
if (!trackEndPosition.isValid() || newLoopEndPosition > trackEndPosition) {
return;
}
diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h
index 3f239e70aa..3a3c0ab603 100644
--- a/src/engine/controls/loopingcontrol.h
+++ b/src/engine/controls/loopingcontrol.h
@@ -182,10 +182,6 @@ class LoopingControl : public EngineControl {
LoopInfo m_oldLoopInfo;
ControlValueAtomic<mixxx::audio::FramePos> m_currentPosition;
ControlObject* m_pQuantizeEnabled;
- ControlObject* m_pNextBeat;
- ControlObject* m_pPreviousBeat;
- ControlObject* m_pClosestBeat;
- ControlObject* m_pTrackSamples;
QAtomicPointer<BeatLoopingControl> m_pActiveBeatLoop;
// Base BeatLoop Control Object.
diff --git a/src/engine/controls/quantizecontrol.cpp b/src/engine/controls/quantizecontrol.cpp
index f36f6159ef..81c40d2f13 100644
--- a/src/engine/controls/quantizecontrol.cpp
+++ b/src/engine/controls/quantizecontrol.cpp
@@ -16,8 +16,10 @@ QuantizeControl::QuantizeControl(const QString& group,
m_pCOQuantizeEnabled = new ControlPushButton(ConfigKey(group, "quantize"), true);
m_pCOQuantizeEnabled->setButtonMode(ControlPushButton::TOGGLE);
m_pCONextBeat = new ControlObject(ConfigKey(group, "beat_next"));
+ m_pCONextBeat->setKbdRepeatable(true);
m_pCONextBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
m_pCOPrevBeat = new ControlObject(ConfigKey(group, "beat_prev"));
+ m_pCOPrevBeat->setKbdRepeatable(true);
m_pCOPrevBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
m_pCOClosestBeat = new ControlObject(ConfigKey(group, "beat_closest"));
m_pCOClosestBeat->set(mixxx::audio::kInvalidFramePos.toEngineSamplePosMaybeInvalid());
diff --git a/src/engine/readaheadmanager.cpp b/src/engine/readaheadmanager.cpp
index fb1908c66d..264196a34d 100644
--- a/src/engine/readaheadmanager.cpp
+++ b/src/engine/readaheadmanager.cpp
@@ -80,7 +80,7 @@ SINT ReadAheadManager::getNextSamples(double dRate, CSAMPLE* pOutput,
}
// Sanity checks.
- if (samples_from_reader < 0) {
+ VERIFY_OR_DEBUG_ASSERT(samples_from_reader >= 0) {
qDebug() << "Need negative samples in ReadAheadManager::getNextSamples. Ignoring read";
return 0;
}
@@ -98,8 +98,8 @@ SINT ReadAheadManager::getNextSamples(double dRate, CSAMPLE* pOutput,
m_cacheMissHappened = true;
} else if (m_cacheMissHappened) {
// Previous read was a cache miss, but now we got something back.
- // Apply ramping gain, because the last buffer has unwanted silenced
- // and new without fading are causing a pop.
+ // Apply ramping gain, because the last buffer has unwanted silence
+ // and new samples without fading are causing a pop.
SampleUtil::applyRampingGain(pOutput, 0.0, 1.0, samples_from_reader);
// Reset the cache miss flag, because we are now back on track.
m_cacheMissHappened = false;
@@ -157,12 +157,10 @@ SINT ReadAheadManager::getNextSamples(double dRate, CSAMPLE* pOutput,
}
// do crossfade from the current buffer into the new loop beginning
- if (samples_from_reader != 0) { // avoid division by zero
- SampleUtil::linearCrossfadeBuffersOut(
- pOutput,
- m_pCrossFadeBuffer,
- samples_from_reader);
- }
+ SampleUtil::linearCrossfadeBuffersOut(
+ pOutput,
+ m_pCrossFadeBuffer,
+ samples_from_reader);
}
//qDebug() << "read" << m_currentPosition << samples_read;
diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp
index 57014e0641..38051d0d11 100644
--- a/src/library/autodj/autodjprocessor.cpp
+++ b/src/library/autodj/autodjprocessor.cpp
@@ -1280,23 +1280,29 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck,
}
pToDeck->fadeBeginPos = toDeckOutroStartSecond;
- double introStart;
+ double toDeckStartSeconds = toDeckPositionSeconds;
+ const double introStart = getIntroStartSecond(pToDeck);
+ const double introEnd = getIntroEndSecond(pToDeck);
if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) {
// toDeckPosition >= pToDeck->fadeBeginPos happens when the
// user has seeked or played the to track behind fadeBeginPos of
// the fade after the next.
// In this case we recue the track just before the transition.
- introStart = getIntroStartSecond(pToDeck);
- } else {
- introStart = toDeckPositionSeconds;
+ toDeckStartSeconds = introStart;
}
double introLength = 0;
- // This returns introStart in case the user has not yet set an intro end
- const double introEnd = getIntroEndSecond(pToDeck);
- if (introStart < introEnd) {
- introLength = introEnd - introStart;
+ // introEnd is equal introStart in case it has not yet been set
+ if (toDeckStartSeconds < introEnd && introStart < introEnd) {
+ // Limit the intro length that results from a revers seek
+ // to a reasonable values. If the seek was too big, ignore it.
+ introLength = introEnd - toDeckStartSeconds;
+ if (introLength > (introEnd - introStart) * 2 &&
+ introLength > (introEnd - introStart) + m_transitionTime &&
+ introLength > outroLength) {
+ introLength = 0;
+ }
}
if constexpr (sDebug) {
@@ -1337,10 +1343,10 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck,
}
}
if (transitionLength > 0) {
- const double transitionEnd = introStart + transitionLength;
+ const double transitionEnd = toDeckStartSeconds + transitionLength;
if (transitionEnd > pToDeck->fadeBeginPos) {
// End intro before next outro starts
- transitionLength = pToDeck->fadeBeginPos - introStart;
+ transitionLength = pToDeck->fadeBeginPos - toDeckStartSeconds;
VERIFY_OR_DEBUG_ASSERT(transitionLength > 0) {
// We seek to intro start above in this case so this never happens
transitionLength = 1;
@@ -1348,9 +1354,9 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck,
}
pFromDeck->fadeBeginPos = outroEnd - transitionLength;
pFromDeck->fadeEndPos = outroEnd;
- pToDeck->startPos = introStart;
+ pToDeck->startPos = toDeckStartSeconds;
} else {
- useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, introStart);
+ useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds);
}
} break;
case TransitionMode::FadeAtOutroStart: {
@@ -1383,10 +1389,10 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck,
transitionLength = introLength;
}
}
- const double transitionEnd = introStart + transitionLength;
+ const double transitionEnd = toDeckStartSeconds + transitionLength;
if (transitionEnd > pToDeck->fadeBeginPos) {
// End intro before next outro starts
- transitionLength = pToDeck->fadeBeginPos - introStart;
+ transitionLength = pToDeck->fadeBeginPos - toDeckStartSeconds;
VERIFY_OR_DEBUG_ASSERT(transitionLength > 0) {
// We seek to intro start above in this case so this never happens
transitionLength = 1;
@@ -1394,14 +1400,14 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck,
}
pFromDeck->fadeBeginPos = outroStart;
pFromDeck->fadeEndPos = outroStart + transitionLength;
- pToDeck->startPos = introStart;
+ pToDeck->startPos = toDeckStartSeconds;
} else if (introLength > 0) {
transitionLength = introLength;
pFromDeck->fadeBeginPos = outroEnd - transitionLength;
pFromDeck->fadeEndPos = outroEnd;
- pToDeck->startPos = introStart;
+ pToDeck->startPos = toDeckStartSeconds;
} else {
- useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, introStart);
+ useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, outroEnd, toDeckStartSeconds);
}
} break;
case TransitionMode::FixedSkipSilence: {
diff --git a/src/library/banshee/bansheefeature.h b/src/library/banshee/bansheefeature.h
index 0b4684db23..57b6357b37 100644
--- a/src/library/banshee/bansheefeature.h
+++ b/src/library/banshee/bansheefeature.h
@@ -19,20 +19,20 @@ class BansheeFeature : public BaseExternalLibraryFeature {
Q_OBJECT
publi