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