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