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