1 #include "engine/controls/cuecontrol.h"
2 
3 #include <QMutexLocker>
4 
5 #include "control/controlindicator.h"
6 #include "control/controlobject.h"
7 #include "control/controlpushbutton.h"
8 #include "engine/enginebuffer.h"
9 #include "moc_cuecontrol.cpp"
10 #include "preferences/colorpalettesettings.h"
11 #include "track/track.h"
12 #include "util/color/color.h"
13 #include "util/color/predefinedcolorpalettes.h"
14 #include "util/sample.h"
15 #include "vinylcontrol/defs_vinylcontrol.h"
16 
17 namespace {
18 
19 // TODO: Convert these doubles to a standard enum
20 // and convert elseif logic to switch statements
21 constexpr double CUE_MODE_MIXXX = 0.0;
22 constexpr double CUE_MODE_PIONEER = 1.0;
23 constexpr double CUE_MODE_DENON = 2.0;
24 constexpr double CUE_MODE_NUMARK = 3.0;
25 constexpr double CUE_MODE_MIXXX_NO_BLINK = 4.0;
26 constexpr double CUE_MODE_CUP = 5.0;
27 
28 /// This is the position of a fresh loaded tack without any seek
29 constexpr double kDefaultLoadPosition = 0.0;
30 constexpr int kNoHotCueNumber = 0;
31 
32 // Helper function to convert control values (i.e. doubles) into RgbColor
33 // instances (or nullopt if value < 0). This happens by using the integer
34 // component as RGB color codes (e.g. 0xFF0000).
doubleToRgbColor(double value)35 inline mixxx::RgbColor::optional_t doubleToRgbColor(double value) {
36     if (value < 0) {
37         return std::nullopt;
38     }
39     auto colorCode = static_cast<mixxx::RgbColor::code_t>(value);
40     if (value != mixxx::RgbColor::validateCode(colorCode)) {
41         return std::nullopt;
42     }
43     return mixxx::RgbColor::optional(colorCode);
44 }
45 
46 /// Convert hot cue index to 1-based number
47 ///
48 /// Works independent of if the hot cue index is either 0-based
49 /// or 1..n-based.
hotcueIndexToHotcueNumber(int hotcueIndex)50 inline int hotcueIndexToHotcueNumber(int hotcueIndex) {
51     if (hotcueIndex >= mixxx::kFirstHotCueIndex) {
52         DEBUG_ASSERT(hotcueIndex != Cue::kNoHotCue);
53         return (hotcueIndex - mixxx::kFirstHotCueIndex) + 1; // to 1-based numbering
54     } else {
55         DEBUG_ASSERT(hotcueIndex == Cue::kNoHotCue);
56         return kNoHotCueNumber;
57     }
58 }
59 
60 /// Convert 1-based hot cue number to hot cue index.
61 ///
62 /// Works independent of if the hot cue index is either 0-based
63 /// or 1..n-based.
hotcueNumberToHotcueIndex(int hotcueNumber)64 inline int hotcueNumberToHotcueIndex(int hotcueNumber) {
65     if (hotcueNumber >= 1) {
66         DEBUG_ASSERT(hotcueNumber != kNoHotCueNumber);
67         return mixxx::kFirstHotCueIndex + (hotcueNumber - 1); // from 1-based numbering
68     } else {
69         DEBUG_ASSERT(hotcueNumber == kNoHotCueNumber);
70         return Cue::kNoHotCue;
71     }
72 }
73 
74 } // namespace
75 
CueControl(const QString & group,UserSettingsPointer pConfig)76 CueControl::CueControl(const QString& group,
77         UserSettingsPointer pConfig)
78         : EngineControl(group, pConfig),
79           m_pConfig(pConfig),
80           m_colorPaletteSettings(ColorPaletteSettings(pConfig)),
81           m_bPreviewing(false),
82           m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))),
83           m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))),
84           m_iCurrentlyPreviewingHotcues(0),
85           m_bypassCueSetByPlay(false),
86           m_iNumHotCues(NUM_HOT_CUES)
87 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
88           ,
89           m_mutex(QMutex::Recursive)
90 #endif
91 {
92     // To silence a compiler warning about CUE_MODE_PIONEER.
93     Q_UNUSED(CUE_MODE_PIONEER);
94     createControls();
95 
96     m_pTrackSamples = ControlObject::getControl(ConfigKey(group, "track_samples"));
97 
98     m_pQuantizeEnabled = ControlObject::getControl(ConfigKey(group, "quantize"));
99     connect(m_pQuantizeEnabled, &ControlObject::valueChanged,
100             this, &CueControl::quantizeChanged,
101             Qt::DirectConnection);
102 
103     m_pClosestBeat = ControlObject::getControl(ConfigKey(group, "beat_closest"));
104 
105     m_pCuePoint = new ControlObject(ConfigKey(group, "cue_point"));
106     m_pCuePoint->set(Cue::kNoPosition);
107 
108     m_pCueMode = new ControlObject(ConfigKey(group, "cue_mode"));
109 
110     m_pCueSet = new ControlPushButton(ConfigKey(group, "cue_set"));
111     m_pCueSet->setButtonMode(ControlPushButton::TRIGGER);
112     connect(m_pCueSet, &ControlObject::valueChanged,
113             this, &CueControl::cueSet,
114             Qt::DirectConnection);
115 
116     m_pCueClear = new ControlPushButton(ConfigKey(group, "cue_clear"));
117     m_pCueClear->setButtonMode(ControlPushButton::TRIGGER);
118     connect(m_pCueClear, &ControlObject::valueChanged,
119             this, &CueControl::cueClear,
120             Qt::DirectConnection);
121 
122     m_pCueGoto = new ControlPushButton(ConfigKey(group, "cue_goto"));
123     connect(m_pCueGoto, &ControlObject::valueChanged,
124             this, &CueControl::cueGoto,
125             Qt::DirectConnection);
126 
127     m_pCueGotoAndPlay =
128             new ControlPushButton(ConfigKey(group, "cue_gotoandplay"));
129     connect(m_pCueGotoAndPlay, &ControlObject::valueChanged,
130             this, &CueControl::cueGotoAndPlay,
131             Qt::DirectConnection);
132 
133     m_pCuePlay =
134             new ControlPushButton(ConfigKey(group, "cue_play"));
135     connect(m_pCuePlay, &ControlObject::valueChanged,
136             this, &CueControl::cuePlay,
137             Qt::DirectConnection);
138 
139     m_pCueGotoAndStop =
140             new ControlPushButton(ConfigKey(group, "cue_gotoandstop"));
141     connect(m_pCueGotoAndStop, &ControlObject::valueChanged,
142             this, &CueControl::cueGotoAndStop,
143             Qt::DirectConnection);
144 
145     m_pCuePreview = new ControlPushButton(ConfigKey(group, "cue_preview"));
146     connect(m_pCuePreview, &ControlObject::valueChanged,
147             this, &CueControl::cuePreview,
148             Qt::DirectConnection);
149 
150     m_pCueCDJ = new ControlPushButton(ConfigKey(group, "cue_cdj"));
151     connect(m_pCueCDJ, &ControlObject::valueChanged,
152             this, &CueControl::cueCDJ,
153             Qt::DirectConnection);
154 
155     m_pCueDefault = new ControlPushButton(ConfigKey(group, "cue_default"));
156     connect(m_pCueDefault, &ControlObject::valueChanged,
157             this, &CueControl::cueDefault,
158             Qt::DirectConnection);
159 
160     m_pPlayStutter = new ControlPushButton(ConfigKey(group, "play_stutter"));
161     connect(m_pPlayStutter, &ControlObject::valueChanged,
162             this, &CueControl::playStutter,
163             Qt::DirectConnection);
164 
165     m_pCueIndicator = new ControlIndicator(ConfigKey(group, "cue_indicator"));
166     m_pPlayIndicator = new ControlIndicator(ConfigKey(group, "play_indicator"));
167 
168     m_pPlayLatched = new ControlObject(ConfigKey(group, "play_latched"));
169     m_pPlayLatched->setReadOnly();
170 
171     m_pIntroStartPosition = new ControlObject(ConfigKey(group, "intro_start_position"));
172     m_pIntroStartPosition->set(Cue::kNoPosition);
173 
174     m_pIntroStartEnabled = new ControlObject(ConfigKey(group, "intro_start_enabled"));
175     m_pIntroStartEnabled->setReadOnly();
176 
177     m_pIntroStartSet = new ControlPushButton(ConfigKey(group, "intro_start_set"));
178     connect(m_pIntroStartSet, &ControlObject::valueChanged,
179             this, &CueControl::introStartSet,
180             Qt::DirectConnection);
181 
182     m_pIntroStartClear = new ControlPushButton(ConfigKey(group, "intro_start_clear"));
183     connect(m_pIntroStartClear, &ControlObject::valueChanged,
184             this, &CueControl::introStartClear,
185             Qt::DirectConnection);
186 
187     m_pIntroStartActivate = new ControlPushButton(ConfigKey(group, "intro_start_activate"));
188     connect(m_pIntroStartActivate, &ControlObject::valueChanged,
189             this, &CueControl::introStartActivate,
190             Qt::DirectConnection);
191 
192     m_pIntroEndPosition = new ControlObject(ConfigKey(group, "intro_end_position"));
193     m_pIntroEndPosition->set(Cue::kNoPosition);
194 
195     m_pIntroEndEnabled = new ControlObject(ConfigKey(group, "intro_end_enabled"));
196     m_pIntroEndEnabled->setReadOnly();
197 
198     m_pIntroEndSet = new ControlPushButton(ConfigKey(group, "intro_end_set"));
199     connect(m_pIntroEndSet, &ControlObject::valueChanged,
200             this, &CueControl::introEndSet,
201             Qt::DirectConnection);
202 
203     m_pIntroEndClear = new ControlPushButton(ConfigKey(group, "intro_end_clear"));
204     connect(m_pIntroEndClear, &ControlObject::valueChanged,
205             this, &CueControl::introEndClear,
206             Qt::DirectConnection);
207 
208     m_pIntroEndActivate = new ControlPushButton(ConfigKey(group, "intro_end_activate"));
209     connect(m_pIntroEndActivate, &ControlObject::valueChanged,
210             this, &CueControl::introEndActivate,
211             Qt::DirectConnection);
212 
213     m_pOutroStartPosition = new ControlObject(ConfigKey(group, "outro_start_position"));
214     m_pOutroStartPosition->set(Cue::kNoPosition);
215 
216     m_pOutroStartEnabled = new ControlObject(ConfigKey(group, "outro_start_enabled"));
217     m_pOutroStartEnabled->setReadOnly();
218 
219     m_pOutroStartSet = new ControlPushButton(ConfigKey(group, "outro_start_set"));
220     connect(m_pOutroStartSet, &ControlObject::valueChanged,
221             this, &CueControl::outroStartSet,
222             Qt::DirectConnection);
223 
224     m_pOutroStartClear = new ControlPushButton(ConfigKey(group, "outro_start_clear"));
225     connect(m_pOutroStartClear, &ControlObject::valueChanged,
226             this, &CueControl::outroStartClear,
227             Qt::DirectConnection);
228 
229     m_pOutroStartActivate = new ControlPushButton(ConfigKey(group, "outro_start_activate"));
230     connect(m_pOutroStartActivate, &ControlObject::valueChanged,
231             this, &CueControl::outroStartActivate,
232             Qt::DirectConnection);
233 
234     m_pOutroEndPosition = new ControlObject(ConfigKey(group, "outro_end_position"));
235     m_pOutroEndPosition->set(Cue::kNoPosition);
236 
237     m_pOutroEndEnabled = new ControlObject(ConfigKey(group, "outro_end_enabled"));
238     m_pOutroEndEnabled->setReadOnly();
239 
240     m_pOutroEndSet = new ControlPushButton(ConfigKey(group, "outro_end_set"));
241     connect(m_pOutroEndSet, &ControlObject::valueChanged,
242             this, &CueControl::outroEndSet,
243             Qt::DirectConnection);
244 
245     m_pOutroEndClear = new ControlPushButton(ConfigKey(group, "outro_end_clear"));
246     connect(m_pOutroEndClear, &ControlObject::valueChanged,
247             this, &CueControl::outroEndClear,
248             Qt::DirectConnection);
249 
250     m_pOutroEndActivate = new ControlPushButton(ConfigKey(group, "outro_end_activate"));
251     connect(m_pOutroEndActivate, &ControlObject::valueChanged,
252             this, &CueControl::outroEndActivate,
253             Qt::DirectConnection);
254 
255     m_pVinylControlEnabled = new ControlProxy(group, "vinylcontrol_enabled");
256     m_pVinylControlMode = new ControlProxy(group, "vinylcontrol_mode");
257 
258     m_pHotcueFocus = new ControlObject(ConfigKey(group, "hotcue_focus"));
259     setHotcueFocusIndex(Cue::kNoHotCue);
260 
261     m_pHotcueFocusColorPrev = new ControlObject(ConfigKey(group, "hotcue_focus_color_prev"));
262     connect(m_pHotcueFocusColorPrev,
263             &ControlObject::valueChanged,
264             this,
265             &CueControl::hotcueFocusColorPrev,
266             Qt::DirectConnection);
267 
268     m_pHotcueFocusColorNext = new ControlObject(ConfigKey(group, "hotcue_focus_color_next"));
269     connect(m_pHotcueFocusColorNext,
270             &ControlObject::valueChanged,
271             this,
272             &CueControl::hotcueFocusColorNext,
273             Qt::DirectConnection);
274 }
275 
~CueControl()276 CueControl::~CueControl() {
277     delete m_pCuePoint;
278     delete m_pCueMode;
279     delete m_pCueSet;
280     delete m_pCueClear;
281     delete m_pCueGoto;
282     delete m_pCueGotoAndPlay;
283     delete m_pCuePlay;
284     delete m_pCueGotoAndStop;
285     delete m_pCuePreview;
286     delete m_pCueCDJ;
287     delete m_pCueDefault;
288     delete m_pPlayStutter;
289     delete m_pCueIndicator;
290     delete m_pPlayIndicator;
291     delete m_pPlayLatched;
292     delete m_pIntroStartPosition;
293     delete m_pIntroStartEnabled;
294     delete m_pIntroStartSet;
295     delete m_pIntroStartClear;
296     delete m_pIntroStartActivate;
297     delete m_pIntroEndPosition;
298     delete m_pIntroEndEnabled;
299     delete m_pIntroEndSet;
300     delete m_pIntroEndClear;
301     delete m_pIntroEndActivate;
302     delete m_pOutroStartPosition;
303     delete m_pOutroStartEnabled;
304     delete m_pOutroStartSet;
305     delete m_pOutroStartClear;
306     delete m_pOutroStartActivate;
307     delete m_pOutroEndPosition;
308     delete m_pOutroEndEnabled;
309     delete m_pOutroEndSet;
310     delete m_pOutroEndClear;
311     delete m_pOutroEndActivate;
312     delete m_pVinylControlEnabled;
313     delete m_pVinylControlMode;
314     delete m_pHotcueFocus;
315     delete m_pHotcueFocusColorPrev;
316     delete m_pHotcueFocusColorNext;
317     qDeleteAll(m_hotcueControls);
318 }
319 
createControls()320 void CueControl::createControls() {
321     for (int i = 0; i < m_iNumHotCues; ++i) {
322         HotcueControl* pControl = new HotcueControl(getGroup(), i);
323 
324         connect(pControl, &HotcueControl::hotcuePositionChanged,
325                 this, &CueControl::hotcuePositionChanged,
326                 Qt::DirectConnection);
327         connect(pControl, &HotcueControl::hotcueSet,
328                 this, &CueControl::hotcueSet,
329                 Qt::DirectConnection);
330         connect(pControl, &HotcueControl::hotcueGoto,
331                 this, &CueControl::hotcueGoto,
332                 Qt::DirectConnection);
333         connect(pControl, &HotcueControl::hotcueGotoAndPlay,
334                 this, &CueControl::hotcueGotoAndPlay,
335                 Qt::DirectConnection);
336         connect(pControl, &HotcueControl::hotcueGotoAndStop,
337                 this, &CueControl::hotcueGotoAndStop,
338                 Qt::DirectConnection);
339         connect(pControl, &HotcueControl::hotcueActivate,
340                 this, &CueControl::hotcueActivate,
341                 Qt::DirectConnection);
342         connect(pControl, &HotcueControl::hotcueActivatePreview,
343                 this, &CueControl::hotcueActivatePreview,
344                 Qt::DirectConnection);
345         connect(pControl, &HotcueControl::hotcueClear,
346                 this, &CueControl::hotcueClear,
347                 Qt::DirectConnection);
348 
349         m_hotcueControls.append(pControl);
350     }
351 }
352 
attachCue(const CuePointer & pCue,HotcueControl * pControl)353 void CueControl::attachCue(const CuePointer& pCue, HotcueControl* pControl) {
354     VERIFY_OR_DEBUG_ASSERT(pControl) {
355         return;
356     }
357     detachCue(pControl);
358     connect(pCue.get(), &Cue::updated,
359             this, &CueControl::cueUpdated,
360             Qt::DirectConnection);
361 
362     pControl->setCue(pCue);
363 }
364 
detachCue(HotcueControl * pControl)365 void CueControl::detachCue(HotcueControl* pControl) {
366     VERIFY_OR_DEBUG_ASSERT(pControl) {
367         return;
368     }
369     CuePointer pCue(pControl->getCue());
370     if (!pCue) {
371         return;
372     }
373     disconnect(pCue.get(), nullptr, this, nullptr);
374     pControl->resetCue();
375 }
376 
trackLoaded(TrackPointer pNewTrack)377 void CueControl::trackLoaded(TrackPointer pNewTrack) {
378     QMutexLocker lock(&m_mutex);
379     if (m_pLoadedTrack) {
380         disconnect(m_pLoadedTrack.get(), nullptr, this, nullptr);
381         for (const auto& pControl : qAsConst(m_hotcueControls)) {
382             detachCue(pControl);
383         }
384 
385         m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
386         m_pCuePoint->set(Cue::kNoPosition);
387         m_pIntroStartPosition->set(Cue::kNoPosition);
388         m_pIntroStartEnabled->forceSet(0.0);
389         m_pIntroEndPosition->set(Cue::kNoPosition);
390         m_pIntroEndEnabled->forceSet(0.0);
391         m_pOutroStartPosition->set(Cue::kNoPosition);
392         m_pOutroStartEnabled->forceSet(0.0);
393         m_pOutroEndPosition->set(Cue::kNoPosition);
394         m_pOutroEndEnabled->forceSet(0.0);
395         setHotcueFocusIndex(Cue::kNoHotCue);
396         m_pLoadedTrack.reset();
397         m_usedSeekOnLoadPosition.setValue(kDefaultLoadPosition);
398     }
399 
400     if (!pNewTrack) {
401         return;
402     }
403     m_pLoadedTrack = pNewTrack;
404 
405     connect(m_pLoadedTrack.get(), &Track::analyzed, this, &CueControl::trackAnalyzed, Qt::DirectConnection);
406 
407     connect(m_pLoadedTrack.get(), &Track::cuesUpdated,
408             this, &CueControl::trackCuesUpdated,
409             Qt::DirectConnection);
410 
411     CuePointer pMainCue;
412     const QList<CuePointer> cuePoints = m_pLoadedTrack->getCuePoints();
413     for (const CuePointer& pCue : cuePoints) {
414         if (pCue->getType() == mixxx::CueType::MainCue) {
415             DEBUG_ASSERT(!pMainCue);
416             pMainCue = pCue;
417         }
418     }
419 
420     // Need to unlock before emitting any signals to prevent deadlock.
421     lock.unlock();
422     // Use pNewTrack from now, because m_pLoadedTrack might have been reset
423     // immediately after leaving the locking scope!
424 
425     // Because of legacy, we store the (load) cue point twice and need to
426     // sync both values.
427     // The mixxx::CueType::MainCue from getCuePoints() has the priority
428     CuePosition mainCuePoint;
429     if (pMainCue) {
430         mainCuePoint.setPosition(pMainCue->getPosition());
431         // adjust the track cue accordingly
432         pNewTrack->setCuePoint(mainCuePoint);
433     } else {
434         // If no load cue point is stored, read from track
435         // Note: This is 0:00 for new tracks
436         mainCuePoint = pNewTrack->getCuePoint();
437         // Than add the load cue to the list of cue
438         CuePointer pCue(pNewTrack->createAndAddCue());
439         pCue->setStartPosition(mainCuePoint.getPosition());
440         pCue->setHotCue(Cue::kNoHotCue);
441         pCue->setType(mixxx::CueType::MainCue);
442     }
443     m_pCuePoint->set(mainCuePoint.getPosition());
444 
445     // Update COs with cues from track.
446     loadCuesFromTrack();
447 
448     // Seek track according to SeekOnLoadMode.
449     SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference();
450 
451     switch (seekOnLoadMode) {
452     case SeekOnLoadMode::Beginning:
453         // This allows users to load tracks and have the needle-drop be maintained.
454         if (!(m_pVinylControlEnabled->toBool() &&
455                     m_pVinylControlMode->get() == MIXXX_VCMODE_ABSOLUTE)) {
456             seekOnLoad(0.0);
457         }
458         break;
459     case SeekOnLoadMode::FirstSound: {
460         CuePointer pAudibleSound = pNewTrack->findCueByType(mixxx::CueType::AudibleSound);
461         double audibleSoundPosition = Cue::kNoPosition;
462         if (pAudibleSound) {
463             audibleSoundPosition = pAudibleSound->getPosition();
464         }
465         if (audibleSoundPosition != Cue::kNoPosition) {
466             seekOnLoad(audibleSoundPosition);
467         } else {
468             seekOnLoad(0.0);
469         }
470         break;
471     }
472     case SeekOnLoadMode::MainCue: {
473         // Take main cue position from CO instead of cue point list because
474         // value in CO will be quantized if quantization is enabled
475         // while value in cue point list will never be quantized.
476         // This prevents jumps when track analysis finishes while quantization is enabled.
477         double cuePoint = m_pCuePoint->get();
478         if (cuePoint != Cue::kNoPosition) {
479             seekOnLoad(cuePoint);
480         } else {
481             seekOnLoad(0.0);
482         }
483         break;
484     }
485     case SeekOnLoadMode::IntroStart: {
486         double introStart = m_pIntroStartPosition->get();
487         if (introStart != Cue::kNoPosition) {
488             seekOnLoad(introStart);
489         } else {
490             seekOnLoad(0.0);
491         }
492         break;
493     }
494     default:
495         DEBUG_ASSERT(!"Unknown enum value");
496         seekOnLoad(0.0);
497         break;
498     }
499 }
500 
seekOnLoad(double seekOnLoadPosition)501 void CueControl::seekOnLoad(double seekOnLoadPosition) {
502     seekExact(seekOnLoadPosition);
503     m_usedSeekOnLoadPosition.setValue(seekOnLoadPosition);
504 }
505 
cueUpdated()506 void CueControl::cueUpdated() {
507     //QMutexLocker lock(&m_mutex);
508     // We should get a trackCuesUpdated call anyway, so do nothing.
509 }
510 
loadCuesFromTrack()511 void CueControl::loadCuesFromTrack() {
512     QMutexLocker lock(&m_mutex);
513     QSet<int> active_hotcues;
514     CuePointer pLoadCue, pIntroCue, pOutroCue;
515 
516     if (!m_pLoadedTrack) {
517         return;
518     }
519 
520     const QList<CuePointer> cues = m_pLoadedTrack->getCuePoints();
521     for (const auto& pCue : cues) {
522         switch (pCue->getType()) {
523         case mixxx::CueType::MainCue:
524             DEBUG_ASSERT(!pLoadCue); // There should be only one MainCue cue
525             pLoadCue = pCue;
526             break;
527         case mixxx::CueType::Intro:
528             DEBUG_ASSERT(!pIntroCue); // There should be only one Intro cue
529             pIntroCue = pCue;
530             break;
531         case mixxx::CueType::Outro:
532             DEBUG_ASSERT(!pOutroCue); // There should be only one Outro cue
533             pOutroCue = pCue;
534             break;
535         case mixxx::CueType::HotCue:
536         case mixxx::CueType::Loop: {
537             // FIXME: While it's not possible to save Loops in Mixxx yet, we do
538             // support importing them from Serato and Rekordbox. For the time
539             // being we treat them like regular hotcues and ignore their end
540             // position until #2194 has been merged.
541             if (pCue->getHotCue() == Cue::kNoHotCue) {
542                 continue;
543             }
544 
545             int hotcue = pCue->getHotCue();
546             HotcueControl* pControl = m_hotcueControls.value(hotcue, NULL);
547 
548             // Cue's hotcue doesn't have a hotcue control.
549             if (pControl == nullptr) {
550                 continue;
551             }
552 
553             CuePointer pOldCue(pControl->getCue());
554 
555             // If the old hotcue is different than this one.
556             if (pOldCue != pCue) {
557                 // old cue is detached if required
558                 attachCue(pCue, pControl);
559             } else {
560                 // If the old hotcue is the same, then we only need to update
561                 pControl->setPosition(pCue->getPosition());
562                 pControl->setColor(pCue->getColor());
563             }
564             // Add the hotcue to the list of active hotcues
565             active_hotcues.insert(hotcue);
566             break;
567         }
568         default:
569             break;
570         }
571     }
572 
573     if (pIntroCue) {
574         double startPosition = pIntroCue->getPosition();
575         double endPosition = pIntroCue->getEndPosition();
576 
577         m_pIntroStartPosition->set(quantizeCuePoint(startPosition));
578         m_pIntroStartEnabled->forceSet(startPosition == Cue::kNoPosition ? 0.0 : 1.0);
579         m_pIntroEndPosition->set(quantizeCuePoint(endPosition));
580         m_pIntroEndEnabled->forceSet(endPosition == Cue::kNoPosition ? 0.0 : 1.0);
581     } else {
582         m_pIntroStartPosition->set(Cue::kNoPosition);
583         m_pIntroStartEnabled->forceSet(0.0);
584         m_pIntroEndPosition->set(Cue::kNoPosition);
585         m_pIntroEndEnabled->forceSet(0.0);
586     }
587 
588     if (pOutroCue) {
589         double startPosition = pOutroCue->getPosition();
590         double endPosition = pOutroCue->getEndPosition();
591 
592         m_pOutroStartPosition->set(quantizeCuePoint(startPosition));
593         m_pOutroStartEnabled->forceSet(startPosition == Cue::kNoPosition ? 0.0 : 1.0);
594         m_pOutroEndPosition->set(quantizeCuePoint(endPosition));
595         m_pOutroEndEnabled->forceSet(endPosition == Cue::kNoPosition ? 0.0 : 1.0);
596     } else {
597         m_pOutroStartPosition->set(Cue::kNoPosition);
598         m_pOutroStartEnabled->forceSet(0.0);
599         m_pOutroEndPosition->set(Cue::kNoPosition);
600         m_pOutroEndEnabled->forceSet(0.0);
601     }
602 
603     if (pLoadCue) {
604         double position = pLoadCue->getPosition();
605         m_pCuePoint->set(quantizeCuePoint(position));
606     } else {
607         m_pCuePoint->set(Cue::kNoPosition);
608     }
609 
610     // Detach all hotcues that are no longer present
611     for (int hotCue = 0; hotCue < m_iNumHotCues; ++hotCue) {
612         if (!active_hotcues.contains(hotCue)) {
613             HotcueControl* pControl = m_hotcueControls.at(hotCue);
614             detachCue(pControl);
615         }
616     }
617 }
618 
trackAnalyzed()619 void CueControl::trackAnalyzed() {
620     if (!m_pLoadedTrack) {
621         return;
622     }
623 
624     SampleOfTrack sampleOfTrack = getSampleOfTrack();
625     if (sampleOfTrack.current != m_usedSeekOnLoadPosition.getValue()) {
626         // the track is already manual cued, don't re-cue
627         return;
628     }
629 
630     // Make track follow the updated cues.
631     SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference();
632 
633     if (seekOnLoadMode == SeekOnLoadMode::MainCue) {
634         double cue = m_pCuePoint->get();
635         if (cue != Cue::kNoPosition) {
636             seekOnLoad(cue);
637         }
638     } else if (seekOnLoadMode == SeekOnLoadMode::IntroStart) {
639         double intro = m_pIntroStartPosition->get();
640         if (intro != Cue::kNoPosition) {
641             seekOnLoad(intro);
642         }
643     }
644 }
645 
trackCuesUpdated()646 void CueControl::trackCuesUpdated() {
647     loadCuesFromTrack();
648 }
649 
trackBeatsUpdated(mixxx::BeatsPointer pBeats)650 void CueControl::trackBeatsUpdated(mixxx::BeatsPointer pBeats) {
651     Q_UNUSED(pBeats);
652     loadCuesFromTrack();
653 }
654 
quantizeChanged(double v)655 void CueControl::quantizeChanged(double v) {
656     Q_UNUSED(v);
657 
658     // check if we were at the cue point before
659     bool wasTrackAtCue = getTrackAt() == TrackAt::Cue;
660     bool wasTrackAtIntro = isTrackAtIntroCue();
661 
662     loadCuesFromTrack();
663 
664     // if we are playing (no matter what reason for) do not seek
665     if (m_pPlay->toBool()) {
666         return;
667     }
668 
669     // Retrieve new cue pos and follow
670     double cue = m_pCuePoint->get();
671     if (wasTrackAtCue && cue != Cue::kNoPosition) {
672         seekExact(cue);
673     }
674     // Retrieve new intro start pos and follow
675     double intro = m_pIntroStartPosition->get();
676     if (wasTrackAtIntro && intro != Cue::kNoPosition) {
677         seekExact(intro);
678     }
679 }
680 
hotcueSet(HotcueControl * pControl,double value)681 void CueControl::hotcueSet(HotcueControl* pControl, double value) {
682     //qDebug() << "CueControl::hotcueSet" << value;
683 
684     if (value <= 0) {
685         return;
686     }
687 
688     QMutexLocker lock(&m_mutex);
689     if (!m_pLoadedTrack) {
690         return;
691     }
692 
693     int hotcueIndex = pControl->getHotcueIndex();
694     // Note: the cue is just detached from the hotcue control
695     // It remains in the database for later use
696     // TODO: find a rule, that allows us to delete the cue as well
697     // https://bugs.launchpad.net/mixxx/+bug/1653276
698     hotcueClear(pControl, value);
699 
700     CuePointer pCue(m_pLoadedTrack->createAndAddCue());
701     double cuePosition = getQuantizedCurrentPosition();
702     pCue->setStartPosition(cuePosition);
703     pCue->setHotCue(hotcueIndex);
704     pCue->setLabel();
705     pCue->setType(mixxx::CueType::HotCue);
706 
707     const ColorPalette hotcueColorPalette =
708             m_colorPaletteSettings.getHotcueColorPalette();
709     if (getConfig()->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false)) {
710         pCue->setColor(hotcueColorPalette.colorForHotcueIndex(hotcueIndex));
711     } else {
712         int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), -1);
713         if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= hotcueColorPalette.size()) {
714             hotcueDefaultColorIndex = hotcueColorPalette.size() - 1; // default to last color (orange)
715         }
716         pCue->setColor(hotcueColorPalette.at(hotcueDefaultColorIndex));
717     }
718 
719     // TODO(XXX) deal with spurious signals
720     attachCue(pCue, pControl);
721 
722     // If quantize is enabled and we are not playing, jump to the cue point
723     // since it's not necessarily where we currently are. TODO(XXX) is this
724     // potentially invalid for vinyl control?
725     bool playing = m_pPlay->toBool();
726     if (!playing && m_pQuantizeEnabled->toBool()) {
727         lock.unlock();  // prevent deadlock.
728         // Enginebuffer will quantize more exactly than we can.
729         seekAbs(cuePosition);
730     }
731 }
732 
hotcueGoto(HotcueControl * pControl,double value)733 void CueControl::hotcueGoto(HotcueControl* pControl, double value) {
734     if (value <= 0) {
735         return;
736     }
737 
738     QMutexLocker lock(&m_mutex);
739     if (!m_pLoadedTrack) {
740         return;
741     }
742 
743     CuePointer pCue(pControl->getCue());
744 
745     // Need to unlock before emitting any signals to prevent deadlock.
746     lock.unlock();
747 
748     if (pCue) {
749         double position = pCue->getPosition();
750         if (position != Cue::kNoPosition) {
751             seekAbs(position);
752         }
753     }
754 }
755 
hotcueGotoAndStop(HotcueControl * pControl,double value)756 void CueControl::hotcueGotoAndStop(HotcueControl* pControl, double value) {
757     if (value <= 0) {
758         return;
759     }
760 
761     QMutexLocker lock(&m_mutex);
762     if (!m_pLoadedTrack) {
763         return;
764     }
765 
766     CuePointer pCue(pControl->getCue());
767 
768     // Need to unlock before emitting any signals to prevent deadlock.
769     lock.unlock();
770 
771     if (pCue) {
772         double position = pCue->getPosition();
773         if (position != Cue::kNoPosition) {
774             if (!m_iCurrentlyPreviewingHotcues && !m_bPreviewing) {
775                 m_pPlay->set(0.0);
776                 seekExact(position);
777             } else {
778                 // this becomes a play latch command if we are previewing
779                 m_pPlay->set(0.0);
780             }
781         }
782     }
783 }
784 
hotcueGotoAndPlay(HotcueControl * pControl,double value)785 void CueControl::hotcueGotoAndPlay(HotcueControl* pControl, double value) {
786     if (value <= 0) {
787         return;
788     }
789 
790     QMutexLocker lock(&m_mutex);
791     if (!m_pLoadedTrack) {
792         return;
793     }
794 
795     CuePointer pCue(pControl->getCue());
796 
797     // Need to unlock before emitting any signals to prevent deadlock.
798     lock.unlock();
799 
800     if (pCue) {
801         double position = pCue->getPosition();
802         if (position != Cue::kNoPosition) {
803             seekAbs(position);
804             if (!isPlayingByPlayButton()) {
805                 // cueGoto is processed asynchrony.
806                 // avoid a wrong cue set if seek by cueGoto is still pending
807                 m_bPreviewing = false;
808                 m_iCurrentlyPreviewingHotcues = 0;
809                 // don't move the cue point to the hot cue point in DENON mode
810                 m_bypassCueSetByPlay = true;
811                 m_pPlay->set(1.0);
812             }
813         }
814     }
815 }
816 
hotcueActivate(HotcueControl * pControl,double value)817 void CueControl::hotcueActivate(HotcueControl* pControl, double value) {
818     //qDebug() << "CueControl::hotcueActivate" << value;
819 
820     QMutexLocker lock(&m_mutex);
821 
822     if (!m_pLoadedTrack) {
823         return;
824     }
825 
826     CuePointer pCue(pControl->getCue());
827 
828     lock.unlock();
829 
830     if (pCue) {
831         if (value > 0) {
832             if (pCue->getPosition() == Cue::kNoPosition) {
833                 hotcueSet(pControl, value);
834             } else {
835                 if (isPlayingByPlayButton()) {
836                     hotcueGoto(pControl, value);
837                 } else {
838                     hotcueActivatePreview(pControl, value);
839                 }
840             }
841         } else {
842             if (pCue->getPosition() != Cue::kNoPosition) {
843                 hotcueActivatePreview(pControl, value);
844             }
845         }
846     } else {
847         // The cue is non-existent ...
848         if (value > 0) {
849             // set it to the current position
850             hotcueSet(pControl, value);
851         } else if (m_iCurrentlyPreviewingHotcues) {
852             // yet we got a release for it and are
853             // currently previewing a hotcue. This is indicative of a corner
854             // case where the cue was detached while we were pressing it. Let
855             // hotcueActivatePreview handle it.
856             hotcueActivatePreview(pControl, value);
857         }
858     }
859 
860     setHotcueFocusIndex(pControl->getHotcueIndex());
861 }
862 
hotcueActivatePreview(HotcueControl * pControl,double value)863 void CueControl::hotcueActivatePreview(HotcueControl* pControl, double value) {
864     QMutexLocker lock(&m_mutex);
865     if (!m_pLoadedTrack) {
866         return;
867     }
868     CuePointer pCue(pControl->getCue());
869 
870     if (value > 0) {
871         if (pCue && pCue->getPosition() != Cue::kNoPosition && !pControl->isPreviewing()) {
872             m_iCurrentlyPreviewingHotcues++;
873             double position = pCue->getPosition();
874             m_bypassCueSetByPlay = true;
875             pControl->setPreviewing(true);
876             pControl->setPreviewingPosition(position);
877 
878             // Need to unlock before emitting any signals to prevent deadlock.
879             lock.unlock();
880 
881             seekAbs(position);
882             m_pPlay->set(1.0);
883         }
884     } else if (pControl->isPreviewing()) {
885         // Mark this hotcue as not previewing.
886         double position = pControl->getPreviewingPosition();
887         pControl->setPreviewing(false);
888         pControl->setPreviewingPosition(Cue::kNoPosition);
889         if (m_iCurrentlyPreviewingHotcues > 0) {
890             // This is a release of an active previewing hotcue.
891             // If this is the last hotcue, leave preview.
892             if (--m_iCurrentlyPreviewingHotcues == 0 && !m_bPreviewing) {
893                 m_pPlay->set(0.0);
894                 // Need to unlock before emitting any signals to prevent deadlock.
895                 lock.unlock();
896                 seekExact(position);
897             }
898         }
899     }
900 }
901 
hotcueClear(HotcueControl * pControl,double value)902 void CueControl::hotcueClear(HotcueControl* pControl, double value) {
903     if (value <= 0) {
904         return;
905     }
906 
907     QMutexLocker lock(&m_mutex);
908     if (!m_pLoadedTrack) {
909         return;
910     }
911 
912     CuePointer pCue(pControl->getCue());
913     if (!pCue) {
914         return;
915     }
916     detachCue(pControl);
917     m_pLoadedTrack->removeCue(pCue);
918     setHotcueFocusIndex(Cue::kNoHotCue);
919 }
920 
hotcuePositionChanged(HotcueControl * pControl,double newPosition)921 void CueControl::hotcuePositionChanged(HotcueControl* pControl, double newPosition) {
922     QMutexLocker lock(&m_mutex);
923     if (!m_pLoadedTrack) {
924         return;
925     }
926 
927     CuePointer pCue(pControl->getCue());
928     if (pCue) {
929         // Setting the position to Cue::kNoPosition is the same as calling hotcue_x_clear
930         if (newPosition == Cue::kNoPosition) {
931             detachCue(pControl);
932         } else if (newPosition > 0 && newPosition < m_pTrackSamples->get()) {
933             pCue->setStartPosition(newPosition);
934         }
935     }
936 }
937 
hintReader(HintVector * pHintList)938 void CueControl::hintReader(HintVector* pHintList) {
939     Hint cue_hint;
940     double cuePoint = m_pCuePoint->get();
941     if (cuePoint >= 0) {
942         cue_hint.frame = SampleUtil::floorPlayPosToFrame(m_pCuePoint->get());
943         cue_hint.frameCount = Hint::kFrameCountForward;
944         cue_hint.priority = 10;
945         pHintList->append(cue_hint);
946     }
947 
948     // this is called from the engine thread
949     // it is no locking required, because m_hotcueControl is filled during the
950     // constructor and getPosition()->get() is a ControlObject
951     for (const auto& pControl : qAsConst(m_hotcueControls)) {
952         double position = pControl->getPosition();
953         if (position != Cue::kNoPosition) {
954             cue_hint.frame = SampleUtil::floorPlayPosToFrame(position);
955             cue_hint.frameCount = Hint::kFrameCountForward;
956             cue_hint.priority = 10;
957             pHintList->append(cue_hint);
958         }
959     }
960 }
961 
962 // Moves the cue point to current position or to closest beat in case
963 // quantize is enabled
cueSet(double value)964 void CueControl::cueSet(double value) {
965     if (value <= 0) {
966         return;
967     }
968 
969     QMutexLocker lock(&m_mutex);
970 
971     double cue = getQuantizedCurrentPosition();
972     m_pCuePoint->set(cue);
973     TrackPointer pLoadedTrack = m_pLoadedTrack;
974     lock.unlock();
975 
976     // Store cue point in loaded track
977     if (pLoadedTrack) {
978         pLoadedTrack->setCuePoint(CuePosition(cue));
979     }
980 }
981 
cueClear(double value)982 void CueControl::cueClear(double value) {
983     if (value <= 0) {
984         return;
985     }
986 
987     QMutexLocker lock(&m_mutex);
988     m_pCuePoint->set(Cue::kNoPosition);
989     TrackPointer pLoadedTrack = m_pLoadedTrack;
990     lock.unlock();
991 
992     if (pLoadedTrack) {
993         pLoadedTrack->setCuePoint(CuePosition());
994     }
995 }
996 
cueGoto(double value)997 void CueControl::cueGoto(double value) {
998     if (value <= 0) {
999         return;
1000     }
1001 
1002     QMutexLocker lock(&m_mutex);
1003     // Seek to cue point
1004     double cuePoint = m_pCuePoint->get();
1005 
1006     // Need to unlock before emitting any signals to prevent deadlock.
1007     lock.unlock();
1008 
1009     seekAbs(cuePoint);
1010 }
1011 
cueGotoAndPlay(double value)1012 void CueControl::cueGotoAndPlay(double value) {
1013     if (value <= 0) {
1014         return;
1015     }
1016 
1017     cueGoto(value);
1018     QMutexLocker lock(&m_mutex);
1019     // Start playing if not already
1020     if (!isPlayingByPlayButton()) {
1021         // cueGoto is processed asynchrony.
1022         // avoid a wrong cue set if seek by cueGoto is still pending
1023         m_bPreviewing = false;
1024         m_iCurrentlyPreviewingHotcues = 0;
1025         m_bypassCueSetByPlay = true;
1026         m_pPlay->set(1.0);
1027     }
1028 }
1029 
cueGotoAndStop(double value)1030 void CueControl::cueGotoAndStop(double value) {
1031     if (value <= 0) {
1032         return;
1033     }
1034 
1035     if (!m_iCurrentlyPreviewingHotcues && !m_bPreviewing) {
1036         m_pPlay->set(0.0);
1037         double position = m_pCuePoint->get();
1038         seekExact(position);
1039     } else {
1040         // this becomes a play latch command if we are previewing
1041         m_pPlay->set(0.0);
1042     }
1043 }
1044 
cuePreview(double value)1045 void CueControl::cuePreview(double value) {
1046     //qDebug() << "CueControl::cuePreview" << value;
1047     QMutexLocker lock(&m_mutex);
1048 
1049     if (value > 0) {
1050         if (!m_bPreviewing) {
1051             m_bPreviewing = true;
1052             m_bypassCueSetByPlay = true;
1053             m_pPlay->set(1.0);
1054         }
1055     } else if (m_bPreviewing) {
1056         m_bPreviewing = false;
1057         if (m_iCurrentlyPreviewingHotcues) {
1058             return;
1059         }
1060         m_pPlay->set(0.0);
1061     } else {
1062         return;
1063     }
1064 
1065     // Need to unlock before emitting any signals to prevent deadlock.
1066     lock.unlock();
1067 
1068     seekAbs(m_pCuePoint->get());
1069 }
1070 
cueCDJ(double value)1071 void CueControl::cueCDJ(double value) {
1072     // This is how Pioneer cue buttons work:
1073     // If pressed while freely playing (i.e. playing and platter NOT being touched), stop playback and go to cue.
1074     // If pressed while NOT freely playing (i.e. stopped or playing but platter IS being touched), set new cue point.
1075     // If pressed while stopped and at cue, play while pressed.
1076     // If play is pressed while holding cue, the deck is now playing. (Handled in playFromCuePreview().)
1077 
1078     QMutexLocker lock(&m_mutex);
1079     const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching();
1080     TrackAt trackAt = getTrackAt();
1081 
1082     if (value > 0) {
1083         if (m_bPreviewing) {
1084             // already previewing, do nothing
1085             return;
1086         } else if (m_iCurrentlyPreviewingHotcues) {
1087             // we are already previewing by hotcues
1088             // just jump to cue point and continue previewing
1089             m_bPreviewing = true;
1090             lock.unlock();
1091             seekAbs(m_pCuePoint->get());
1092         } else if (freely_playing || trackAt == TrackAt::End) {
1093             // Jump to cue when playing or when at end position
1094             m_pPlay->set(0.0);
1095             // Need to unlock before emitting any signals to prevent deadlock.
1096             lock.unlock();
1097             seekAbs(m_pCuePoint->get());
1098         } else if (trackAt == TrackAt::Cue) {
1099             // paused at cue point
1100             m_bPreviewing = true;
1101             m_pPlay->set(1.0);
1102         } else {
1103             // Paused not at cue point and not at end position
1104             cueSet(value);
1105 
1106             // If quantize is enabled, jump to the cue point since it's not
1107             // necessarily where we currently are
1108             if (m_pQuantizeEnabled->toBool()) {
1109                 lock.unlock();  // prevent deadlock.
1110                 // Enginebuffer will quantize more exactly than we can.
1111                 seekAbs(m_pCuePoint->get());
1112             }
1113         }
1114     } else if (m_bPreviewing) {
1115         m_bPreviewing = false;
1116         if (!m_iCurrentlyPreviewingHotcues) {
1117             m_pPlay->set(0.0);
1118 
1119             // Need to unlock before emitting any signals to prevent deadlock.
1120             lock.unlock();
1121 
1122             seekAbs(m_pCuePoint->get());
1123         }
1124     }
1125     // indicator may flash because the delayed adoption of seekAbs
1126     // Correct the Indicator set via play
1127     if (m_pLoadedTrack && !freely_playing) {
1128         m_pCueIndicator->setBlinkValue(ControlIndicator::ON);
1129     } else {
1130         m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1131     }
1132 }
1133 
cueDenon(double value)1134 void CueControl::cueDenon(double value) {
1135     // This is how Denon DN-S 3700 cue buttons work:
1136     // If pressed go to cue and stop.
1137     // If pressed while stopped and at cue, play while pressed.
1138     // Cue Point is moved by play from pause
1139 
1140     QMutexLocker lock(&m_mutex);
1141     bool playing = (m_pPlay->toBool());
1142     TrackAt trackAt = getTrackAt();
1143 
1144     if (value > 0) {
1145         if (m_bPreviewing) {
1146             // already previewing, do nothing
1147             return;
1148         } else if (m_iCurrentlyPreviewingHotcues) {
1149             // we are already previewing by hotcues
1150             // just jump to cue point and continue previewing
1151             m_bPreviewing = true;
1152             lock.unlock();
1153             seekAbs(m_pCuePoint->get());
1154         } else if (!playing && trackAt == TrackAt::Cue) {
1155             // paused at cue point
1156             m_bPreviewing = true;
1157             m_pPlay->set(1.0);
1158         } else {
1159             m_pPlay->set(0.0);
1160             // Need to unlock before emitting any signals to prevent deadlock.
1161             lock.unlock();
1162             seekAbs(m_pCuePoint->get());
1163         }
1164     } else if (m_bPreviewing) {
1165         m_bPreviewing = false;
1166         if (!m_iCurrentlyPreviewingHotcues) {
1167             m_pPlay->set(0.0);
1168 
1169             // Need to unlock before emitting any signals to prevent deadlock.
1170             lock.unlock();
1171 
1172             seekAbs(m_pCuePoint->get());
1173         }
1174     }
1175 }
1176 
cuePlay(double value)1177 void CueControl::cuePlay(double value) {
1178     // This is how CUP button works:
1179     // If freely playing (i.e. playing and platter NOT being touched), press to go to cue and stop.
1180     // If not freely playing (i.e. stopped or platter IS being touched), press to go to cue and stop.
1181     // On release, start playing from cue point.
1182 
1183 
1184     QMutexLocker lock(&m_mutex);
1185     const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching();
1186     TrackAt trackAt = getTrackAt();
1187 
1188     // pressed
1189     if (value > 0) {
1190         if (freely_playing) {
1191             m_bPreviewing = false;
1192             m_pPlay->set(0.0);
1193 
1194             // Need to unlock before emitting any signals to prevent deadlock.
1195             lock.unlock();
1196 
1197             seekAbs(m_pCuePoint->get());
1198         } else if (trackAt == TrackAt::ElseWhere) {
1199             // Pause not at cue point and not at end position
1200             cueSet(value);
1201             // Just in case.
1202             m_bPreviewing = false;
1203             m_pPlay->set(0.0);
1204             // If quantize is enabled, jump to the cue point since it's not
1205             // necessarily where we currently are
1206             if (m_pQuantizeEnabled->toBool()) {
1207                 lock.unlock();  // prevent deadlock.
1208                 // Enginebuffer will quantize more exactly than we can.
1209                 seekAbs(m_pCuePoint->get());
1210             }
1211         }
1212     } else if (trackAt == TrackAt::Cue) {
1213         m_bPreviewing = false;
1214         m_pPlay->set(1.0);
1215         lock.unlock();
1216     }
1217 }
1218 
cueDefault(double v)1219 void CueControl::cueDefault(double v) {
1220     double cueMode = m_pCueMode->get();
1221     // Decide which cue implementation to call based on the user preference
1222     if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) {
1223         cueDenon(v);
1224     } else if (cueMode == CUE_MODE_CUP) {
1225         cuePlay(v);
1226     } else {
1227         // The modes CUE_MODE_PIONEER and CUE_MODE_MIXXX are similar
1228         // are handled inside cueCDJ(v)
1229         // default to Pioneer mode
1230         cueCDJ(v);
1231     }
1232 }
1233 
pause(double v)1234 void CueControl::pause(double v) {
1235     QMutexLocker lock(&m_mutex);
1236     //qDebug() << "CueControl::pause()" << v;
1237     if (v != 0.0) {
1238         m_pPlay->set(0.0);
1239     }
1240 }
1241 
playStutter(double v)1242 void CueControl::playStutter(double v) {
1243     QMutexLocker lock(&m_mutex);
1244     //qDebug() << "playStutter" << v;
1245     if (v != 0.0) {
1246         if (isPlayingByPlayButton()) {
1247             cueGoto(1.0);
1248         } else {
1249             m_pPlay->set(1.0);
1250         }
1251     }
1252 }
1253 
introStartSet(double value)1254 void CueControl::introStartSet(double value) {
1255     if (value <= 0) {
1256         return;
1257     }
1258 
1259     QMutexLocker lock(&m_mutex);
1260 
1261     double position = getQuantizedCurrentPosition();
1262 
1263     // Make sure user is not trying to place intro start cue on or after
1264     // other intro/outro cues.
1265     double introEnd = m_pIntroEndPosition->get();
1266     double outroStart = m_pOutroStartPosition->get();
1267     double outroEnd = m_pOutroEndPosition->get();
1268     if (introEnd != Cue::kNoPosition && position >= introEnd) {
1269         qWarning() << "Trying to place intro start cue on or after intro end cue.";
1270         return;
1271     }
1272     if (outroStart != Cue::kNoPosition && position >= outroStart) {
1273         qWarning() << "Trying to place intro start cue on or after outro start cue.";
1274         return;
1275     }
1276     if (outroEnd != Cue::kNoPosition && position >= outroEnd) {
1277         qWarning() << "Trying to place intro start cue on or after outro end cue.";
1278         return;
1279     }
1280 
1281     TrackPointer pLoadedTrack = m_pLoadedTrack;
1282     lock.unlock();
1283 
1284     if (pLoadedTrack) {
1285         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro);
1286         if (!pCue) {
1287             pCue = pLoadedTrack->createAndAddCue();
1288             pCue->setType(mixxx::CueType::Intro);
1289         }
1290         pCue->setStartPosition(position);
1291         pCue->setEndPosition(introEnd);
1292     }
1293 }
1294 
introStartClear(double value)1295 void CueControl::introStartClear(double value) {
1296     if (value <= 0) {
1297         return;
1298     }
1299 
1300     QMutexLocker lock(&m_mutex);
1301     double introEnd = m_pIntroEndPosition->get();
1302     TrackPointer pLoadedTrack = m_pLoadedTrack;
1303     lock.unlock();
1304 
1305     if (pLoadedTrack) {
1306         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro);
1307         if (introEnd != Cue::kNoPosition) {
1308             pCue->setStartPosition(Cue::kNoPosition);
1309             pCue->setEndPosition(introEnd);
1310         } else if (pCue) {
1311             pLoadedTrack->removeCue(pCue);
1312         }
1313     }
1314 }
1315 
introStartActivate(double value)1316 void CueControl::introStartActivate(double value) {
1317     if (value == 0) {
1318         return;
1319     }
1320 
1321     QMutexLocker lock(&m_mutex);
1322     double introStart = m_pIntroStartPosition->get();
1323     lock.unlock();
1324 
1325     if (introStart == Cue::kNoPosition) {
1326         introStartSet(1.0);
1327     } else {
1328         seekAbs(introStart);
1329     }
1330 }
1331 
introEndSet(double value)1332 void CueControl::introEndSet(double value) {
1333     if (value <= 0) {
1334         return;
1335     }
1336 
1337     QMutexLocker lock(&m_mutex);
1338 
1339     double position = getQuantizedCurrentPosition();
1340 
1341     // Make sure user is not trying to place intro end cue on or before
1342     // intro start cue, or on or after outro start/end cue.
1343     double introStart = m_pIntroStartPosition->get();
1344     double outroStart = m_pOutroStartPosition->get();
1345     double outroEnd = m_pOutroEndPosition->get();
1346     if (introStart != Cue::kNoPosition && position <= introStart) {
1347         qWarning() << "Trying to place intro end cue on or before intro start cue.";
1348         return;
1349     }
1350     if (outroStart != Cue::kNoPosition && position >= outroStart) {
1351         qWarning() << "Trying to place intro end cue on or after outro start cue.";
1352         return;
1353     }
1354     if (outroEnd != Cue::kNoPosition && position >= outroEnd) {
1355         qWarning() << "Trying to place intro end cue on or after outro end cue.";
1356         return;
1357     }
1358 
1359     TrackPointer pLoadedTrack = m_pLoadedTrack;
1360     lock.unlock();
1361 
1362     if (pLoadedTrack) {
1363         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro);
1364         if (!pCue) {
1365             pCue = pLoadedTrack->createAndAddCue();
1366             pCue->setType(mixxx::CueType::Intro);
1367         }
1368         pCue->setStartPosition(introStart);
1369         pCue->setEndPosition(position);
1370     }
1371 }
1372 
introEndClear(double value)1373 void CueControl::introEndClear(double value) {
1374     if (value <= 0) {
1375         return;
1376     }
1377 
1378     QMutexLocker lock(&m_mutex);
1379     double introStart = m_pIntroStartPosition->get();
1380     TrackPointer pLoadedTrack = m_pLoadedTrack;
1381     lock.unlock();
1382 
1383     if (pLoadedTrack) {
1384         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Intro);
1385         if (introStart != Cue::kNoPosition) {
1386             pCue->setStartPosition(introStart);
1387             pCue->setEndPosition(Cue::kNoPosition);
1388         } else if (pCue) {
1389             pLoadedTrack->removeCue(pCue);
1390         }
1391     }
1392 }
1393 
introEndActivate(double value)1394 void CueControl::introEndActivate(double value) {
1395     if (value == 0) {
1396         return;
1397     }
1398 
1399     QMutexLocker lock(&m_mutex);
1400     double introEnd = m_pIntroEndPosition->get();
1401     lock.unlock();
1402 
1403     if (introEnd == Cue::kNoPosition) {
1404         introEndSet(1.0);
1405     } else {
1406         seekAbs(introEnd);
1407     }
1408 }
1409 
outroStartSet(double value)1410 void CueControl::outroStartSet(double value) {
1411     if (value <= 0) {
1412         return;
1413     }
1414 
1415     QMutexLocker lock(&m_mutex);
1416 
1417     double position = getQuantizedCurrentPosition();
1418 
1419     // Make sure user is not trying to place outro start cue on or before
1420     // intro end cue or on or after outro end cue.
1421     double introStart = m_pIntroStartPosition->get();
1422     double introEnd = m_pIntroEndPosition->get();
1423     double outroEnd = m_pOutroEndPosition->get();
1424     if (introStart != Cue::kNoPosition && position <= introStart) {
1425         qWarning() << "Trying to place outro start cue on or before intro start cue.";
1426         return;
1427     }
1428     if (introEnd != Cue::kNoPosition && position <= introEnd) {
1429         qWarning() << "Trying to place outro start cue on or before intro end cue.";
1430         return;
1431     }
1432     if (outroEnd != Cue::kNoPosition && position >= outroEnd) {
1433         qWarning() << "Trying to place outro start cue on or after outro end cue.";
1434         return;
1435     }
1436 
1437     TrackPointer pLoadedTrack = m_pLoadedTrack;
1438     lock.unlock();
1439 
1440     if (pLoadedTrack) {
1441         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro);
1442         if (!pCue) {
1443             pCue = pLoadedTrack->createAndAddCue();
1444             pCue->setType(mixxx::CueType::Outro);
1445         }
1446         pCue->setStartPosition(position);
1447         pCue->setEndPosition(outroEnd);
1448     }
1449 }
1450 
outroStartClear(double value)1451 void CueControl::outroStartClear(double value) {
1452     if (value <= 0) {
1453         return;
1454     }
1455 
1456     QMutexLocker lock(&m_mutex);
1457     double outroEnd = m_pOutroEndPosition->get();
1458     TrackPointer pLoadedTrack = m_pLoadedTrack;
1459     lock.unlock();
1460 
1461     if (pLoadedTrack) {
1462         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro);
1463         if (outroEnd != Cue::kNoPosition) {
1464             pCue->setStartPosition(Cue::kNoPosition);
1465             pCue->setEndPosition(outroEnd);
1466         } else if (pCue) {
1467             pLoadedTrack->removeCue(pCue);
1468         }
1469     }
1470 }
1471 
outroStartActivate(double value)1472 void CueControl::outroStartActivate(double value) {
1473     if (value <= 0) {
1474         return;
1475     }
1476 
1477     QMutexLocker lock(&m_mutex);
1478     double outroStart = m_pOutroStartPosition->get();
1479     lock.unlock();
1480 
1481     if (outroStart == Cue::kNoPosition) {
1482         outroStartSet(1.0);
1483     } else {
1484         seekAbs(outroStart);
1485     }
1486 }
1487 
outroEndSet(double value)1488 void CueControl::outroEndSet(double value) {
1489     if (value <= 0) {
1490         return;
1491     }
1492 
1493     QMutexLocker lock(&m_mutex);
1494 
1495     double position = getQuantizedCurrentPosition();
1496 
1497     // Make sure user is not trying to place outro end cue on or before
1498     // other intro/outro cues.
1499     double introStart = m_pIntroStartPosition->get();
1500     double introEnd = m_pIntroEndPosition->get();
1501     double outroStart = m_pOutroStartPosition->get();
1502     if (introStart != Cue::kNoPosition && position <= introStart) {
1503         qWarning() << "Trying to place outro end cue on or before intro start cue.";
1504         return;
1505     }
1506     if (introEnd != Cue::kNoPosition && position <= introEnd) {
1507         qWarning() << "Trying to place outro end cue on or before intro end cue.";
1508         return;
1509     }
1510     if (outroStart != Cue::kNoPosition && position <= outroStart) {
1511         qWarning() << "Trying to place outro end cue on or before outro start cue.";
1512         return;
1513     }
1514 
1515     TrackPointer pLoadedTrack = m_pLoadedTrack;
1516     lock.unlock();
1517 
1518     if (pLoadedTrack) {
1519         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro);
1520         if (!pCue) {
1521             pCue = pLoadedTrack->createAndAddCue();
1522             pCue->setType(mixxx::CueType::Outro);
1523         }
1524         pCue->setStartPosition(outroStart);
1525         pCue->setEndPosition(position);
1526     }
1527 }
1528 
outroEndClear(double value)1529 void CueControl::outroEndClear(double value) {
1530     if (value <= 0) {
1531         return;
1532     }
1533 
1534     QMutexLocker lock(&m_mutex);
1535     double outroStart = m_pOutroStartPosition->get();
1536     TrackPointer pLoadedTrack = m_pLoadedTrack;
1537     lock.unlock();
1538 
1539     if (pLoadedTrack) {
1540         CuePointer pCue = pLoadedTrack->findCueByType(mixxx::CueType::Outro);
1541         if (outroStart != Cue::kNoPosition) {
1542             pCue->setStartPosition(outroStart);
1543             pCue->setEndPosition(Cue::kNoPosition);
1544         } else if (pCue) {
1545             pLoadedTrack->removeCue(pCue);
1546         }
1547     }
1548 }
1549 
outroEndActivate(double value)1550 void CueControl::outroEndActivate(double value) {
1551     if (value <= 0) {
1552         return;
1553     }
1554 
1555     QMutexLocker lock(&m_mutex);
1556     double outroEnd = m_pOutroEndPosition->get();
1557     lock.unlock();
1558 
1559     if (outroEnd == Cue::kNoPosition) {
1560         outroEndSet(1.0);
1561     } else {
1562         seekAbs(outroEnd);
1563     }
1564 }
1565 
1566 // This is also called from the engine thread. No locking allowed.
updateIndicatorsAndModifyPlay(bool newPlay,bool oldPlay,bool playPossible)1567 bool CueControl::updateIndicatorsAndModifyPlay(bool newPlay, bool oldPlay, bool playPossible) {
1568     //qDebug() << "updateIndicatorsAndModifyPlay" << newPlay << playPossible
1569     //        << m_iCurrentlyPreviewingHotcues << m_bPreviewing;
1570     CueMode cueMode = static_cast<CueMode>(static_cast<int>(m_pCueMode->get()));
1571     if ((cueMode == CueMode::Denon || cueMode == CueMode::Numark) &&
1572             newPlay && !oldPlay && playPossible &&
1573             !m_bypassCueSetByPlay) {
1574         // in Denon mode each play from pause moves the cue point
1575         // if not previewing
1576         cueSet(1.0);
1577     }
1578     m_bypassCueSetByPlay = false;
1579 
1580     // when previewing, "play" was set by cue button, a following toggle request
1581     // (play = 0.0) is used for latching play.
1582     bool previewing = false;
1583     if (m_bPreviewing || m_iCurrentlyPreviewingHotcues) {
1584         if (!newPlay && oldPlay) {
1585             // play latch request: stop previewing and go into normal play mode.
1586             m_bPreviewing = false;
1587             m_iCurrentlyPreviewingHotcues = 0;
1588             newPlay = true;
1589             m_pPlayLatched->forceSet(1.0);
1590         } else {
1591             previewing = true;
1592             m_pPlayLatched->forceSet(0.0);
1593         }
1594     }
1595 
1596     TrackAt trackAt = getTrackAt();
1597 
1598     if (!playPossible) {
1599         // play not possible
1600         newPlay = false;
1601         m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1602         m_pStopButton->set(0.0);
1603         m_pPlayLatched->forceSet(0.0);
1604     } else if (newPlay && !previewing) {
1605         // Play: Indicates a latched Play
1606         m_pPlayIndicator->setBlinkValue(ControlIndicator::ON);
1607         m_pStopButton->set(0.0);
1608         m_pPlayLatched->forceSet(1.0);
1609     } else {
1610         // Pause:
1611         m_pStopButton->set(1.0);
1612         m_pPlayLatched->forceSet(0.0);
1613         if (cueMode == CueMode::Denon) {
1614             if (trackAt == TrackAt::Cue || previewing) {
1615                 m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1616             } else {
1617                 // Flashing indicates that a following play would move cue point
1618                 m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS);
1619             }
1620         } else if (cueMode == CueMode::Mixxx || cueMode == CueMode::MixxxNoBlinking ||
1621                 cueMode == CueMode::Numark) {
1622             m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1623         } else {
1624             // Flashing indicates that play is possible in Pioneer mode
1625             m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS);
1626         }
1627     }
1628 
1629     if (cueMode != CueMode::Denon && cueMode != CueMode::Numark) {
1630         if (m_pCuePoint->get() != Cue::kNoPosition) {
1631             if (newPlay == 0.0 && trackAt == TrackAt::ElseWhere) {
1632                 if (cueMode == CueMode::Mixxx) {
1633                     // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point
1634                     m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS);
1635                 } else if (cueMode == CueMode::MixxxNoBlinking) {
1636                     m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1637                 } else {
1638                     // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point
1639                     m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS);
1640                 }
1641             } else {
1642                 m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1643             }
1644         } else {
1645             m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1646         }
1647     }
1648     m_pPlayStutter->set(newPlay ? 1.0 : 0.0);
1649 
1650     return newPlay;
1651 }
1652 
1653 // called from the engine thread
updateIndicators()1654 void CueControl::updateIndicators() {
1655     // No need for mutex lock because we are only touching COs.
1656     double cueMode = m_pCueMode->get();
1657     TrackAt trackAt = getTrackAt();
1658 
1659     if (cueMode == CUE_MODE_DENON || cueMode == CUE_MODE_NUMARK) {
1660         // Cue button is only lit at cue point
1661         bool playing = m_pPlay->toBool();
1662         if (trackAt == TrackAt::Cue) {
1663             // at cue point
1664             if (!playing) {
1665                 m_pCueIndicator->setBlinkValue(ControlIndicator::ON);
1666                 m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1667             }
1668         } else {
1669             m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1670             if (!playing) {
1671                 if (trackAt != TrackAt::End && cueMode != CUE_MODE_NUMARK) {
1672                     // Play will move cue point
1673                     m_pPlayIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS);
1674                 } else {
1675                     // At track end
1676                     m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1677                 }
1678             }
1679         }
1680     } else {
1681         // Here we have CUE_MODE_PIONEER or CUE_MODE_MIXXX
1682         // default to Pioneer mode
1683         if (!m_bPreviewing) {
1684             const auto freely_playing = m_pPlay->toBool() && !getEngineBuffer()->getScratching();
1685             if (!freely_playing) {
1686                 switch (trackAt) {
1687                 case TrackAt::ElseWhere:
1688                     if (cueMode == CUE_MODE_MIXXX) {
1689                         // in Mixxx mode Cue Button is flashing slow if CUE will move Cue point
1690                         m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_500MS);
1691                     } else if (cueMode == CUE_MODE_MIXXX_NO_BLINK) {
1692                         m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1693                     } else {
1694                         // in Pioneer mode Cue Button is flashing fast if CUE will move Cue point
1695                         m_pCueIndicator->setBlinkValue(ControlIndicator::RATIO1TO1_250MS);
1696                     }
1697                     break;
1698                 case TrackAt::End:
1699                     // At track end
1700                     m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1701                     break;
1702                 case TrackAt::Cue:
1703                     // Next Press is preview
1704                     m_pCueIndicator->setBlinkValue(ControlIndicator::ON);
1705                     break;
1706                 }
1707             } else {
1708                 // Cue indicator should be off when freely playing
1709                 m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1710             }
1711         }
1712     }
1713 }
1714 
resetIndicators()1715 void CueControl::resetIndicators() {
1716     m_pCueIndicator->setBlinkValue(ControlIndicator::OFF);
1717     m_pPlayIndicator->setBlinkValue(ControlIndicator::OFF);
1718 }
1719 
getTrackAt() const1720 CueControl::TrackAt CueControl::getTrackAt() const {
1721     SampleOfTrack sot = getSampleOfTrack();
1722     // Note: current can be in the padded silence after the track end > total.
1723     if (sot.current >= sot.total) {
1724         return TrackAt::End;
1725     }
1726     double cue = m_pCuePoint->get();
1727     if (cue != Cue::kNoPosition && fabs(sot.current - cue) < 1.0f) {
1728         return TrackAt::Cue;
1729     }
1730     return TrackAt::ElseWhere;
1731 }
1732 
getQuantizedCurrentPosition()1733 double CueControl::getQuantizedCurrentPosition() {
1734     SampleOfTrack sampleOfTrack = getSampleOfTrack();
1735     double currentPos = sampleOfTrack.current;
1736     const double total = sampleOfTrack.total;
1737 
1738     // Note: currentPos can be past the end of the track, in the padded
1739     // silence of the last buffer. This position might be not reachable in
1740     // a future runs, depending on the buffering.
1741     currentPos = math_min(currentPos, total);
1742 
1743     // Don't quantize if quantization is disabled.
1744     if (!m_pQuantizeEnabled->toBool()) {
1745         return currentPos;
1746     }
1747 
1748     double closestBeat = m_pClosestBeat->get();
1749     // Note: closestBeat can be an interpolated beat past the end of the track,
1750     // which cannot be reached.
1751     if (closestBeat != -1.0 && closestBeat <= total) {
1752         return closestBeat;
1753     }
1754 
1755     return currentPos;
1756 }
1757 
quantizeCuePoint(double cuePos)1758 double CueControl::quantizeCuePoint(double cuePos) {
1759     // we need to use m_pTrackSamples here because SampleOfTrack
1760     // is set later by the engine and not during EngineBuffer::slotTrackLoaded
1761     const double total = m_pTrackSamples->get();
1762 
1763     if (cuePos > total) {
1764         // This can happen if the track length has changed or the cue was set in the
1765         // the padded silence after the track.
1766         cuePos = total;
1767     }
1768 
1769     // Don't quantize unset cues, manual cues or when quantization is disabled.
1770     if (cuePos == Cue::kNoPosition || !m_pQuantizeEnabled->toBool()) {
1771         return cuePos;
1772     }
1773 
1774     const mixxx::BeatsPointer pBeats = m_pLoadedTrack->getBeats();
1775     if (!pBeats) {
1776         return cuePos;
1777     }
1778 
1779     double closestBeat = pBeats->findClosestBeat(cuePos);
1780     // The closest beat can be an unreachable  interpolated beat past the end of
1781     // the track.
1782     if (closestBeat != -1.0 && closestBeat <= total) {
1783         return closestBeat;
1784     }
1785 
1786     return cuePos;
1787 }
1788 
isTrackAtIntroCue()1789 bool CueControl::isTrackAtIntroCue() {
1790     return (fabs(getSampleOfTrack().current - m_pIntroStartPosition->get()) < 1.0f);
1791 }
1792 
isPlayingByPlayButton()1793 bool CueControl::isPlayingByPlayButton() {
1794     return m_pPlay->toBool() &&
1795             !m_iCurrentlyPreviewingHotcues && !m_bPreviewing;
1796 }
1797 
getSeekOnLoadPreference()1798 SeekOnLoadMode CueControl::getSeekOnLoadPreference() {
1799     int configValue = getConfig()->getValue(ConfigKey("[Controls]", "CueRecall"),
1800             static_cast<int>(SeekOnLoadMode::IntroStart));
1801     return static_cast<SeekOnLoadMode>(configValue);
1802 }
1803 
hotcueFocusColorPrev(double value)1804 void CueControl::hotcueFocusColorPrev(double value) {
1805     if (value <= 0) {
1806         return;
1807     }
1808 
1809     int hotcueIndex = getHotcueFocusIndex();
1810     if (hotcueIndex < 0 || hotcueIndex >= m_hotcueControls.size()) {
1811         return;
1812     }
1813 
1814     HotcueControl* pControl = m_hotcueControls.at(hotcueIndex);
1815     if (!pControl) {
1816         return;
1817     }
1818 
1819     CuePointer pCue = pControl->getCue();
1820     if (!pCue) {
1821         return;
1822     }
1823 
1824     mixxx::RgbColor::optional_t color = pCue->getColor();
1825     if (!color) {
1826         return;
1827     }
1828 
1829     ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette();
1830     pCue->setColor(colorPalette.previousColor(*color));
1831 }
1832 
hotcueFocusColorNext(double value)1833 void CueControl::hotcueFocusColorNext(double value) {
1834     if (value <= 0) {
1835         return;
1836     }
1837 
1838     int hotcueIndex = getHotcueFocusIndex();
1839     if (hotcueIndex < 0 || hotcueIndex >= m_hotcueControls.size()) {
1840         return;
1841     }
1842 
1843     HotcueControl* pControl = m_hotcueControls.at(hotcueIndex);
1844     if (!pControl) {
1845         return;
1846     }
1847 
1848     CuePointer pCue = pControl->getCue();
1849     if (!pCue) {
1850         return;
1851     }
1852 
1853     mixxx::RgbColor::optional_t color = pCue->getColor();
1854     if (!color) {
1855         return;
1856     }
1857 
1858     ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette();
1859     pCue->setColor(colorPalette.nextColor(*color));
1860 }
1861 
setHotcueFocusIndex(int hotcueIndex)1862 void CueControl::setHotcueFocusIndex(int hotcueIndex) {
1863     m_pHotcueFocus->set(hotcueIndexToHotcueNumber(hotcueIndex));
1864 }
1865 
getHotcueFocusIndex() const1866 int CueControl::getHotcueFocusIndex() const {
1867     return hotcueNumberToHotcueIndex(static_cast<int>(m_pHotcueFocus->get()));
1868 }
1869 
keyForControl(const QString & name)1870 ConfigKey HotcueControl::keyForControl(const QString& name) {
1871     ConfigKey key;
1872     key.group = m_group;
1873     // Add one to hotcue so that we don't have a hotcue_0
1874     key.item = QStringLiteral("hotcue_") +
1875             QString::number(hotcueIndexToHotcueNumber(m_hotcueIndex)) +
1876             QChar('_') + name;
1877     return key;
1878 }
1879 
HotcueControl(const QString & group,int hotcueIndex)1880 HotcueControl::HotcueControl(const QString& group, int hotcueIndex)
1881         : m_group(group),
1882           m_hotcueIndex(hotcueIndex),
1883           m_pCue(nullptr),
1884           m_bPreviewing(false),
1885           m_previewingPosition(-1) {
1886     m_hotcuePosition = std::make_unique<ControlObject>(keyForControl(QStringLiteral("position")));
1887     connect(m_hotcuePosition.get(),
1888             &ControlObject::valueChanged,
1889             this,
1890             &HotcueControl::slotHotcuePositionChanged,
1891             Qt::DirectConnection);
1892     m_hotcuePosition->set(Cue::kNoPosition);
1893 
1894     m_hotcueEnabled = std::make_unique<ControlObject>(keyForControl(QStringLiteral("enabled")));
1895     m_hotcueEnabled->setReadOnly();
1896 
1897     // The rgba value  of the color assigned to this color.
1898     m_hotcueColor = std::make_unique<ControlObject>(keyForControl(QStringLiteral("color")));
1899     m_hotcueColor->connectValueChangeRequest(
1900             this,
1901             &HotcueControl::slotHotcueColorChangeRequest,
1902             Qt::DirectConnection);
1903     connect(m_hotcueColor.get(),
1904             &ControlObject::valueChanged,
1905             this,
1906             &HotcueControl::slotHotcueColorChanged,
1907             Qt::DirectConnection);
1908 
1909     m_hotcueSet = std::make_unique<ControlPushButton>(keyForControl(QStringLiteral("set")));
1910     connect(m_hotcueSet.get(),
1911             &ControlObject::valueChanged,
1912             this,
1913             &HotcueControl::slotHotcueSet,
1914             Qt::DirectConnection);
1915 
1916     m_hotcueGoto = std::make_unique<ControlPushButton>(keyForControl(QStringLiteral("goto")));
1917     connect(m_hotcueGoto.get(),
1918             &ControlObject::valueChanged,
1919             this,
1920             &HotcueControl::slotHotcueGoto,
1921             Qt::DirectConnection);
1922 
1923     m_hotcueGotoAndPlay = std::make_unique<ControlPushButton>(
1924             keyForControl(QStringLiteral("gotoandplay")));
1925     connect(m_hotcueGotoAndPlay.get(),
1926             &ControlObject::valueChanged,
1927             this,
1928             &HotcueControl::slotHotcueGotoAndPlay,
1929             Qt::DirectConnection);
1930 
1931     m_hotcueGotoAndStop = std::make_unique<ControlPushButton>(
1932             keyForControl(QStringLiteral("gotoandstop")));
1933     connect(m_hotcueGotoAndStop.get(),
1934             &ControlObject::valueChanged,
1935             this,
1936             &HotcueControl::slotHotcueGotoAndStop,
1937             Qt::DirectConnection);
1938 
1939     m_hotcueActivate = std::make_unique<ControlPushButton>(
1940             keyForControl(QStringLiteral("activate")));
1941     connect(m_hotcueActivate.get(),
1942             &ControlObject::valueChanged,
1943             this,
1944             &HotcueControl::slotHotcueActivate,
1945             Qt::DirectConnection);
1946 
1947     m_hotcueActivatePreview = std::make_unique<ControlPushButton>(
1948             keyForControl(QStringLiteral("activate_preview")));
1949     connect(m_hotcueActivatePreview.get(),
1950             &ControlObject::valueChanged,
1951             this,
1952             &HotcueControl::slotHotcueActivatePreview,
1953             Qt::DirectConnection);
1954 
1955     m_hotcueClear = std::make_unique<ControlPushButton>(keyForControl(QStringLiteral("clear")));
1956     connect(m_hotcueClear.get(),
1957             &ControlObject::valueChanged,
1958             this,
1959             &HotcueControl::slotHotcueClear,
1960             Qt::DirectConnection);
1961 }
1962 
1963 HotcueControl::~HotcueControl() = default;
1964 
slotHotcueSet(double v)1965 void HotcueControl::slotHotcueSet(double v) {
1966     emit hotcueSet(this, v);
1967 }
1968 
slotHotcueGoto(double v)1969 void HotcueControl::slotHotcueGoto(double v) {
1970     emit hotcueGoto(this, v);
1971 }
1972 
slotHotcueGotoAndPlay(double v)1973 void HotcueControl::slotHotcueGotoAndPlay(double v) {
1974     emit hotcueGotoAndPlay(this, v);
1975 }
1976 
slotHotcueGotoAndStop(double v)1977 void HotcueControl::slotHotcueGotoAndStop(double v) {
1978     emit hotcueGotoAndStop(this, v);
1979 }
1980 
slotHotcueActivate(double v)1981 void HotcueControl::slotHotcueActivate(double v) {
1982     emit hotcueActivate(this, v);
1983 }
1984 
slotHotcueActivatePreview(double v)1985 void HotcueControl::slotHotcueActivatePreview(double v) {
1986     emit hotcueActivatePreview(this, v);
1987 }
1988 
slotHotcueClear(double v)1989 void HotcueControl::slotHotcueClear(double v) {
1990     emit hotcueClear(this, v);
1991 }
1992 
slotHotcuePositionChanged(double newPosition)1993 void HotcueControl::slotHotcuePositionChanged(double newPosition) {
1994     m_hotcueEnabled->forceSet(newPosition == Cue::kNoPosition ? 0.0 : 1.0);
1995     emit hotcuePositionChanged(this, newPosition);
1996 }
1997 
slotHotcueColorChangeRequest(double color)1998 void HotcueControl::slotHotcueColorChangeRequest(double color) {
1999     if (color < 0 || color > 0xFFFFFF) {
2000         qWarning() << "slotHotcueColorChanged got invalid value:" << color;
2001         return;
2002     }
2003     m_hotcueColor->setAndConfirm(color);
2004 }
2005 
slotHotcueColorChanged(double newColor)2006 void HotcueControl::slotHotcueColorChanged(double newColor) {
2007     if (!m_pCue) {
2008         return;
2009     }
2010 
2011     mixxx::RgbColor::optional_t color = doubleToRgbColor(newColor);
2012     VERIFY_OR_DEBUG_ASSERT(color) {
2013         return;
2014     }
2015 
2016     m_pCue->setColor(*color);
2017     emit hotcueColorChanged(this, newColor);
2018 }
2019 
getPosition() const2020 double HotcueControl::getPosition() const {
2021     return m_hotcuePosition->get();
2022 }
2023 
setCue(const CuePointer & pCue)2024 void HotcueControl::setCue(const CuePointer& pCue) {
2025     setPosition(pCue->getPosition());
2026     setColor(pCue->getColor());
2027     // set pCue only if all other data is in place
2028     // because we have a null check for valid data else where in the code
2029     m_pCue = pCue;
2030 }
getColor() const2031 mixxx::RgbColor::optional_t HotcueControl::getColor() const {
2032     return doubleToRgbColor(m_hotcueColor->get());
2033 }
2034 
setColor(mixxx::RgbColor::optional_t newColor)2035 void HotcueControl::setColor(mixxx::RgbColor::optional_t newColor) {
2036     if (newColor) {
2037         m_hotcueColor->set(*newColor);
2038     }
2039 }
resetCue()2040 void HotcueControl::resetCue() {
2041     // clear pCue first because we have a null check for valid data else where
2042     // in the code
2043     m_pCue.reset();
2044     setPosition(Cue::kNoPosition);
2045 }
2046 
setPosition(double position)2047 void HotcueControl::setPosition(double position) {
2048     m_hotcuePosition->set(position);
2049     m_hotcueEnabled->forceSet(position == Cue::kNoPosition ? 0.0 : 1.0);
2050 }
2051