1 #include "mixer/basetrackplayer.h"
2
3 #include <QMessageBox>
4
5 #include "control/controlobject.h"
6 #include "control/controlpotmeter.h"
7 #include "effects/effectsmanager.h"
8 #include "engine/channels/enginedeck.h"
9 #include "engine/controls/enginecontrol.h"
10 #include "engine/engine.h"
11 #include "engine/enginebuffer.h"
12 #include "engine/enginemaster.h"
13 #include "engine/sync/enginesync.h"
14 #include "mixer/playerinfo.h"
15 #include "mixer/playermanager.h"
16 #include "moc_basetrackplayer.cpp"
17 #include "sources/soundsourceproxy.h"
18 #include "track/beatgrid.h"
19 #include "track/track.h"
20 #include "util/compatibility.h"
21 #include "util/platform.h"
22 #include "util/sandbox.h"
23 #include "vinylcontrol/defs_vinylcontrol.h"
24 #include "waveform/renderers/waveformwidgetrenderer.h"
25 #include "waveform/visualsmanager.h"
26
27 namespace {
28
29 const double kNoTrackColor = -1;
30 const double kShiftCuesOffsetMillis = 10;
31 const double kShiftCuesOffsetSmallMillis = 1;
32
trackColorToDouble(mixxx::RgbColor::optional_t color)33 inline double trackColorToDouble(mixxx::RgbColor::optional_t color) {
34 return (color ? static_cast<double>(*color) : kNoTrackColor);
35 }
36 } // namespace
37
BaseTrackPlayer(QObject * pParent,const QString & group)38 BaseTrackPlayer::BaseTrackPlayer(QObject* pParent, const QString& group)
39 : BasePlayer(pParent, group) {
40 }
41
BaseTrackPlayerImpl(QObject * pParent,UserSettingsPointer pConfig,EngineMaster * pMixingEngine,EffectsManager * pEffectsManager,VisualsManager * pVisualsManager,EngineChannel::ChannelOrientation defaultOrientation,const QString & group,bool defaultMaster,bool defaultHeadphones,bool primaryDeck)42 BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent,
43 UserSettingsPointer pConfig,
44 EngineMaster* pMixingEngine,
45 EffectsManager* pEffectsManager,
46 VisualsManager* pVisualsManager,
47 EngineChannel::ChannelOrientation defaultOrientation,
48 const QString& group,
49 bool defaultMaster,
50 bool defaultHeadphones,
51 bool primaryDeck)
52 : BaseTrackPlayer(pParent, group),
53 m_pConfig(pConfig),
54 m_pEngineMaster(pMixingEngine),
55 m_pLoadedTrack(),
56 m_replaygainPending(false),
57 m_pChannelToCloneFrom(nullptr) {
58 ChannelHandleAndGroup channelGroup =
59 pMixingEngine->registerChannelGroup(group);
60 m_pChannel = new EngineDeck(channelGroup, pConfig, pMixingEngine, pEffectsManager, defaultOrientation, primaryDeck);
61
62 m_pInputConfigured = make_parented<ControlProxy>(group, "input_configured", this);
63 #ifdef __VINYLCONTROL__
64 m_pVinylControlEnabled = make_parented<ControlProxy>(group, "vinylcontrol_enabled", this);
65 m_pVinylControlEnabled->connectValueChanged(this, &BaseTrackPlayerImpl::slotVinylControlEnabled);
66 m_pVinylControlStatus = make_parented<ControlProxy>(group, "vinylcontrol_status", this);
67 #endif
68
69 EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer();
70 pMixingEngine->addChannel(m_pChannel);
71
72 // Set the routing option defaults for the master and headphone mixes.
73 m_pChannel->setMaster(defaultMaster);
74 m_pChannel->setPfl(defaultHeadphones);
75
76 // Connect our signals and slots with the EngineBuffer's signals and
77 // slots. This will let us know when the reader is done loading a track, and
78 // let us request that the reader load a track.
79 connect(pEngineBuffer, &EngineBuffer::trackLoaded, this, &BaseTrackPlayerImpl::slotTrackLoaded);
80 connect(pEngineBuffer,
81 &EngineBuffer::trackLoadFailed,
82 this,
83 &BaseTrackPlayerImpl::slotLoadFailed);
84
85 // Get loop point control objects
86 m_pLoopInPoint = make_parented<ControlProxy>(
87 getGroup(), "loop_start_position", this);
88 m_pLoopOutPoint = make_parented<ControlProxy>(
89 getGroup(), "loop_end_position", this);
90
91 // Duration of the current song, we create this one because nothing else does.
92 m_pDuration = std::make_unique<ControlObject>(
93 ConfigKey(getGroup(), "duration"));
94
95 // Track color of the current track
96 m_pTrackColor = std::make_unique<ControlObject>(
97 ConfigKey(getGroup(), "track_color"));
98
99 m_pTrackColor->set(kNoTrackColor);
100 m_pTrackColor->connectValueChangeRequest(
101 this, &BaseTrackPlayerImpl::slotTrackColorChangeRequest);
102
103 // Deck cloning
104 m_pCloneFromDeck = std::make_unique<ControlObject>(
105 ConfigKey(getGroup(), "CloneFromDeck"),
106 false);
107 connect(m_pCloneFromDeck.get(),
108 &ControlObject::valueChanged,
109 this,
110 &BaseTrackPlayerImpl::slotCloneFromDeck);
111
112 // Sampler cloning
113 m_pCloneFromSampler = std::make_unique<ControlObject>(
114 ConfigKey(getGroup(), "CloneFromSampler"),
115 false);
116 connect(m_pCloneFromSampler.get(),
117 &ControlObject::valueChanged,
118 this,
119 &BaseTrackPlayerImpl::slotCloneFromSampler);
120
121 // Waveform controls
122 // This acts somewhat like a ControlPotmeter, but the normal _up/_down methods
123 // do not work properly with this CO.
124 m_pWaveformZoom =
125 std::make_unique<ControlObject>(ConfigKey(group, "waveform_zoom"));
126 m_pWaveformZoom->connectValueChangeRequest(this,
127 &BaseTrackPlayerImpl::slotWaveformZoomValueChangeRequest,
128 Qt::DirectConnection);
129 m_pWaveformZoom->set(1.0);
130 m_pWaveformZoomUp = std::make_unique<ControlPushButton>(
131 ConfigKey(group, "waveform_zoom_up"));
132 connect(m_pWaveformZoomUp.get(),
133 &ControlPushButton::valueChanged,
134 this,
135 &BaseTrackPlayerImpl::slotWaveformZoomUp);
136 m_pWaveformZoomDown = std::make_unique<ControlPushButton>(
137 ConfigKey(group, "waveform_zoom_down"));
138 connect(m_pWaveformZoomDown.get(),
139 &ControlPushButton::valueChanged,
140 this,
141 &BaseTrackPlayerImpl::slotWaveformZoomDown);
142 m_pWaveformZoomSetDefault = std::make_unique<ControlPushButton>(
143 ConfigKey(group, "waveform_zoom_set_default"));
144 connect(m_pWaveformZoomSetDefault.get(),
145 &ControlPushButton::valueChanged,
146 this,
147 &BaseTrackPlayerImpl::slotWaveformZoomSetDefault);
148
149 m_pPreGain = make_parented<ControlProxy>(group, "pregain", this);
150
151 m_pShiftCuesEarlier = std::make_unique<ControlPushButton>(
152 ConfigKey(group, "shift_cues_earlier"));
153 connect(m_pShiftCuesEarlier.get(),
154 &ControlObject::valueChanged,
155 this,
156 [this](double value) { slotShiftCuesMillisButton(value, -kShiftCuesOffsetMillis); });
157 m_pShiftCuesLater = std::make_unique<ControlPushButton>(
158 ConfigKey(group, "shift_cues_later"));
159 connect(m_pShiftCuesLater.get(),
160 &ControlObject::valueChanged,
161 this,
162 [this](double value) { slotShiftCuesMillisButton(value, kShiftCuesOffsetMillis); });
163 m_pShiftCuesEarlierSmall = std::make_unique<ControlPushButton>(
164 ConfigKey(group, "shift_cues_earlier_small"));
165 connect(m_pShiftCuesEarlierSmall.get(),
166 &ControlObject::valueChanged,
167 this,
168 [this](double value) {
169 slotShiftCuesMillisButton(value, -kShiftCuesOffsetSmallMillis);
170 });
171 m_pShiftCuesLaterSmall = std::make_unique<ControlPushButton>(
172 ConfigKey(group, "shift_cues_later_small"));
173 connect(m_pShiftCuesLaterSmall.get(),
174 &ControlObject::valueChanged,
175 this,
176 [this](double value) {
177 slotShiftCuesMillisButton(value, kShiftCuesOffsetSmallMillis);
178 });
179 m_pShiftCues = std::make_unique<ControlObject>(
180 ConfigKey(group, "shift_cues"));
181 connect(m_pShiftCues.get(),
182 &ControlObject::valueChanged,
183 this,
184 &BaseTrackPlayerImpl::slotShiftCuesMillis);
185
186 // BPM of the current song
187 m_pFileBPM = std::make_unique<ControlObject>(ConfigKey(group, "file_bpm"));
188 m_pKey = make_parented<ControlProxy>(group, "file_key", this);
189
190 m_pReplayGain = make_parented<ControlProxy>(group, "replaygain", this);
191 m_pPlay = make_parented<ControlProxy>(group, "play", this);
192 m_pPlay->connectValueChanged(this, &BaseTrackPlayerImpl::slotPlayToggled);
193
194 m_pRateRatio = make_parented<ControlProxy>(group, "rate_ratio", this);
195 m_pPitchAdjust = make_parented<ControlProxy>(group, "pitch_adjust", this);
196
197 pVisualsManager->addDeck(group);
198 }
199
~BaseTrackPlayerImpl()200 BaseTrackPlayerImpl::~BaseTrackPlayerImpl() {
201 unloadTrack();
202 }
203
loadFakeTrack(bool bPlay,double filebpm)204 TrackPointer BaseTrackPlayerImpl::loadFakeTrack(bool bPlay, double filebpm) {
205 TrackPointer pTrack(Track::newTemporary());
206 pTrack->setAudioProperties(
207 mixxx::kEngineChannelCount,
208 mixxx::audio::SampleRate(44100),
209 mixxx::audio::Bitrate(),
210 mixxx::Duration::fromSeconds(10));
211 if (filebpm > 0) {
212 pTrack->trySetBpm(filebpm);
213 }
214
215 TrackPointer pOldTrack = m_pLoadedTrack;
216 m_pLoadedTrack = pTrack;
217 if (m_pLoadedTrack) {
218 // Listen for updates to the file's BPM
219 connect(m_pLoadedTrack.get(),
220 &Track::bpmUpdated,
221 m_pFileBPM.get(),
222 QOverload<double>::of(&ControlObject::set));
223
224 connect(m_pLoadedTrack.get(),
225 &Track::keyUpdated,
226 m_pKey.get(),
227 &ControlProxy::set);
228
229 // Listen for updates to the file's Replay Gain
230 connect(m_pLoadedTrack.get(),
231 &Track::replayGainUpdated,
232 this,
233 &BaseTrackPlayerImpl::slotSetReplayGain);
234
235 connect(m_pLoadedTrack.get(),
236 &Track::colorUpdated,
237 this,
238 &BaseTrackPlayerImpl::slotSetTrackColor);
239 }
240
241 // Request a new track from EngineBuffer
242 EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer();
243 pEngineBuffer->loadFakeTrack(pTrack, bPlay);
244
245 // await slotTrackLoaded()/slotLoadFailed()
246 emit loadingTrack(pTrack, pOldTrack);
247
248 return pTrack;
249 }
250
loadTrack(TrackPointer pTrack)251 void BaseTrackPlayerImpl::loadTrack(TrackPointer pTrack) {
252 DEBUG_ASSERT(!m_pLoadedTrack);
253
254 m_pLoadedTrack = std::move(pTrack);
255 if (!m_pLoadedTrack) {
256 // nothing to
257 return;
258 }
259
260 // Clear loop
261 // It seems that the trick is to first clear the loop out point, and then
262 // the loop in point. If we first clear the loop in point, the loop out point
263 // does not get cleared.
264 m_pLoopOutPoint->set(kNoTrigger);
265 m_pLoopInPoint->set(kNoTrigger);
266
267 // The loop in and out points must be set here and not in slotTrackLoaded
268 // so LoopingControl::trackLoaded can access them.
269 if (!m_pChannelToCloneFrom) {
270 const QList<CuePointer> trackCues(m_pLoadedTrack->getCuePoints());
271 QListIterator<CuePointer> it(trackCues);
272 CuePointer pLoopCue;
273 // Restore loop from the first loop cue with minimum hotcue number.
274 // For the volatile "most recent loop" the hotcue number will be -1.
275 // If no such loop exists, restore a saved loop cue.
276 while (it.hasNext()) {
277 CuePointer pCue(it.next());
278 if (pCue->getType() != mixxx::CueType::Loop) {
279 continue;
280 }
281
282 if (pLoopCue && pLoopCue->getHotCue() <= pCue->getHotCue()) {
283 continue;
284 }
285
286 pLoopCue = pCue;
287 }
288
289 if (pLoopCue) {
290 double loopStart = pLoopCue->getPosition();
291 double loopEnd = loopStart + pLoopCue->getLength();
292 if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) {
293 m_pLoopInPoint->set(loopStart);
294 m_pLoopOutPoint->set(loopEnd);
295 }
296 }
297 } else {
298 // copy loop in and out points from other deck because any new loops
299 // won't be saved yet
300 m_pLoopInPoint->set(ControlObject::get(
301 ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_start_position")));
302 m_pLoopOutPoint->set(ControlObject::get(
303 ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_end_position")));
304 }
305
306 connectLoadedTrack();
307 }
308
unloadTrack()309 TrackPointer BaseTrackPlayerImpl::unloadTrack() {
310 if (!m_pLoadedTrack) {
311 // nothing to do
312 return TrackPointer();
313 }
314
315 // Save the loops that are currently set in a loop cue. If no loop cue is
316 // currently on the track, then create a new one.
317 double loopStart = m_pLoopInPoint->get();
318 double loopEnd = m_pLoopOutPoint->get();
319 if (loopStart != kNoTrigger && loopEnd != kNoTrigger && loopStart <= loopEnd) {
320 CuePointer pLoopCue;
321 QList<CuePointer> cuePoints(m_pLoadedTrack->getCuePoints());
322 QListIterator<CuePointer> it(cuePoints);
323 while (it.hasNext()) {
324 CuePointer pCue(it.next());
325 if (pCue->getType() == mixxx::CueType::Loop && pCue->getHotCue() == Cue::kNoHotCue) {
326 pLoopCue = pCue;
327 break;
328 }
329 }
330 if (!pLoopCue) {
331 pLoopCue = m_pLoadedTrack->createAndAddCue();
332 pLoopCue->setType(mixxx::CueType::Loop);
333 }
334 pLoopCue->setStartPosition(loopStart);
335 pLoopCue->setEndPosition(loopEnd);
336 }
337
338 disconnectLoadedTrack();
339
340 // Do not reset m_pReplayGain here, because the track might be still
341 // playing and the last buffer will be processed.
342
343 m_pPlay->set(0.0);
344
345 TrackPointer pUnloadedTrack(std::move(m_pLoadedTrack));
346 DEBUG_ASSERT(!m_pLoadedTrack);
347 return pUnloadedTrack;
348 }
349
connectLoadedTrack()350 void BaseTrackPlayerImpl::connectLoadedTrack() {
351 connect(m_pLoadedTrack.get(),
352 &Track::bpmUpdated,
353 m_pFileBPM.get(),
354 QOverload<double>::of(&ControlObject::set));
355 connect(m_pLoadedTrack.get(),
356 &Track::keyUpdated,
357 m_pKey.get(),
358 &ControlProxy::set);
359 connect(m_pLoadedTrack.get(),
360 &Track::replayGainUpdated,
361 this,
362 &BaseTrackPlayerImpl::slotSetReplayGain);
363 connect(m_pLoadedTrack.get(),
364 &Track::colorUpdated,
365 this,
366 &BaseTrackPlayerImpl::slotSetTrackColor);
367 }
368
disconnectLoadedTrack()369 void BaseTrackPlayerImpl::disconnectLoadedTrack() {
370 // WARNING: Never. Ever. call bare disconnect() on an object. Mixxx
371 // relies on signals and slots to get tons of things done. Don't
372 // randomly disconnect things.
373 disconnect(m_pLoadedTrack.get(), nullptr, m_pFileBPM.get(), nullptr);
374 disconnect(m_pLoadedTrack.get(), nullptr, this, nullptr);
375 disconnect(m_pLoadedTrack.get(), nullptr, m_pKey.get(), nullptr);
376 }
377
slotLoadTrack(TrackPointer pNewTrack,bool bPlay)378 void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) {
379 qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup();
380 // Before loading the track, ensure we have access. This uses lazy
381 // evaluation to make sure track isn't NULL before we dereference it.
382 if (pNewTrack && !Sandbox::askForAccess(pNewTrack->getCanonicalLocation())) {
383 // We don't have access.
384 return;
385 }
386
387 auto pOldTrack = unloadTrack();
388
389 loadTrack(pNewTrack);
390
391 // Request a new track from EngineBuffer
392 EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer();
393 pEngineBuffer->loadTrack(pNewTrack, bPlay);
394
395 // await slotTrackLoaded()/slotLoadFailed()
396 emit loadingTrack(pNewTrack, pOldTrack);
397 }
398
slotLoadFailed(TrackPointer pTrack,const QString & reason)399 void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, const QString& reason) {
400 // Note: This slot can be a load failure from the current track or a
401 // a delayed signal from a previous load.
402 // We have probably received a slotTrackLoaded signal, of an old track that
403 // was loaded before. Here we must unload the
404 // We must unload the track m_pLoadedTrack as well
405 if (pTrack == m_pLoadedTrack) {
406 qDebug() << "Failed to load track" << pTrack->getFileInfo() << reason;
407 slotTrackLoaded(TrackPointer(), pTrack);
408 } else if (pTrack) {
409 qDebug() << "Stray failed to load track" << pTrack->getFileInfo() << reason;
410 } else {
411 qDebug() << "Failed to load track (NULL track object)" << reason;
412 }
413 m_pChannelToCloneFrom = nullptr;
414 // Alert user.
415 QMessageBox::warning(nullptr, tr("Couldn't load track."), reason);
416 }
417
slotTrackLoaded(TrackPointer pNewTrack,TrackPointer pOldTrack)418 void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
419 TrackPointer pOldTrack) {
420 //qDebug() << "BaseTrackPlayerImpl::slotTrackLoaded";
421 if (!pNewTrack &&
422 pOldTrack &&
423 pOldTrack == m_pLoadedTrack) {
424 // eject Track
425 unloadTrack();
426
427 // Causes the track's data to be saved back to the library database and
428 // for all the widgets to change the track and update themselves.
429 emit loadingTrack(pNewTrack, pOldTrack);
430 m_pDuration->set(0);
431 m_pFileBPM->set(0);
432 m_pKey->set(0);
433 setReplayGain(0);
434 slotSetTrackColor(std::nullopt);
435 m_pLoopInPoint->set(kNoTrigger);
436 m_pLoopOutPoint->set(kNoTrigger);
437 m_pLoadedTrack.reset();
438 emit playerEmpty();
439 } else if (pNewTrack && pNewTrack == m_pLoadedTrack) {
440 // NOTE(uklotzde): In a previous version track metadata was reloaded
441 // from the source file at this point again. This is no longer necessary
442 // since track objects will always be created in a controlled manner
443 // and populated from the database and their source file as required
444 // before handing them out to application code.
445 // TODO(XXX): Don't hesitate to delete the preceding NOTE if you think
446 // that it is not needed anymore.
447
448 // Update the BPM and duration values that are stored in ControlObjects
449 m_pDuration->set(m_pLoadedTrack->getDuration());
450 m_pFileBPM->set(m_pLoadedTrack->getBpm());
451 m_pKey->set(m_pLoadedTrack->getKey());
452 setReplayGain(m_pLoadedTrack->getReplayGain().getRatio());
453 slotSetTrackColor(m_pLoadedTrack->getColor());
454
455 if(m_pConfig->getValue(
456 ConfigKey("[Mixer Profile]", "EqAutoReset"), false)) {
457 if (m_pLowFilter) {
458 m_pLowFilter->set(1.0);
459 }
460 if (m_pMidFilter) {
461 m_pMidFilter->set(1.0);
462 }
463 if (m_pHighFilter) {
464 m_pHighFilter->set(1.0);
465 }
466 if (m_pLowFilterKill) {
467 m_pLowFilterKill->set(0.0);
468 }
469 if (m_pMidFilterKill) {
470 m_pMidFilterKill->set(0.0);
471 }
472 if (m_pHighFilterKill) {
473 m_pHighFilterKill->set(0.0);
474 }
475 }
476 if (m_pConfig->getValue(
477 ConfigKey("[Mixer Profile]", "GainAutoReset"), false)) {
478 m_pPreGain->set(1.0);
479 }
480
481 if (!m_pChannelToCloneFrom) {
482 int reset = m_pConfig->getValue<int>(
483 ConfigKey("[Controls]", "SpeedAutoReset"), RESET_PITCH);
484 if (reset == RESET_SPEED || reset == RESET_PITCH_AND_SPEED) {
485 // Avoid resetting speed if master sync is enabled and other decks with sync enabled
486 // are playing, as this would change the speed of already playing decks.
487 if (!m_pEngineMaster->getEngineSync()->otherSyncedPlaying(getGroup())) {
488 m_pRateRatio->set(1.0);
489 }
490 }
491 if (reset == RESET_PITCH || reset == RESET_PITCH_AND_SPEED) {
492 m_pPitchAdjust->set(0.0);
493 }
494 } else {
495 // perform a clone of the given channel
496
497 // copy rate
498 m_pRateRatio->set(ControlObject::get(ConfigKey(
499 m_pChannelToCloneFrom->getGroup(), "rate_ratio")));
500
501 // copy pitch
502 m_pPitchAdjust->set(ControlObject::get(ConfigKey(
503 m_pChannelToCloneFrom->getGroup(), "pitch_adjust")));
504
505 // copy play state
506 ControlObject::set(ConfigKey(getGroup(), "play"),
507 ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "play")));
508
509 // copy the play position
510 m_pChannel->getEngineBuffer()->requestClonePosition(m_pChannelToCloneFrom);
511
512 // copy the loop state
513 if (ControlObject::get(ConfigKey(m_pChannelToCloneFrom->getGroup(), "loop_enabled")) == 1.0) {
514 ControlObject::set(ConfigKey(getGroup(), "reloop_toggle"), 1.0);
515 }
516 }
517
518 emit newTrackLoaded(m_pLoadedTrack);
519 } else {
520 // this is the result from an outdated load or unload signal
521 // A new load is already pending
522 // Ignore this signal and wait for the new one
523 qDebug() << "stray BaseTrackPlayerImpl::slotTrackLoaded()";
524 }
525
526 m_pChannelToCloneFrom = nullptr;
527
528 // Update the PlayerInfo class that is used in EngineBroadcast to replace
529 // the metadata of a stream
530 PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack);
531 }
532
getLoadedTrack() const533 TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const {
534 return m_pLoadedTrack;
535 }
536
slotCloneDeck()537 void BaseTrackPlayerImpl::slotCloneDeck() {
538 Syncable* syncable = m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel);
539 if (syncable) {
540 slotCloneChannel(syncable->getChannel());
541 }
542 }
543
slotCloneFromGroup(const QString & group)544 void BaseTrackPlayerImpl::slotCloneFromGroup(const QString& group) {
545 EngineChannel* pChannel = m_pEngineMaster->getChannel(group);
546 if (!pChannel) {
547 return;
548 }
549
550 slotCloneChannel(pChannel);
551 }
552
slotCloneFromDeck(double d)553 void BaseTrackPlayerImpl::slotCloneFromDeck(double d) {
554 int deck = static_cast<int>(d);
555 if (deck < 1) {
556 slotCloneDeck();
557 } else {
558 slotCloneFromGroup(PlayerManager::groupForDeck(deck - 1));
559 }
560 }
561
slotCloneFromSampler(double d)562 void BaseTrackPlayerImpl::slotCloneFromSampler(double d) {
563 int sampler = static_cast<int>(d);
564 if (sampler >= 1) {
565 slotCloneFromGroup(PlayerManager::groupForSampler(sampler - 1));
566 }
567 }
568
slotCloneChannel(EngineChannel * pChannel)569 void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) {
570 // don't clone from ourselves
571 if (pChannel == m_pChannel) {
572 return;
573 }
574
575 m_pChannelToCloneFrom = pChannel;
576 if (!m_pChannelToCloneFrom) {
577 return;
578 }
579
580 TrackPointer pTrack = m_pChannelToCloneFrom->getEngineBuffer()->getLoadedTrack();
581 if (!pTrack) {
582 m_pChannelToCloneFrom = nullptr;
583 return;
584 }
585
586 slotLoadTrack(pTrack, false);
587 }
588
slotSetReplayGain(mixxx::ReplayGain replayGain)589 void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) {
590 // Do not change replay gain when track is playing because
591 // this may lead to an unexpected volume change
592 if (m_pPlay->get() == 0.0) {
593 setReplayGain(replayGain.getRatio());
594 } else {
595 m_replaygainPending = true;
596 }
597 }
598
slotSetTrackColor(mixxx::RgbColor::optional_t color)599 void BaseTrackPlayerImpl::slotSetTrackColor(mixxx::RgbColor::optional_t color) {
600 m_pTrackColor->forceSet(trackColorToDouble(color));
601 }
602
slotTrackColorChangeRequest(double v)603 void BaseTrackPlayerImpl::slotTrackColorChangeRequest(double v) {
604 if (!m_pLoadedTrack) {
605 return;
606 }
607
608 mixxx::RgbColor::optional_t color = std::nullopt;
609 if (v != kNoTrackColor) {
610 auto colorCode = static_cast<mixxx::RgbColor::code_t>(v);
611 if (!mixxx::RgbColor::isValidCode(colorCode)) {
612 return;
613 }
614 color = mixxx::RgbColor::optional(colorCode);
615 }
616 m_pTrackColor->setAndConfirm(trackColorToDouble(color));
617 m_pLoadedTrack->setColor(color);
618 }
619
slotPlayToggled(double value)620 void BaseTrackPlayerImpl::slotPlayToggled(double value) {
621 if (value == 0 && m_replaygainPending) {
622 setReplayGain(m_pLoadedTrack->getReplayGain().getRatio());
623 }
624 }
625
getEngineDeck() const626 EngineDeck* BaseTrackPlayerImpl::getEngineDeck() const {
627 return m_pChannel;
628 }
629
setupEqControls()630 void BaseTrackPlayerImpl::setupEqControls() {
631 const QString group = getGroup();
632 m_pLowFilter = make_parented<ControlProxy>(group, "filterLow", this);
633 m_pMidFilter = make_parented<ControlProxy>(group, "filterMid", this);
634 m_pHighFilter = make_parented<ControlProxy>(group, "filterHigh", this);
635 m_pLowFilterKill = make_parented<ControlProxy>(group, "filterLowKill", this);
636 m_pMidFilterKill = make_parented<ControlProxy>(group, "filterMidKill", this);
637 m_pHighFilterKill = make_parented<ControlProxy>(group, "filterHighKill", this);
638 }
639
slotVinylControlEnabled(double v)640 void BaseTrackPlayerImpl::slotVinylControlEnabled(double v) {
641 #ifdef __VINYLCONTROL__
642 bool configured = m_pInputConfigured->toBool();
643 bool vinylcontrol_enabled = v > 0.0;
644
645 // Warn the user if they try to enable vinyl control on a player with no
646 // configured input.
647 if (!configured && vinylcontrol_enabled) {
648 m_pVinylControlEnabled->set(0.0);
649 m_pVinylControlStatus->set(VINYL_STATUS_DISABLED);
650 emit noVinylControlInputConfigured();
651 }
652 #else
653 Q_UNUSED(v);
654 #endif
655 }
656
slotWaveformZoomValueChangeRequest(double v)657 void BaseTrackPlayerImpl::slotWaveformZoomValueChangeRequest(double v) {
658 if (v <= WaveformWidgetRenderer::s_waveformMaxZoom
659 && v >= WaveformWidgetRenderer::s_waveformMinZoom) {
660 m_pWaveformZoom->setAndConfirm(v);
661 }
662 }
663
slotWaveformZoomUp(double pressed)664 void BaseTrackPlayerImpl::slotWaveformZoomUp(double pressed) {
665 if (pressed <= 0.0) {
666 return;
667 }
668
669 m_pWaveformZoom->set(m_pWaveformZoom->get() + 1.0);
670 }
671
slotWaveformZoomDown(double pressed)672 void BaseTrackPlayerImpl::slotWaveformZoomDown(double pressed) {
673 if (pressed <= 0.0) {
674 return;
675 }
676
677 m_pWaveformZoom->set(m_pWaveformZoom->get() - 1.0);
678 }
679
slotWaveformZoomSetDefault(double pressed)680 void BaseTrackPlayerImpl::slotWaveformZoomSetDefault(double pressed) {
681 if (pressed <= 0.0) {
682 return;
683 }
684
685 double defaultZoom = m_pConfig->getValue(ConfigKey("[Waveform]","DefaultZoom"),
686 WaveformWidgetRenderer::s_waveformDefaultZoom);
687 m_pWaveformZoom->set(defaultZoom);
688 }
689
slotShiftCuesMillis(double milliseconds)690 void BaseTrackPlayerImpl::slotShiftCuesMillis(double milliseconds) {
691 if (!m_pLoadedTrack) {
692 return;
693 }
694 m_pLoadedTrack->shiftCuePositionsMillis(milliseconds);
695 }
696
slotShiftCuesMillisButton(double value,double milliseconds)697 void BaseTrackPlayerImpl::slotShiftCuesMillisButton(double value, double milliseconds) {
698 if (value <= 0) {
699 return;
700 }
701 slotShiftCuesMillis(milliseconds);
702 }
703
setReplayGain(double value)704 void BaseTrackPlayerImpl::setReplayGain(double value) {
705 m_pReplayGain->set(value);
706 m_replaygainPending = false;
707 }
708