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