1 #include "engine/enginebuffer.h"
2
3 #include <QtDebug>
4
5 #include "control/controlindicator.h"
6 #include "control/controllinpotmeter.h"
7 #include "control/controlpotmeter.h"
8 #include "control/controlproxy.h"
9 #include "control/controlpushbutton.h"
10 #include "engine/bufferscalers/enginebufferscalelinear.h"
11 #include "engine/bufferscalers/enginebufferscalerubberband.h"
12 #include "engine/bufferscalers/enginebufferscalest.h"
13 #include "engine/cachingreader/cachingreader.h"
14 #include "engine/channels/enginechannel.h"
15 #include "engine/controls/bpmcontrol.h"
16 #include "engine/controls/clockcontrol.h"
17 #include "engine/controls/cuecontrol.h"
18 #include "engine/controls/enginecontrol.h"
19 #include "engine/controls/keycontrol.h"
20 #include "engine/controls/loopingcontrol.h"
21 #include "engine/controls/quantizecontrol.h"
22 #include "engine/controls/ratecontrol.h"
23 #include "engine/enginemaster.h"
24 #include "engine/engineworkerscheduler.h"
25 #include "engine/readaheadmanager.h"
26 #include "engine/sync/enginesync.h"
27 #include "engine/sync/synccontrol.h"
28 #include "moc_enginebuffer.cpp"
29 #include "preferences/usersettings.h"
30 #include "track/beatfactory.h"
31 #include "track/keyutils.h"
32 #include "track/track.h"
33 #include "util/assert.h"
34 #include "util/compatibility.h"
35 #include "util/defs.h"
36 #include "util/logger.h"
37 #include "util/sample.h"
38 #include "util/timer.h"
39 #include "waveform/visualplayposition.h"
40 #include "waveform/waveformwidgetfactory.h"
41
42 #ifdef __VINYLCONTROL__
43 #include "engine/controls/vinylcontrolcontrol.h"
44 #endif
45
46 namespace {
47 const mixxx::Logger kLogger("EngineBuffer");
48
49 constexpr double kLinearScalerElipsis =
50 1.00058; // 2^(0.01/12): changes < 1 cent allows a linear scaler
51
52 constexpr SINT kSamplesPerFrame = 2; // Engine buffer uses Stereo frames only
53
54 // Rate at which the playpos slider is updated
55 constexpr int kPlaypositionUpdateRate = 15; // updates per second
56
57 } // anonymous namespace
58
EngineBuffer(const QString & group,UserSettingsPointer pConfig,EngineChannel * pChannel,EngineMaster * pMixingEngine)59 EngineBuffer::EngineBuffer(const QString& group,
60 UserSettingsPointer pConfig,
61 EngineChannel* pChannel,
62 EngineMaster* pMixingEngine)
63 : m_group(group),
64 m_pConfig(pConfig),
65 m_pLoopingControl(nullptr),
66 m_pSyncControl(nullptr),
67 m_pVinylControlControl(nullptr),
68 m_pRateControl(nullptr),
69 m_pBpmControl(nullptr),
70 m_pKeyControl(nullptr),
71 m_pReadAheadManager(nullptr),
72 m_pReader(nullptr),
73 m_filepos_play(kInitalSamplePosition),
74 m_speed_old(0),
75 m_tempo_ratio_old(1.),
76 m_scratching_old(false),
77 m_reverse_old(false),
78 m_pitch_old(0),
79 m_baserate_old(0),
80 m_rate_old(0.),
81 m_trackSamplesOld(0),
82 m_trackSampleRateOld(0),
83 m_dSlipPosition(0.),
84 m_dSlipRate(1.0),
85 m_bSlipEnabledProcessing(false),
86 m_pRepeat(nullptr),
87 m_startButton(nullptr),
88 m_endButton(nullptr),
89 m_bScalerOverride(false),
90 m_iSeekPhaseQueued(0),
91 m_iEnableSyncQueued(SYNC_REQUEST_NONE),
92 m_iSyncModeQueued(SYNC_INVALID),
93 m_iTrackLoading(0),
94 m_bPlayAfterLoading(false),
95 m_iSampleRate(0),
96 m_pCrossfadeBuffer(SampleUtil::alloc(MAX_BUFFER_LEN)),
97 m_bCrossfadeReady(false),
98 m_iLastBufferSize(0) {
99 m_queuedSeek.setValue(kNoQueuedSeek);
100
101 // zero out crossfade buffer
102 SampleUtil::clear(m_pCrossfadeBuffer, MAX_BUFFER_LEN);
103
104 m_pReader = new CachingReader(group, pConfig);
105 connect(m_pReader, &CachingReader::trackLoading,
106 this, &EngineBuffer::slotTrackLoading,
107 Qt::DirectConnection);
108 connect(m_pReader, &CachingReader::trackLoaded,
109 this, &EngineBuffer::slotTrackLoaded,
110 Qt::DirectConnection);
111 connect(m_pReader, &CachingReader::trackLoadFailed,
112 this, &EngineBuffer::slotTrackLoadFailed,
113 Qt::DirectConnection);
114
115 // Play button
116 m_playButton = new ControlPushButton(ConfigKey(m_group, "play"));
117 m_playButton->setButtonMode(ControlPushButton::TOGGLE);
118 m_playButton->connectValueChangeRequest(
119 this, &EngineBuffer::slotControlPlayRequest,
120 Qt::DirectConnection);
121
122 //Play from Start Button (for sampler)
123 m_playStartButton = new ControlPushButton(ConfigKey(m_group, "start_play"));
124 connect(m_playStartButton, &ControlObject::valueChanged,
125 this, &EngineBuffer::slotControlPlayFromStart,
126 Qt::DirectConnection);
127
128 // Jump to start and stop button
129 m_stopStartButton = new ControlPushButton(ConfigKey(m_group, "start_stop"));
130 connect(m_stopStartButton, &ControlObject::valueChanged,
131 this, &EngineBuffer::slotControlJumpToStartAndStop,
132 Qt::DirectConnection);
133
134 //Stop playback (for sampler)
135 m_stopButton = new ControlPushButton(ConfigKey(m_group, "stop"));
136 connect(m_stopButton, &ControlObject::valueChanged,
137 this, &EngineBuffer::slotControlStop,
138 Qt::DirectConnection);
139
140 // Start button
141 m_startButton = new ControlPushButton(ConfigKey(m_group, "start"));
142 m_startButton->setButtonMode(ControlPushButton::TRIGGER);
143 connect(m_startButton, &ControlObject::valueChanged,
144 this, &EngineBuffer::slotControlStart,
145 Qt::DirectConnection);
146
147 // End button
148 m_endButton = new ControlPushButton(ConfigKey(m_group, "end"));
149 connect(m_endButton, &ControlObject::valueChanged,
150 this, &EngineBuffer::slotControlEnd,
151 Qt::DirectConnection);
152
153 m_pSlipButton = new ControlPushButton(ConfigKey(m_group, "slip_enabled"));
154 m_pSlipButton->setButtonMode(ControlPushButton::TOGGLE);
155
156 m_playposSlider = new ControlLinPotmeter(
157 ConfigKey(m_group, "playposition"), 0.0, 1.0, 0, 0, true);
158 connect(m_playposSlider, &ControlObject::valueChanged,
159 this, &EngineBuffer::slotControlSeek,
160 Qt::DirectConnection);
161
162 // Control used to communicate ratio playpos to GUI thread
163 m_visualPlayPos = VisualPlayPosition::getVisualPlayPosition(m_group);
164
165 m_pRepeat = new ControlPushButton(ConfigKey(m_group, "repeat"));
166 m_pRepeat->setButtonMode(ControlPushButton::TOGGLE);
167
168 m_pSampleRate = new ControlProxy("[Master]", "samplerate", this);
169
170 m_pKeylockEngine = new ControlProxy("[Master]", "keylock_engine", this);
171 m_pKeylockEngine->connectValueChanged(this, &EngineBuffer::slotKeylockEngineChanged,
172 Qt::DirectConnection);
173
174 m_pTrackSamples = new ControlObject(ConfigKey(m_group, "track_samples"));
175 m_pTrackSampleRate = new ControlObject(ConfigKey(m_group, "track_samplerate"));
176
177 m_pKeylock = new ControlPushButton(ConfigKey(m_group, "keylock"), true);
178 m_pKeylock->setButtonMode(ControlPushButton::TOGGLE);
179
180 m_pEject = new ControlPushButton(ConfigKey(m_group, "eject"));
181 connect(m_pEject, &ControlObject::valueChanged,
182 this, &EngineBuffer::slotEjectTrack,
183 Qt::DirectConnection);
184
185 m_pTrackLoaded = new ControlObject(ConfigKey(m_group, "track_loaded"), false);
186 m_pTrackLoaded->setReadOnly();
187
188 // Quantization Controller for enabling and disabling the
189 // quantization (alignment) of loop in/out positions and (hot)cues with
190 // beats.
191 QuantizeControl* quantize_control = new QuantizeControl(group, pConfig);
192 addControl(quantize_control);
193 m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize"));
194
195 // Create the Loop Controller
196 m_pLoopingControl = new LoopingControl(group, pConfig);
197 addControl(m_pLoopingControl);
198
199 m_pEngineSync = pMixingEngine->getEngineSync();
200
201 m_pSyncControl = new SyncControl(group, pConfig, pChannel, m_pEngineSync);
202
203 #ifdef __VINYLCONTROL__
204 m_pVinylControlControl = new VinylControlControl(group, pConfig);
205 addControl(m_pVinylControlControl);
206 #endif
207
208 // Create the Rate Controller
209 m_pRateControl = new RateControl(group, pConfig);
210 // Add the Rate Controller
211 addControl(m_pRateControl);
212 // Looping Control needs Rate Control for Reverse Button
213 m_pLoopingControl->setRateControl(m_pRateControl);
214
215 // Create the BPM Controller
216 m_pBpmControl = new BpmControl(group, pConfig);
217 addControl(m_pBpmControl);
218
219 // TODO(rryan) remove this dependence?
220 m_pRateControl->setBpmControl(m_pBpmControl);
221 m_pSyncControl->setEngineControls(m_pRateControl, m_pBpmControl);
222 pMixingEngine->getEngineSync()->addSyncableDeck(m_pSyncControl);
223 addControl(m_pSyncControl);
224
225 m_fwdButton = ControlObject::getControl(ConfigKey(group, "fwd"));
226 m_backButton = ControlObject::getControl(ConfigKey(group, "back"));
227
228 m_pKeyControl = new KeyControl(group, pConfig);
229 addControl(m_pKeyControl);
230
231 // Create the clock controller
232 m_pClockControl = new ClockControl(group, pConfig);
233 addControl(m_pClockControl);
234
235 // Create the cue controller
236 m_pCueControl = new CueControl(group, pConfig);
237 addControl(m_pCueControl);
238
239 m_pReadAheadManager = new ReadAheadManager(m_pReader,
240 m_pLoopingControl);
241 m_pReadAheadManager->addRateControl(m_pRateControl);
242
243 // Construct scaling objects
244 m_pScaleLinear = new EngineBufferScaleLinear(m_pReadAheadManager);
245 m_pScaleST = new EngineBufferScaleST(m_pReadAheadManager);
246 m_pScaleRB = new EngineBufferScaleRubberBand(m_pReadAheadManager);
247 if (m_pKeylockEngine->get() == SOUNDTOUCH) {
248 m_pScaleKeylock = m_pScaleST;
249 } else {
250 m_pScaleKeylock = m_pScaleRB;
251 }
252 m_pScaleVinyl = m_pScaleLinear;
253 m_pScale = m_pScaleVinyl;
254 m_pScale->clear();
255 m_bScalerChanged = true;
256
257 m_pPassthroughEnabled = new ControlProxy(group, "passthrough", this);
258 m_pPassthroughEnabled->connectValueChanged(this, &EngineBuffer::slotPassthroughChanged,
259 Qt::DirectConnection);
260
261 #ifdef __SCALER_DEBUG__
262 df.setFileName("mixxx-debug.csv");
263 df.open(QIODevice::WriteOnly | QIODevice::Text);
264 writer.setDevice(&df);
265 #endif
266
267 // Now that all EngineControls have been created call setEngineMaster.
268 // TODO(XXX): Get rid of EngineControl::setEngineMaster and
269 // EngineControl::setEngineBuffer entirely and pass them through the
270 // constructor.
271 setEngineMaster(pMixingEngine);
272 }
273
~EngineBuffer()274 EngineBuffer::~EngineBuffer() {
275 #ifdef __SCALER_DEBUG__
276 //close the writer
277 df.close();
278 #endif
279 delete m_pReadAheadManager;
280 delete m_pReader;
281
282 delete m_playButton;
283 delete m_playStartButton;
284 delete m_stopStartButton;
285
286 delete m_startButton;
287 delete m_endButton;
288 delete m_stopButton;
289 delete m_playposSlider;
290
291 delete m_pSlipButton;
292 delete m_pRepeat;
293 delete m_pSampleRate;
294
295 delete m_pTrackLoaded;
296 delete m_pTrackSamples;
297 delete m_pTrackSampleRate;
298
299 delete m_pScaleLinear;
300 delete m_pScaleST;
301 delete m_pScaleRB;
302
303 delete m_pKeylock;
304 delete m_pEject;
305
306 SampleUtil::free(m_pCrossfadeBuffer);
307
308 qDeleteAll(m_engineControls);
309 }
310
fractionalPlayposFromAbsolute(double absolutePlaypos)311 double EngineBuffer::fractionalPlayposFromAbsolute(double absolutePlaypos) {
312 double fFractionalPlaypos = 0.0;
313 if (m_trackSamplesOld != 0) {
314 fFractionalPlaypos = math_min<double>(absolutePlaypos, m_trackSamplesOld);
315 fFractionalPlaypos /= m_trackSamplesOld;
316 }
317 return fFractionalPlaypos;
318 }
319
enableIndependentPitchTempoScaling(bool bEnable,const int iBufferSize)320 void EngineBuffer::enableIndependentPitchTempoScaling(bool bEnable,
321 const int iBufferSize) {
322 // MUST ACQUIRE THE PAUSE MUTEX BEFORE CALLING THIS METHOD
323
324 // When no time-stretching or pitch-shifting is needed we use our own linear
325 // interpolation code (EngineBufferScaleLinear). It is faster and sounds
326 // much better for scratching.
327
328 // m_pScaleKeylock and m_pScaleVinyl could change out from under us,
329 // so cache it.
330 EngineBufferScale* keylock_scale = m_pScaleKeylock;
331 EngineBufferScale* vinyl_scale = m_pScaleVinyl;
332
333 if (bEnable && m_pScale != keylock_scale) {
334 if (m_speed_old != 0.0) {
335 // Crossfade if we are not paused.
336 // If we start from zero a ramping gain is
337 // applied later
338 readToCrossfadeBuffer(iBufferSize);
339 }
340 m_pScale = keylock_scale;
341 m_pScale->clear();
342 m_bScalerChanged = true;
343 } else if (!bEnable && m_pScale != vinyl_scale) {
344 if (m_speed_old != 0.0) {
345 // Crossfade if we are not paused
346 // (for slow speeds below 0.1 the vinyl_scale is used)
347 readToCrossfadeBuffer(iBufferSize);
348 }
349 m_pScale = vinyl_scale;
350 m_pScale->clear();
351 m_bScalerChanged = true;
352 }
353 }
354
getBpm()355 double EngineBuffer::getBpm()
356 {
357 return m_pBpmControl->getBpm();
358 }
359
getLocalBpm()360 double EngineBuffer::getLocalBpm() {
361 return m_pBpmControl->getLocalBpm();
362 }
363
setEngineMaster(EngineMaster * pEngineMaster)364 void EngineBuffer::setEngineMaster(EngineMaster* pEngineMaster) {
365 for (const auto& pControl: qAsConst(m_engineControls)) {
366 pControl->setEngineMaster(pEngineMaster);
367 }
368 }
369
queueNewPlaypos(double newpos,enum SeekRequest seekType)370 void EngineBuffer::queueNewPlaypos(double newpos, enum SeekRequest seekType) {
371 // All seeks need to be done in the Engine thread so queue it up.
372 // Write the position before the seek type, to reduce a possible race
373 // condition effect
374 VERIFY_OR_DEBUG_ASSERT(seekType != SEEK_PHASE) {
375 // SEEK_PHASE with a position is not supported
376 // use SEEK_STANDARD for that
377 seekType = SEEK_STANDARD;
378 }
379 m_queuedSeek.setValue({newpos, seekType});
380 }
381
requestSyncPhase()382 void EngineBuffer::requestSyncPhase() {
383 // Don't overwrite m_iSeekQueued
384 m_iSeekPhaseQueued = 1;
385 }
386
requestEnableSync(bool enabled)387 void EngineBuffer::requestEnableSync(bool enabled) {
388 // If we're not playing, the queued event won't get processed so do it now.
389 if (m_playButton->get() == 0.0) {
390 m_pEngineSync->requestEnableSync(m_pSyncControl, enabled);
391 return;
392 }
393 SyncRequestQueued enable_request =
394 static_cast<SyncRequestQueued>(atomicLoadRelaxed(m_iEnableSyncQueued));
395 if (enabled) {
396 m_iEnableSyncQueued = SYNC_REQUEST_ENABLE;
397 } else {
398 // If sync is enabled and disabled very quickly, it's is a one-shot
399 // sync event and needs to be handled specially. Otherwise the sync
400 // state will get stuck on or won't go on at all.
401 if (enable_request == SYNC_REQUEST_ENABLE) {
402 m_iEnableSyncQueued = SYNC_REQUEST_ENABLEDISABLE;
403 } else {
404 // Note that there is no DISABLEENABLE, because that's an irrelevant
405 // queuing. Moreover, ENABLEDISABLEENABLE is also redundant, so
406 // we don't have to handle any special cases.
407 m_iEnableSyncQueued = SYNC_REQUEST_DISABLE;
408 }
409 }
410 }
411
requestSyncMode(SyncMode mode)412 void EngineBuffer::requestSyncMode(SyncMode mode) {
413 // If we're not playing, the queued event won't get processed so do it now.
414 if (kLogger.traceEnabled()) {
415 kLogger.trace() << getGroup() << "EngineBuffer::requestSyncMode";
416 }
417 if (m_playButton->get() == 0.0) {
418 m_pEngineSync->requestSyncMode(m_pSyncControl, mode);
419 } else {
420 m_iSyncModeQueued = mode;
421 }
422 }
423
requestClonePosition(EngineChannel * pChannel)424 void EngineBuffer::requestClonePosition(EngineChannel* pChannel) {
425 atomicStoreRelaxed(m_pChannelToCloneFrom, pChannel);
426 }
427
readToCrossfadeBuffer(const int iBufferSize)428 void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) {
429 if (!m_bCrossfadeReady) {
430 // Read buffer, as if there where no parameter change
431 // (Must be called only once per callback)
432 m_pScale->scaleBuffer(m_pCrossfadeBuffer, iBufferSize);
433 // Restore the original position that was lost due to scaleBuffer() above
434 m_pReadAheadManager->notifySeek(m_filepos_play);
435 m_bCrossfadeReady = true;
436 }
437 }
438
seekCloneBuffer(EngineBuffer * pOtherBuffer)439 void EngineBuffer::seekCloneBuffer(EngineBuffer* pOtherBuffer) {
440 doSeekPlayPos(pOtherBuffer->getExactPlayPos(), SEEK_EXACT);
441 }
442
443 // WARNING: This method is not thread safe and must not be called from outside
444 // the engine callback!
setNewPlaypos(double newpos)445 void EngineBuffer::setNewPlaypos(double newpos) {
446 if (kLogger.traceEnabled()) {
447 kLogger.trace() << m_group << "EngineBuffer::setNewPlaypos" << newpos;
448 }
449
450 m_filepos_play = newpos;
451
452 if (m_rate_old != 0.0) {
453 // Before seeking, read extra buffer for crossfading
454 // this also sets m_pReadAheadManager to newpos
455 readToCrossfadeBuffer(m_iLastBufferSize);
456 } else {
457 m_pReadAheadManager->notifySeek(m_filepos_play);
458 }
459 m_pScale->clear();
460
461 // Ensures that the playpos slider gets updated in next process call
462 m_iSamplesSinceLastIndicatorUpdate = 1000000;
463
464 // Must hold the engineLock while using m_engineControls
465 for (const auto& pControl: qAsConst(m_engineControls)) {
466 pControl->notifySeek(m_filepos_play);
467 }
468
469 verifyPlay(); // verify or update play button and indicator
470 }
471
getGroup()472 QString EngineBuffer::getGroup() {
473 return m_group;
474 }
475
getSpeed()476 double EngineBuffer::getSpeed() {
477 return m_speed_old;
478 }
479
getScratching()480 bool EngineBuffer::getScratching() {
481 return m_scratching_old;
482 }
483
484 // WARNING: Always called from the EngineWorker thread pool
slotTrackLoading()485 void EngineBuffer::slotTrackLoading() {
486 // Pause EngineBuffer from processing frames
487 m_pause.lock();
488 // Setting m_iTrackLoading inside a m_pause.lock ensures that
489 // track buffer is not processed when starting to load a new one
490 m_iTrackLoading = 1;
491 m_pause.unlock();
492
493 // Set play here, to signal the user that the play command is adopted
494 m_playButton->set((double)m_bPlayAfterLoading);
495 m_pTrackSamples->set(0); // Stop renderer
496 }
497
loadFakeTrack(TrackPointer pTrack,bool bPlay)498 void EngineBuffer::loadFakeTrack(TrackPointer pTrack, bool bPlay) {
499 if (bPlay) {
500 m_playButton->set((double)bPlay);
501 }
502 slotTrackLoaded(pTrack, pTrack->getSampleRate(),
503 pTrack->getSampleRate() * pTrack->getDurationInt());
504 }
505
506 // WARNING: Always called from the EngineWorker thread pool
slotTrackLoaded(TrackPointer pTrack,int iTrackSampleRate,int iTrackNumSamples)507 void EngineBuffer::slotTrackLoaded(TrackPointer pTrack,
508 int iTrackSampleRate,
509 int iTrackNumSamples) {
510 if (kLogger.traceEnabled()) {
511 kLogger.trace() << getGroup() << "EngineBuffer::slotTrackLoaded";
512 }
513 TrackPointer pOldTrack = m_pCurrentTrack;
514
515 m_pause.lock();
516 m_visualPlayPos->setInvalid();
517 m_filepos_play = kInitalSamplePosition; // for execute seeks to 0.0
518 m_pCurrentTrack = pTrack;
519 m_pTrackSamples->set(iTrackNumSamples);
520 m_pTrackSampleRate->set(iTrackSampleRate);
521 // Reset slip mode
522 m_pSlipButton->set(0);
523 m_bSlipEnabledProcessing = false;
524 m_dSlipPosition = 0.;
525 m_dSlipRate = 0;
526 m_pTrackLoaded->forceSet(1);
527 // Reset the pitch value for the new track.
528 m_pause.unlock();
529
530 notifyTrackLoaded(pTrack, pOldTrack);
531 // Start buffer processing after all EngineContols are up to date
532 // with the current track e.g track is seeked to Cue
533 m_iTrackLoading = 0;
534 }
535
536 // WARNING: Always called from the EngineWorker thread pool
slotTrackLoadFailed(TrackPointer pTrack,const QString & reason)537 void EngineBuffer::slotTrackLoadFailed(TrackPointer pTrack,
538 const QString& reason) {
539 m_iTrackLoading = 0;
540 // Loading of a new track failed.
541 // eject the currently loaded track (the old Track) as well
542 ejectTrack();
543 emit trackLoadFailed(pTrack, reason);
544 }
545
getLoadedTrack() const546 TrackPointer EngineBuffer::getLoadedTrack() const {
547 return m_pCurrentTrack;
548 }
549
isReverse()550 bool EngineBuffer::isReverse() {
551 return m_reverse_old;
552 }
553
ejectTrack()554 void EngineBuffer::ejectTrack() {
555 // clear track values in any case, this may fix Bug #1450424
556 if (kLogger.traceEnabled()) {
557 kLogger.trace() << "EngineBuffer::ejectTrack()";
558 }
559 m_pause.lock();
560 m_iTrackLoading = 0;
561 m_pTrackLoaded->forceSet(0);
562 m_pTrackSamples->set(0);
563 m_pTrackSampleRate->set(0);
564 m_visualPlayPos->set(0.0, 0.0, 0.0, 0.0, 0.0);
565 TrackPointer pTrack = m_pCurrentTrack;
566 m_pCurrentTrack.reset();
567 m_playButton->set(0.0);
568 m_playposSlider->set(0);
569 m_pCueControl->resetIndicators();
570 doSeekPlayPos(0.0, SEEK_EXACT);
571 m_pause.unlock();
572
573 // Close open file handles by unloading the current track
574 m_pReader->newTrack(TrackPointer());
575
576 if (pTrack) {
577 notifyTrackLoaded(TrackPointer(), pTrack);
578 }
579 }
580
slotPassthroughChanged(double enabled)581 void EngineBuffer::slotPassthroughChanged(double enabled) {
582 if (enabled != 0) {
583 // If passthrough was enabled, stop playing the current track.
584 slotControlStop(1.0);
585 }
586 }
587
588 // WARNING: This method runs in both the GUI thread and the Engine Thread
slotControlSeek(double fractionalPos)589 void EngineBuffer::slotControlSeek(double fractionalPos) {
590 doSeekFractional(fractionalPos, SEEK_STANDARD);
591 }
592
593 // WARNING: This method runs from SyncWorker and Engine Worker
slotControlSeekAbs(double playPosition)594 void EngineBuffer::slotControlSeekAbs(double playPosition) {
595 doSeekPlayPos(playPosition, SEEK_STANDARD);
596 }
597
598 // WARNING: This method runs from SyncWorker and Engine Worker
slotControlSeekExact(double playPosition)599 void EngineBuffer::slotControlSeekExact(double playPosition) {
600 doSeekPlayPos(playPosition, SEEK_EXACT);
601 }
602
doSeekFractional(double fractionalPos,enum SeekRequest seekType)603 void EngineBuffer::doSeekFractional(double fractionalPos, enum SeekRequest seekType) {
604 // Prevent NaN's from sneaking into the engine.
605 VERIFY_OR_DEBUG_ASSERT(!util_isnan(fractionalPos)) {
606 return;
607 }
608 double newSamplePosition = fractionalPos * m_pTrackSamples->get();
609 doSeekPlayPos(newSamplePosition, seekType);
610 }
611
doSeekPlayPos(double new_playpos,enum SeekRequest seekType)612 void EngineBuffer::doSeekPlayPos(double new_playpos, enum SeekRequest seekType) {
613 #ifdef __VINYLCONTROL__
614 // Notify the vinyl control that a seek has taken place in case it is in
615 // absolute mode and needs be switched to relative.
616 if (m_pVinylControlControl) {
617 m_pVinylControlControl->notifySeekQueued();
618 }
619 #endif
620
621 queueNewPlaypos(new_playpos, seekType);
622 }
623
updateIndicatorsAndModifyPlay(bool newPlay,bool oldPlay)624 bool EngineBuffer::updateIndicatorsAndModifyPlay(bool newPlay, bool oldPlay) {
625 // If no track is currently loaded, turn play off. If a track is loading
626 // allow the set since it might apply to a track we are loading due to the
627 // asynchrony.
628 bool playPossible = true;
629 const QueuedSeek queuedSeek = m_queuedSeek.getValue();
630 if ((!m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0) ||
631 (m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0 &&
632 m_filepos_play >= m_pTrackSamples->get() &&
633 queuedSeek.seekType == SEEK_NONE) ||
634 m_pPassthroughEnabled->toBool()) {
635 // play not possible
636 playPossible = false;
637 }
638
639 return m_pCueControl->updateIndicatorsAndModifyPlay(newPlay, oldPlay, playPossible);
640 }
641
verifyPlay()642 void EngineBuffer::verifyPlay() {
643 bool play = m_playButton->toBool();
644 bool verifiedPlay = updateIndicatorsAndModifyPlay(play, play);
645 if (play != verifiedPlay) {
646 m_playButton->setAndConfirm(verifiedPlay ? 1.0 : 0.0);
647 }
648 }
649
slotControlPlayRequest(double v)650 void EngineBuffer::slotControlPlayRequest(double v) {
651 bool oldPlay = m_playButton->toBool();
652 bool verifiedPlay = updateIndicatorsAndModifyPlay(v > 0.0, oldPlay);
653
654 if (!oldPlay && verifiedPlay) {
655 if (m_pQuantize->toBool()
656 #ifdef __VINYLCONTROL__
657 && m_pVinylControlControl && !m_pVinylControlControl->isEnabled()
658 #endif
659 ) {
660 requestSyncPhase();
661 }
662 }
663
664 // set and confirm must be called here in any case to update the widget toggle state
665 m_playButton->setAndConfirm(verifiedPlay ? 1.0 : 0.0);
666 }
667
slotControlStart(double v)668 void EngineBuffer::slotControlStart(double v)
669 {
670 if (v > 0.0) {
671 doSeekFractional(0., SEEK_EXACT);
672 }
673 }
674
slotControlEnd(double v)675 void EngineBuffer::slotControlEnd(double v)
676 {
677 if (v > 0.0) {
678 doSeekFractional(1., SEEK_EXACT);
679 }
680 }
681
slotControlPlayFromStart(double v)682 void EngineBuffer::slotControlPlayFromStart(double v)
683 {
684 if (v > 0.0) {
685 doSeekFractional(0., SEEK_EXACT);
686 m_playButton->set(1);
687 }
688 }
689
slotControlJumpToStartAndStop(double v)690 void EngineBuffer::slotControlJumpToStartAndStop(double v)
691 {
692 if (v > 0.0) {
693 doSeekFractional(0., SEEK_EXACT);
694 m_playButton->set(0);
695 }
696 }
697
slotControlStop(double v)698 void EngineBuffer::slotControlStop(double v)
699 {
700 if (v > 0.0) {
701 m_playButton->set(0);
702 }
703 }
704
slotKeylockEngineChanged(double dIndex)705 void EngineBuffer::slotKeylockEngineChanged(double dIndex) {
706 if (m_bScalerOverride) {
707 return;
708 }
709 // static_cast<KeylockEngine>(dIndex); direct cast produces a "not used" warning with gcc
710 int iEngine = static_cast<int>(dIndex);
711 KeylockEngine engine = static_cast<KeylockEngine>(iEngine);
712 if (engine == SOUNDTOUCH) {
713 m_pScaleKeylock = m_pScaleST;
714 } else {
715 m_pScaleKeylock = m_pScaleRB;
716 }
717 }
718
processTrackLocked(CSAMPLE * pOutput,const int iBufferSize,int sample_rate)719 void EngineBuffer::processTrackLocked(
720 CSAMPLE* pOutput, const int iBufferSize, int sample_rate) {
721 ScopedTimer t("EngineBuffer::process_pauselock");
722
723 m_trackSampleRateOld = m_pTrackSampleRate->get();
724 m_trackSamplesOld = m_pTrackSamples->get();
725
726 double baserate = 0.0;
727 if (sample_rate > 0) {
728 baserate = m_trackSampleRateOld / sample_rate;
729 }
730
731 // Sync requests can affect rate, so process those first.
732 processSyncRequests();
733
734 // Note: play is also active during cue preview
735 bool paused = !m_playButton->toBool();
736 KeyControl::PitchTempoRatio pitchTempoRatio = m_pKeyControl->getPitchTempoRatio();
737
738 // The pitch adjustment in Ratio (1.0 being normal
739 // pitch. 2.0 is a full octave shift up).
740 double pitchRatio = pitchTempoRatio.pitchRatio;
741 double tempoRatio = pitchTempoRatio.tempoRatio;
742 const bool keylock_enabled = pitchTempoRatio.keylock;
743
744 bool is_scratching = false;
745 bool is_reverse = false;
746
747 // Update the slipped position and seek if it was disabled.
748 processSlip(iBufferSize);
749
750 // Note: This may effects the m_filepos_play, play, scaler and crossfade buffer
751 processSeek(paused);
752
753 // speed is the ratio between track-time and real-time
754 // (1.0 being normal rate. 2.0 plays at 2x speed -- 2 track seconds
755 // pass for every 1 real second). Depending on whether
756 // keylock is enabled, this is applied to either the rate or the tempo.
757 double speed = m_pRateControl->calculateSpeed(
758 baserate,
759 tempoRatio,
760 paused,
761 iBufferSize,
762 &is_scratching,
763 &is_reverse);
764
765 bool useIndependentPitchAndTempoScaling = false;
766
767 // TODO(owen): Maybe change this so that rubberband doesn't disable
768 // keylock on scratch. (just check m_pScaleKeylock == m_pScaleST)
769 if (is_scratching || fabs(speed) > 1.9) {
770 // Scratching and high speeds with always disables keylock
771 // because Soundtouch sounds terrible in these conditions. Rubberband
772 // sounds better, but still has some problems (it may reallocate in
773 // a party-crashing manner at extremely slow speeds).
774 // High seek speeds also disables keylock. Our pitch slider could go
775 // to 90%, so that's the cutoff point.
776
777 // Force pitchRatio to the linear pitch set by speed
778 pitchRatio = speed;
779 // This is for the natural speed pitch found on turn tables
780 } else if (fabs(speed) < 0.1) {
781 // We have pre-allocated big buffers in Rubberband and Soundtouch for
782 // a minimum speed of 0.1. Slower speeds will re-allocate much bigger
783 // buffers which may cause underruns.
784 // Disable keylock under these conditions.
785
786 // Force pitchRatio to the linear pitch set by speed
787 pitchRatio = speed;
788 } else if (keylock_enabled) {
789 // always use IndependentPitchAndTempoScaling
790 // to avoid clicks when crossing the linear pitch
791 // in this case it is most likely that the user
792 // will have an non linear pitch
793 // Note: We have undesired noise when cossfading between scalers
794 useIndependentPitchAndTempoScaling = true;
795 } else {
796 // We might have have temporary speed change, so adjust pitch if not locked
797 // Note: This will not update key and tempo widgets
798 if (tempoRatio != 0) {
799 pitchRatio *= (speed / tempoRatio);
800 }
801
802 // Check if we are off-linear (musical key has been adjusted
803 // independent from speed) to determine if the keylock scaler
804 // should be used even though keylock is disabled.
805 if (speed != 0.0) {
806 double offlinear = pitchRatio / speed;
807 if (offlinear > kLinearScalerElipsis ||
808 offlinear < 1 / kLinearScalerElipsis) {
809 // only enable keylock scaler if pitch adjustment is at
810 // least 1 cent. Everything below is not hear-able.
811 useIndependentPitchAndTempoScaling = true;
812 }
813 }
814 }
815
816 if (speed != 0.0) {
817 // Do not switch scaler when we have no transport
818 enableIndependentPitchTempoScaling(useIndependentPitchAndTempoScaling,
819 iBufferSize);
820 } else if (m_speed_old != 0 && !is_scratching) {
821 // we are stopping, collect samples for fade out
822 readToCrossfadeBuffer(iBufferSize);
823 // Clear the scaler information
824 m_pScale->clear();
825 }
826
827 // How speed/tempo/pitch are related:
828 // Processing is done in two parts, the first part is calculated inside
829 // the KeyKontrol class and effects the visual key/pitch widgets.
830 // The Speed slider controls the tempoRatio and a speedSliderPitchRatio,
831 // the pitch amount caused by it.
832 // By default the speed slider controls pitch and tempo with the same
833 // value.
834 // If key lock is enabled, the speedSliderPitchRatio is decoupled from
835 // the speed slider (const).
836 //
837 // With preference mode KeylockMode = kLockOriginalKey
838 // the speedSliderPitchRatio is reset to 1 and back to the tempoRatio
839 // (natural vinyl Pitch) when keylock is disabled and enabled.
840 //
841 // With preference mode KeylockMode = kCurrentKey
842 // the speedSliderPitchRatio is not reset when keylock is enabled.
843 // This mode allows to enable keylock
844 // while the track is already played. You can reset to the tracks
845 // original pitch by resetting the pitch knob to center. When disabling
846 // keylock the pitch is reset to the linear vinyl pitch.
847
848 // The Pitch knob turns if the speed slider is moved without keylock.
849 // This is useful to get always an analog impression of current pitch,
850 // and its distance to the original track pitch
851 //
852 // The Pitch_Adjust knob does not reflect the speedSliderPitchRatio.
853 // So it is is useful for controller mappings, because it is not
854 // changed by the speed slider or keylock.
855
856 // In the second part all other speed changing controls are processed.
857 // They may produce an additional pitch if keylock is disabled or
858 // override the pitch in scratching case.
859 // If pitch ratio and tempo ratio are equal, a linear scaler is used,
860 // otherwise tempo and pitch are processed individual
861
862 // If we were scratching, and scratching is over, and we're a follower,
863 // and we're quantized, and not paused,
864 // we need to sync phase or we'll be totally out of whack and the sync
865 // adjuster will kick in and push the track back in to sync with the
866 // master.
867 if (m_scratching_old && !is_scratching && m_pQuantize->toBool()
868 && m_pSyncControl->getSyncMode() == SYNC_FOLLOWER && !paused) {
869 // TODO() The resulting seek is processed in the following callback
870 // That is to late
871 requestSyncPhase();
872 }
873
874 double rate = 0;
875 // If the baserate, speed, or pitch has changed, we need to update the
876 // scaler. Also, if we have changed scalers then we need to update the
877 // scaler.
878 if (baserate != m_baserate_old || speed != m_speed_old ||
879 pitchRatio != m_pitch_old || tempoRatio != m_tempo_ratio_old ||
880 m_bScalerChanged) {
881 // The rate returned by the scale object can be different from the
882 // wanted rate! Make sure new scaler has proper position. This also
883 // crossfades between the old scaler and new scaler to prevent
884 // clicks.
885
886 // Handle direction change.
887 // The linear scaler supports ramping though zero.
888 // This is used for scratching, but not for reverse
889 // For the other, crossfade forward and backward samples
890 if ((m_speed_old * speed < 0) && // Direction has changed!
891 (m_pScale != m_pScaleVinyl || // only m_pScaleLinear supports going though 0
892 m_reverse_old != is_reverse)) { // no pitch change when reversing
893 //XXX: Trying to force RAMAN to read from correct
894 // playpos when rate changes direction - Albert
895 readToCrossfadeBuffer(iBufferSize);
896 // Clear the scaler information
897 m_pScale->clear();
898 }
899
900 m_baserate_old = baserate;
901 m_speed_old = speed;
902 m_pitch_old = pitchRatio;
903 m_tempo_ratio_old = tempoRatio;
904 m_reverse_old = is_reverse;
905
906 // Now we need to update the scaler with the master sample rate, the
907 // base rate (ratio between sample rate of the source audio and the
908 // master samplerate), the deck speed, the pitch shift, and whether
909 // the deck speed should affect the pitch.
910
911 m_pScale->setScaleParameters(baserate,
912 &speed,
913 &pitchRatio);
914
915 // The way we treat rate inside of EngineBuffer is actually a
916 // description of "sample consumption rate" or percentage of samples
917 // consumed relative to playing back the track at its native sample
918 // rate and normal speed. pitch_adjust does not change the playback
919 // rate.
920 rate = baserate * speed;
921
922 // Scaler is up to date now.
923 m_bScalerChanged = false;
924 } else {
925 // Scaler did not need updating. By definition this means we are at
926 // our old rate.
927 rate = m_rate_old;
928 }
929
930 bool at_end = m_filepos_play >= m_trackSamplesOld;
931 bool backwards = rate < 0;
932
933 bool bCurBufferPaused = false;
934 if (at_end && !backwards) {
935 // do not play past end
936 bCurBufferPaused = true;
937 } else if (rate == 0 && !is_scratching) {
938 // do not process samples if have no transport
939 // the linear scaler supports ramping down to 0
940 // this is used for pause by scratching only
941 bCurBufferPaused = true;
942 }
943
944 m_rate_old = rate;
945
946 // If the buffer is not paused, then scale the audio.
947 if (!bCurBufferPaused) {
948 // Perform scaling of Reader buffer into buffer.
949 double framesRead =
950 m_pScale->scaleBuffer(pOutput, iBufferSize);
951
952 // TODO(XXX): The result framesRead might not be an integer value.
953 // Converting to samples here does not make sense. All positional
954 // calculations should be done in frames instead of samples! Otherwise
955 // rounding errors might occur when converting from samples back to
956 // frames later.
957 double samplesRead = framesRead * kSamplesPerFrame;
958
959 if (m_bScalerOverride) {
960 // If testing, we don't have a real log so we fake the position.
961 m_filepos_play += samplesRead;
962 } else {
963 // Adjust filepos_play by the amount we processed.
964 m_filepos_play =
965 m_pReadAheadManager->getFilePlaypositionFromLog(
966 m_filepos_play, samplesRead);
967 }
968 // Note: The last buffer of a track is padded with silence.
969 // This silence is played together with the last samples in the last
970 // callback and the m_filepos_play is advanced behind the end of the track.
971
972 if (m_bCrossfadeReady) {
973 // Bring pOutput with the new parameters in and fade out the old one,
974 // stored with the old parameters in m_pCrossfadeBuffer
975 SampleUtil::linearCrossfadeBuffersIn(
976 pOutput, m_pCrossfadeBuffer, iBufferSize);
977 }
978 // Note: we do not fade here if we pass the end or the start of
979 // the track in reverse direction
980 // because we assume that the track samples itself start and stop
981 // towards zero.
982 // If it turns out that ramping is required be aware that the end
983 // or start may pass in the middle of the buffer.
984 } else {
985 // Pause
986 if (m_bCrossfadeReady) {
987 // We don't ramp here, since EnginePregain handles fades
988 // from and to speed == 0
989 SampleUtil::copy(pOutput, m_pCrossfadeBuffer, iBufferSize);
990 } else {
991 SampleUtil::clear(pOutput, iBufferSize);
992 }
993 }
994
995 for (const auto& pControl: qAsConst(m_engineControls)) {
996 pControl->setCurrentSample(m_filepos_play, m_trackSamplesOld, m_trackSampleRateOld);
997 pControl->process(rate, m_filepos_play, iBufferSize);
998 }
999
1000 m_scratching_old = is_scratching;
1001
1002 // Handle repeat mode
1003 bool at_start = m_filepos_play <= 0;
1004 at_end = m_filepos_play >= m_trackSamplesOld;
1005
1006 bool repeat_enabled = m_pRepeat->toBool();
1007
1008 bool end_of_track = //(at_start && backwards) ||
1009 (at_end && !backwards);
1010
1011 // If playbutton is pressed, check if we are at start or end of track
1012 if ((m_playButton->toBool() || (m_fwdButton->toBool() || m_backButton->toBool()))
1013 && end_of_track) {
1014 if (repeat_enabled) {
1015 double fractionalPos = at_start ? 1.0 : 0;
1016 doSeekFractional(fractionalPos, SEEK_STANDARD);
1017 } else {
1018 m_playButton->set(0.);
1019 }
1020 }
1021
1022 // Give the Reader hints as to which chunks of the current song we
1023 // really care about. It will try very hard to keep these in memory
1024 hintReader(rate);
1025 }
1026
process(CSAMPLE * pOutput,const int iBufferSize)1027 void EngineBuffer::process(CSAMPLE* pOutput, const int iBufferSize) {
1028 // Bail if we receive a buffer size with incomplete sample frames. Assert in debug builds.
1029 VERIFY_OR_DEBUG_ASSERT((iBufferSize % kSamplesPerFrame) == 0) {
1030 return;
1031 }
1032 m_pReader->process();
1033 // Steps:
1034 // - Lookup new reader information
1035 // - Calculate current rate
1036 // - Scale the audio with m_pScale, copy the resulting samples into the
1037 // output buffer
1038 // - Give EngineControl's a chance to do work / request seeks, etc
1039 // - Process repeat mode if we're at the end or beginning of a track
1040 // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other
1041 // miscellaneous upkeep issues.
1042
1043 m_iSampleRate = static_cast<int>(m_pSampleRate->get());
1044
1045 // If the sample rate has changed, force Rubberband to reset so that
1046 // it doesn't reallocate when the user engages keylock during playback.
1047 // We do this even if rubberband is not active.
1048 const auto sampleRate = mixxx::audio::SampleRate(m_iSampleRate);
1049 m_pScaleLinear->setSampleRate(sampleRate);
1050 m_pScaleST->setSampleRate(sampleRate);
1051 m_pScaleRB->setSampleRate(sampleRate);
1052
1053 bool bTrackLoading = atomicLoadRelaxed(m_iTrackLoading) != 0;
1054 if (!bTrackLoading && m_pause.tryLock()) {
1055 processTrackLocked(pOutput, iBufferSize, m_iSampleRate);
1056 // release the pauselock
1057 m_pause.unlock();
1058 } else {
1059 // We are loading a new Track
1060
1061 // Here the old track was playing and loading the new track is in
1062 // progress. We can't predict when it happens, so we are not able
1063 // to collect old samples. New samples are also not in place and
1064 // we can't predict when they will be in place.
1065 // If one does this, a click from breaking the last track is somehow
1066 // natural and he should know that such sound should not be played to
1067 // the master (audience).
1068 // Workaround: Simply pause the track before.
1069
1070 // TODO(XXX):
1071 // A click free solution requires more refactoring how loading a track
1072 // is handled. For now we apply a rectangular Gain change here which
1073 // may click.
1074
1075 SampleUtil::clear(pOutput, iBufferSize);
1076
1077 m_rate_old = 0;
1078 m_speed_old = 0;
1079 m_scratching_old = false;
1080 }
1081
1082 #ifdef __SCALER_DEBUG__
1083 for (int i=0; i<iBufferSize; i+=2) {
1084 writer << pOutput[i] << "\n";
1085 }
1086 #endif
1087
1088 if (isMaster(m_pSyncControl->getSyncMode())) {
1089 // Report our speed to SyncControl immediately instead of waiting
1090 // for postProcess so we can broadcast this update to followers.
1091 m_pSyncControl->reportPlayerSpeed(m_speed_old, m_scratching_old);
1092 }
1093
1094 m_iLastBufferSize = iBufferSize;
1095 m_bCrossfadeReady = false;
1096 }
1097
processSlip(int iBufferSize)1098 void EngineBuffer::processSlip(int iBufferSize) {
1099 // Do a single read from m_bSlipEnabled so we don't run in to race conditions.
1100 bool enabled = m_pSlipButton->toBool();
1101 if (enabled != m_bSlipEnabledProcessing) {
1102 m_bSlipEnabledProcessing = enabled;
1103 if (enabled) {
1104 m_dSlipPosition = m_filepos_play;
1105 m_dSlipRate = m_rate_old;
1106 } else {
1107 // TODO(owen) assuming that looping will get canceled properly
1108 double newPlayFrame = m_dSlipPosition / kSamplesPerFrame;
1109 double roundedSlip = round(newPlayFrame) * kSamplesPerFrame;
1110 slotControlSeekExact(roundedSlip);
1111 m_dSlipPosition = 0;
1112 }
1113 }
1114
1115 // Increment slip position even if it was just toggled -- this ensures the position is correct.
1116 if (enabled) {
1117 m_dSlipPosition += static_cast<double>(iBufferSize) * m_dSlipRate;
1118 }
1119 }
1120
processSyncRequests()1121 void EngineBuffer::processSyncRequests() {
1122 SyncRequestQueued enable_request =
1123 static_cast<SyncRequestQueued>(
1124 m_iEnableSyncQueued.fetchAndStoreRelease(SYNC_REQUEST_NONE));
1125 SyncMode mode_request =
1126 static_cast<SyncMode>(m_iSyncModeQueued.fetchAndStoreRelease(SYNC_INVALID));
1127 switch (enable_request) {
1128 case SYNC_REQUEST_ENABLE:
1129 m_pEngineSync->requestEnableSync(m_pSyncControl, true);
1130 break;
1131 case SYNC_REQUEST_DISABLE:
1132 m_pEngineSync->requestEnableSync(m_pSyncControl, false);
1133 break;
1134 case SYNC_REQUEST_ENABLEDISABLE:
1135 m_pEngineSync->requestEnableSync(m_pSyncControl, true);
1136 m_pEngineSync->requestEnableSync(m_pSyncControl, false);
1137 break;
1138 case SYNC_REQUEST_NONE:
1139 break;
1140 }
1141 if (mode_request != SYNC_INVALID) {
1142 m_pEngineSync->requestSyncMode(m_pSyncControl,
1143 static_cast<SyncMode>(mode_request));
1144 }
1145 }
1146
processSeek(bool paused)1147 void EngineBuffer::processSeek(bool paused) {
1148 // Check if we are cloning another channel before doing any seeking.
1149 EngineChannel* pChannel = m_pChannelToCloneFrom.fetchAndStoreRelaxed(nullptr);
1150 if (pChannel) {
1151 seekCloneBuffer(pChannel->getEngineBuffer());
1152 }
1153
1154 const QueuedSeek queuedSeek = m_queuedSeek.getValue();
1155
1156 SeekRequests seekType = queuedSeek.seekType;
1157 double position = queuedSeek.position;
1158
1159 // Don't allow the playposition to go past the end.
1160 if (position > m_trackSamplesOld) {
1161 position = m_trackSamplesOld;
1162 }
1163
1164 // Add SEEK_PHASE bit, if any
1165 if (m_iSeekPhaseQueued.fetchAndStoreRelease(0)) {
1166 seekType |= SEEK_PHASE;
1167 }
1168
1169 switch (seekType) {
1170 case SEEK_NONE:
1171 return;
1172 case SEEK_PHASE:
1173 // only adjust phase
1174 position = m_filepos_play;
1175 break;
1176 case SEEK_STANDARD:
1177 if (m_pQuantize->toBool()) {
1178 seekType |= SEEK_PHASE;
1179 }
1180 // new position was already set above
1181 break;
1182 case SEEK_EXACT:
1183 case SEEK_EXACT_PHASE: // artificial state = SEEK_EXACT | SEEK_PHASE
1184 case SEEK_STANDARD_PHASE: // artificial state = SEEK_STANDARD | SEEK_PHASE
1185 // new position was already set above
1186 break;
1187 default:
1188 DEBUG_ASSERT(!"Unhandled seek request type");
1189 m_queuedSeek.setValue(kNoQueuedSeek);
1190 return;
1191 }
1192
1193 if (!paused && (seekType & SEEK_PHASE)) {
1194 if (kLogger.traceEnabled()) {
1195 kLogger.trace() << "EngineBuffer::processSeek Seeking phase";
1196 }
1197 double requestedPosition = position;
1198 double syncPosition = m_pBpmControl->getBeatMatchPosition(position, true, true);
1199 position = m_pLoopingControl->getSyncPositionInsideLoop(requestedPosition, syncPosition);
1200 if (kLogger.traceEnabled()) {
1201 kLogger.trace()
1202 << "EngineBuffer::processSeek seek info: " << m_filepos_play
1203 << " -> " << position;
1204 }
1205 }
1206 if (position != m_filepos_play) {
1207 if (kLogger.traceEnabled()) {
1208 kLogger.trace() << "EngineBuffer::processSeek Seek to" << position;
1209 }
1210 setNewPlaypos(position);
1211 }
1212 // Reset the m_queuedSeek value after it has been processed in
1213 // setNewPlaypos() so that the Engine Controls have always access to the
1214 // position of the upcoming buffer cycle (used for loop cues)
1215 m_queuedSeek.setValue(kNoQueuedSeek);
1216 }
1217
postProcess(const int iBufferSize)1218 void EngineBuffer::postProcess(const int iBufferSize) {
1219 // The order of events here is very delicate. It's necessary to update
1220 // some values before others, because the later updates may require
1221 // values from the first update.
1222 if (kLogger.traceEnabled()) {
1223 kLogger.trace() << getGroup() << "EngineBuffer::postProcess";
1224 }
1225 double local_bpm = m_pBpmControl->updateLocalBpm();
1226 double beat_distance = m_pBpmControl->updateBeatDistance();
1227 m_pSyncControl->setLocalBpm(local_bpm);
1228 m_pSyncControl->updateAudible();
1229 SyncMode mode = m_pSyncControl->getSyncMode();
1230 if (isMaster(mode)) {
1231 m_pEngineSync->notifyBeatDistanceChanged(m_pSyncControl, beat_distance);
1232 } else if (mode == SYNC_FOLLOWER) {
1233 // Report our speed to SyncControl. If we are master, we already did this.
1234 m_pSyncControl->reportPlayerSpeed(m_speed_old, m_scratching_old);
1235 m_pSyncControl->updateTargetBeatDistance();
1236 }
1237
1238 // Update all the indicators that EngineBuffer publishes to allow
1239 // external parts of Mixxx to observe its status.
1240 updateIndicators(m_speed_old, iBufferSize);
1241 }
1242
updateIndicators(double speed,int iBufferSize)1243 void EngineBuffer::updateIndicators(double speed, int iBufferSize) {
1244 if (m_trackSampleRateOld == 0) {
1245 // This happens if Deck Passthrough is active but no track is loaded.
1246 // We skip indicator updates.
1247 return;
1248 }
1249
1250 // Increase samplesCalculated by the buffer size
1251 m_iSamplesSinceLastIndicatorUpdate += iBufferSize;
1252
1253 const double fFractionalPlaypos = fractionalPlayposFromAbsolute(m_filepos_play);
1254
1255 const double tempoTrackSeconds = m_trackSamplesOld / kSamplesPerFrame
1256 / m_trackSampleRateOld / m_tempo_ratio_old;
1257 if(speed > 0 && fFractionalPlaypos == 1.0) {
1258 // At Track end
1259 speed = 0;
1260 }
1261
1262 // Report fractional playpos to SyncControl.
1263 // TODO(rryan) It's kind of hacky that this is in updateIndicators but it
1264 // prevents us from computing fFractionalPlaypos multiple times per
1265 // EngineBuffer::process().
1266 m_pSyncControl->reportTrackPosition(fFractionalPlaypos);
1267
1268 // Update indicators that are only updated after every
1269 // sampleRate/kiUpdateRate samples processed. (e.g. playposSlider)
1270 if (m_iSamplesSinceLastIndicatorUpdate >
1271 (kSamplesPerFrame * m_pSampleRate->get() /
1272 kPlaypositionUpdateRate)) {
1273 m_playposSlider->set(fFractionalPlaypos);
1274 m_pCueControl->updateIndicators();
1275 }
1276
1277 // Update visual control object, this needs to be done more often than the
1278 // playpos slider
1279 m_visualPlayPos->set(fFractionalPlaypos, speed * m_baserate_old,
1280 (double)iBufferSize / m_trackSamplesOld,
1281 fractionalPlayposFromAbsolute(m_dSlipPosition),
1282 tempoTrackSeconds);
1283 }
1284
hintReader(const double dRate)1285 void EngineBuffer::hintReader(const double dRate) {
1286 m_hintList.clear();
1287 m_pReadAheadManager->hintReader(dRate, &m_hintList);
1288
1289 //if slipping, hint about virtual position so we're ready for it
1290 if (m_bSlipEnabledProcessing) {
1291 Hint hint;
1292 hint.frame = SampleUtil::floorPlayPosToFrame(m_dSlipPosition);
1293 hint.priority = 1;
1294 if (m_dSlipRate >= 0) {
1295 hint.frameCount = Hint::kFrameCountForward;
1296 } else {
1297 hint.frameCount = Hint::kFrameCountBackward;
1298 }
1299 m_hintList.append(hint);
1300 }
1301
1302 for (const auto& pControl: qAsConst(m_engineControls)) {
1303 pControl->hintReader(&m_hintList);
1304 }
1305 m_pReader->hintAndMaybeWake(m_hintList);
1306 }
1307
1308 // WARNING: This method runs in the GUI thread
loadTrack(TrackPointer pTrack,bool play)1309 void EngineBuffer::loadTrack(TrackPointer pTrack, bool play) {
1310 if (pTrack) {
1311 // Signal to the reader to load the track. The reader will respond with
1312 // trackLoading and then either with trackLoaded or trackLoadFailed signals.
1313 m_bPlayAfterLoading = play;
1314 m_pReader->newTrack(pTrack);
1315 } else {
1316 // Loading a null track means "eject"
1317 ejectTrack();
1318 }
1319 }
1320
addControl(EngineControl * pControl)1321 void EngineBuffer::addControl(EngineControl* pControl) {
1322 // Connect to signals from EngineControl here...
1323 m_engineControls.push_back(pControl);
1324 pControl->setEngineBuffer(this);
1325 }
1326
bindWorkers(EngineWorkerScheduler * pWorkerScheduler)1327 void EngineBuffer::bindWorkers(EngineWorkerScheduler* pWorkerScheduler) {
1328 m_pReader->setScheduler(pWorkerScheduler);
1329 }
1330
isTrackLoaded()1331 bool EngineBuffer::isTrackLoaded() {
1332 if (m_pCurrentTrack) {
1333 return true;
1334 }
1335 return false;
1336 }
1337
getQueuedSeekPosition(double * pSeekPosition)1338 bool EngineBuffer::getQueuedSeekPosition(double* pSeekPosition) {
1339 const QueuedSeek queuedSeek = m_queuedSeek.getValue();
1340 *pSeekPosition = queuedSeek.position;
1341 return (queuedSeek.seekType != SEEK_NONE);
1342 }
1343
slotEjectTrack(double v)1344 void EngineBuffer::slotEjectTrack(double v) {
1345 if (v > 0) {
1346 // Don't allow rejections while playing a track. We don't need to lock to
1347 // call ControlObject::get() so this is fine.
1348 if (m_playButton->get() > 0) {
1349 return;
1350 }
1351 ejectTrack();
1352 }
1353 }
1354
getExactPlayPos()1355 double EngineBuffer::getExactPlayPos() {
1356 double visualPlayPos = getVisualPlayPos();
1357 if (visualPlayPos > 0) {
1358 return getVisualPlayPos() * getTrackSamples();
1359 } else {
1360 // Track was just loaded and the first buffer was not processed yet.
1361 // assume it is at 0:00
1362 return 0.0;
1363 }
1364 }
1365
getVisualPlayPos()1366 double EngineBuffer::getVisualPlayPos() {
1367 return m_visualPlayPos->getEnginePlayPos();
1368 }
1369
getTrackSamples()1370 double EngineBuffer::getTrackSamples() {
1371 return m_pTrackSamples->get();
1372 }
1373
setScalerForTest(EngineBufferScale * pScaleVinyl,EngineBufferScale * pScaleKeylock)1374 void EngineBuffer::setScalerForTest(EngineBufferScale* pScaleVinyl,
1375 EngineBufferScale* pScaleKeylock) {
1376 m_pScaleVinyl = pScaleVinyl;
1377 m_pScaleKeylock = pScaleKeylock;
1378 m_pScale = m_pScaleVinyl;
1379 m_pScale->clear();
1380 m_bScalerChanged = true;
1381 // This bool is permanently set and can't be undone.
1382 m_bScalerOverride = true;
1383 }
1384
collectFeatures(GroupFeatureState * pGroupFeatures) const1385 void EngineBuffer::collectFeatures(GroupFeatureState* pGroupFeatures) const {
1386 if (m_pBpmControl != nullptr) {
1387 m_pBpmControl->collectFeatures(pGroupFeatures);
1388 }
1389 }
1390
getRateRatio() const1391 double EngineBuffer::getRateRatio() const {
1392 if (m_pBpmControl != nullptr) {
1393 return m_pBpmControl->getRateRatio();
1394 }
1395 return 1.0;
1396 }
1397
notifyTrackLoaded(TrackPointer pNewTrack,TrackPointer pOldTrack)1398 void EngineBuffer::notifyTrackLoaded(
1399 TrackPointer pNewTrack, TrackPointer pOldTrack) {
1400 if (pOldTrack) {
1401 disconnect(
1402 pOldTrack.get(),
1403 &Track::beatsUpdated,
1404 this,
1405 &EngineBuffer::slotUpdatedTrackBeats);
1406 }
1407
1408 // First inform engineControls directly
1409 // Note: we are still in a worker thread.
1410 for (const auto& pControl: qAsConst(m_engineControls)) {
1411 pControl->trackLoaded(pNewTrack);
1412 pControl->setCurrentSample(m_filepos_play,
1413 m_pTrackSamples->get(),
1414 m_pTrackSampleRate->get());
1415 }
1416
1417 if (pNewTrack) {
1418 connect(
1419 pNewTrack.get(),
1420 &Track::beatsUpdated,
1421 this,
1422 &EngineBuffer::slotUpdatedTrackBeats,
1423 Qt::DirectConnection);
1424 }
1425
1426 // Inform BaseTrackPlayer via a queued connection
1427 emit trackLoaded(pNewTrack, pOldTrack);
1428 }
1429
slotUpdatedTrackBeats()1430 void EngineBuffer::slotUpdatedTrackBeats() {
1431 TrackPointer pTrack = m_pCurrentTrack;
1432 if (pTrack) {
1433 for (const auto& pControl : qAsConst(m_engineControls)) {
1434 pControl->trackBeatsUpdated(pTrack->getBeats());
1435 }
1436 }
1437 }
1438