1 #include "engine/sync/internalclock.h"
2 
3 #include <QtDebug>
4 
5 #include "control/controllinpotmeter.h"
6 #include "control/controlobject.h"
7 #include "control/controlpushbutton.h"
8 #include "engine/sync/enginesync.h"
9 #include "moc_internalclock.cpp"
10 #include "preferences/usersettings.h"
11 #include "util/logger.h"
12 
13 namespace {
14 const mixxx::Logger kLogger("InternalClock");
15 } // namespace
16 
InternalClock(const QString & group,SyncableListener * pEngineSync)17 InternalClock::InternalClock(const QString& group, SyncableListener* pEngineSync)
18         : m_group(group),
19           m_pEngineSync(pEngineSync),
20           m_mode(SYNC_NONE),
21           m_iOldSampleRate(44100),
22           m_dOldBpm(124.0),
23           m_dBaseBpm(124.0),
24           m_dBeatLength(m_iOldSampleRate * 60.0 / m_dOldBpm),
25           m_dClockPosition(0) {
26     // Pick a wide range (1 to 200) and allow out of bounds sets. This lets you
27     // map a soft-takeover MIDI knob to the master BPM. This also creates bpm_up
28     // and bpm_down controls.
29     // bpm_up / bpm_down steps by 1
30     // bpm_up_small / bpm_down_small steps by 0.1
31     m_pClockBpm.reset(
32             new ControlLinPotmeter(ConfigKey(m_group, "bpm"), 1, 200, 1, 0.1, true));
33     connect(m_pClockBpm.data(), &ControlObject::valueChanged,
34             this, &InternalClock::slotBpmChanged,
35             Qt::DirectConnection);
36 
37     // The relative position between two beats in the range 0.0 ... 1.0
38     m_pClockBeatDistance.reset(new ControlObject(ConfigKey(m_group, "beat_distance")));
39     connect(m_pClockBeatDistance.data(), &ControlObject::valueChanged,
40             this, &InternalClock::slotBeatDistanceChanged,
41             Qt::DirectConnection);
42 
43     m_pSyncMasterEnabled.reset(
44             new ControlPushButton(ConfigKey(m_group, "sync_master")));
45     m_pSyncMasterEnabled->setButtonMode(ControlPushButton::TOGGLE);
46     m_pSyncMasterEnabled->connectValueChangeRequest(
47             this, &InternalClock::slotSyncMasterEnabledChangeRequest, Qt::DirectConnection);
48 }
49 
~InternalClock()50 InternalClock::~InternalClock() {
51 }
52 
setSyncMode(SyncMode mode)53 void InternalClock::setSyncMode(SyncMode mode) {
54     // Syncable has absolutely no say in the matter. This is what EngineSync
55     // requires. Bypass confirmation by using setAndConfirm.
56     m_mode = mode;
57     m_pSyncMasterEnabled->setAndConfirm(isMaster(mode));
58 }
59 
notifyOnlyPlayingSyncable()60 void InternalClock::notifyOnlyPlayingSyncable() {
61     // No action necessary.
62 }
63 
requestSync()64 void InternalClock::requestSync() {
65     // TODO(owilliams): This should probably be how we reset the internal beat distance.
66 }
67 
slotSyncMasterEnabledChangeRequest(double state)68 void InternalClock::slotSyncMasterEnabledChangeRequest(double state) {
69     SyncMode mode = m_mode;
70     //Note: internal clock is always sync enabled
71     if (state > 0.0) {
72         if (mode == SYNC_MASTER_EXPLICIT) {
73             // Already master.
74             return;
75         }
76         if (mode == SYNC_MASTER_SOFT) {
77             // user request: make master explicit
78             m_mode = SYNC_MASTER_EXPLICIT;
79             return;
80         }
81         if (mode == SYNC_NONE) {
82             m_dBaseBpm = m_dOldBpm;
83         }
84         m_pEngineSync->requestSyncMode(this, SYNC_MASTER_EXPLICIT);
85     } else {
86         // Turning off master goes back to follower mode.
87         if (mode == SYNC_FOLLOWER) {
88             // Already not master.
89             return;
90         }
91         m_pEngineSync->requestSyncMode(this, SYNC_FOLLOWER);
92     }
93 }
94 
getBeatDistance() const95 double InternalClock::getBeatDistance() const {
96     if (m_dBeatLength <= 0) {
97         qDebug() << "ERROR: InternalClock beat length should never be less than zero";
98         return 0.0;
99     }
100     return m_dClockPosition / m_dBeatLength;
101 }
102 
setMasterBeatDistance(double beatDistance)103 void InternalClock::setMasterBeatDistance(double beatDistance) {
104     if (kLogger.traceEnabled()) {
105         kLogger.trace() << "InternalClock::setMasterBeatDistance" << beatDistance;
106     }
107     m_dClockPosition = beatDistance * m_dBeatLength;
108     m_pClockBeatDistance->set(beatDistance);
109     // Make sure followers have an up-to-date beat distance.
110     m_pEngineSync->notifyBeatDistanceChanged(this, beatDistance);
111 }
112 
getBaseBpm() const113 double InternalClock::getBaseBpm() const {
114     return m_dBaseBpm;
115 }
116 
getBpm() const117 double InternalClock::getBpm() const {
118     return m_pClockBpm->get();
119 }
120 
setMasterBpm(double bpm)121 void InternalClock::setMasterBpm(double bpm) {
122     if (kLogger.traceEnabled()) {
123         kLogger.trace() << "InternalClock::setBpm" << bpm;
124     }
125     if (bpm == 0) {
126         return;
127     }
128     m_pClockBpm->set(bpm);
129     updateBeatLength(m_iOldSampleRate, bpm);
130 }
131 
setInstantaneousBpm(double bpm)132 void InternalClock::setInstantaneousBpm(double bpm) {
133     if (kLogger.traceEnabled()) {
134         kLogger.trace() << "InternalClock::setInstantaneousBpm" << bpm;
135     }
136     // Do nothing.
137     Q_UNUSED(bpm);
138 }
139 
setMasterParams(double beatDistance,double baseBpm,double bpm)140 void InternalClock::setMasterParams(double beatDistance, double baseBpm, double bpm) {
141     if (kLogger.traceEnabled()) {
142         kLogger.trace() << "InternalClock::setMasterParams" << beatDistance << baseBpm << bpm;
143     }
144     if (bpm <= 0.0 || baseBpm <= 0.0) {
145         return;
146     }
147     m_dBaseBpm = baseBpm;
148     setMasterBpm(bpm);
149     setMasterBeatDistance(beatDistance);
150 }
151 
slotBpmChanged(double bpm)152 void InternalClock::slotBpmChanged(double bpm) {
153     updateBeatLength(m_iOldSampleRate, bpm);
154     if (!isSynchronized()) {
155         return;
156     }
157     m_pEngineSync->notifyBpmChanged(this, bpm);
158 }
159 
slotBeatDistanceChanged(double beat_distance)160 void InternalClock::slotBeatDistanceChanged(double beat_distance) {
161     if (beat_distance < 0.0 || beat_distance > 1.0) {
162         return;
163     }
164     setMasterBeatDistance(beat_distance);
165 }
166 
updateBeatLength(int sampleRate,double bpm)167 void InternalClock::updateBeatLength(int sampleRate, double bpm) {
168     if (m_iOldSampleRate == sampleRate && bpm == m_dOldBpm) {
169         return;
170     }
171 
172     // Changing the beat length changes the beat distance. Record the current
173     // beat distance so we can restore it when we are done.
174     double oldBeatDistance = getBeatDistance();
175 
176     //to get samples per beat, do:
177     //
178     // samples   samples     60 seconds     minutes
179     // ------- = -------  *  ----------  *  -------
180     //   beat    second       1 minute       beats
181 
182     // that last term is 1 over bpm.
183 
184     if (qFuzzyCompare(bpm, 0)) {
185         qDebug() << "WARNING: Master bpm reported to be zero, internal clock guessing 124bpm";
186         m_dBeatLength = (sampleRate * 60.0) / 124.0;
187         m_dOldBpm = 124.0;
188     } else {
189         m_dOldBpm = bpm;
190         m_dBeatLength = (sampleRate * 60.0) / bpm;
191         if (m_dBeatLength <= 0) {
192             qDebug() << "WARNING: Tried to set samples per beat <=0";
193             m_dBeatLength = sampleRate;
194         }
195     }
196 
197     m_iOldSampleRate = sampleRate;
198 
199     // Restore the old beat distance.
200     setMasterBeatDistance(oldBeatDistance);
201 }
202 
onCallbackStart(int sampleRate,int bufferSize)203 void InternalClock::onCallbackStart(int sampleRate, int bufferSize) {
204     Q_UNUSED(sampleRate)
205     Q_UNUSED(bufferSize)
206     m_pEngineSync->notifyInstantaneousBpmChanged(this, getBpm());
207 }
208 
onCallbackEnd(int sampleRate,int bufferSize)209 void InternalClock::onCallbackEnd(int sampleRate, int bufferSize) {
210     updateBeatLength(sampleRate, m_pClockBpm->get());
211 
212     // stereo samples, so divide by 2
213     m_dClockPosition += bufferSize / 2;
214 
215     // Can't use mod because we're in double land.
216     if (m_dBeatLength <= 0) {
217         qDebug() << "ERROR: Calculated <= 0 samples per beat which is impossible.  Forcibly "
218                  << "setting to about 124 bpm at 44.1Khz.";
219         m_dBeatLength = 21338;
220     }
221 
222     while (m_dClockPosition >= m_dBeatLength) {
223         m_dClockPosition -= m_dBeatLength;
224     }
225 
226     double beat_distance = getBeatDistance();
227     m_pClockBeatDistance->set(beat_distance);
228     m_pEngineSync->notifyBeatDistanceChanged(this, beat_distance);
229 }
230