1 #include "engine/controls/ratecontrol.h"
2
3 #include <QtDebug>
4
5 #include "control/controlobject.h"
6 #include "control/controlpotmeter.h"
7 #include "control/controlproxy.h"
8 #include "control/controlpushbutton.h"
9 #include "control/controlttrotary.h"
10 #include "engine/controls/bpmcontrol.h"
11 #include "engine/controls/enginecontrol.h"
12 #include "engine/positionscratchcontroller.h"
13 #include "moc_ratecontrol.cpp"
14 #include "util/rotary.h"
15 #include "vinylcontrol/defs_vinylcontrol.h"
16
17 namespace {
18 constexpr int kRateSensitivityMin = 100;
19 constexpr int kRateSensitivityMax = 2500;
20 } // namespace
21
22 // Static default values for rate buttons (percents)
23 ControlValueAtomic<double> RateControl::m_dTemporaryRateChangeCoarse;
24 ControlValueAtomic<double> RateControl::m_dTemporaryRateChangeFine;
25 ControlValueAtomic<double> RateControl::m_dPermanentRateChangeCoarse;
26 ControlValueAtomic<double> RateControl::m_dPermanentRateChangeFine;
27 int RateControl::m_iRateRampSensitivity;
28 RateControl::RampMode RateControl::m_eRateRampMode;
29
30 const double RateControl::kWheelMultiplier = 40.0;
31 const double RateControl::kPausedJogMultiplier = 18.0;
32
RateControl(const QString & group,UserSettingsPointer pConfig)33 RateControl::RateControl(const QString& group,
34 UserSettingsPointer pConfig)
35 : EngineControl(group, pConfig),
36 m_pBpmControl(nullptr),
37 m_bTempStarted(false),
38 m_tempRateRatio(0.0),
39 m_dRateTempRampChange(0.0) {
40 m_pScratchController = new PositionScratchController(group);
41
42 // This is the resulting rate ratio that can be used for display or calculations.
43 // The track original rate ratio is 1.
44 m_pRateRatio = new ControlObject(ConfigKey(group, "rate_ratio"),
45 true, false, false, 1.0);
46 connect(m_pRateRatio, &ControlObject::valueChanged,
47 this, &RateControl::slotRateRatioChanged,
48 Qt::DirectConnection);
49
50 m_pRateDir = new ControlObject(ConfigKey(group, "rate_dir"));
51 connect(m_pRateDir, &ControlObject::valueChanged,
52 this, &RateControl::slotRateRangeChanged,
53 Qt::DirectConnection);
54 m_pRateRange = new ControlPotmeter(
55 ConfigKey(group, "rateRange"), 0.01, 4.00);
56 connect(m_pRateRange, &ControlObject::valueChanged,
57 this, &RateControl::slotRateRangeChanged,
58 Qt::DirectConnection);
59
60 // Allow rate slider to go out of bounds so that master sync rate
61 // adjustments are not capped.
62 m_pRateSlider = new ControlPotmeter(
63 ConfigKey(group, "rate"), -1.0, 1.0, true);
64 connect(m_pRateSlider, &ControlObject::valueChanged,
65 this, &RateControl::slotRateSliderChanged,
66 Qt::DirectConnection);
67
68 // Search rate. Rate used when searching in sound. This overrules the
69 // playback rate
70 m_pRateSearch = new ControlPotmeter(ConfigKey(group, "rateSearch"), -300., 300.);
71
72 // Reverse button
73 m_pReverseButton = new ControlPushButton(ConfigKey(group, "reverse"));
74 m_pReverseButton->set(0);
75
76 // Forward button
77 m_pForwardButton = new ControlPushButton(ConfigKey(group, "fwd"));
78 connect(m_pForwardButton, &ControlObject::valueChanged,
79 this, &RateControl::slotControlFastForward,
80 Qt::DirectConnection);
81 m_pForwardButton->set(0);
82
83 // Back button
84 m_pBackButton = new ControlPushButton(ConfigKey(group, "back"));
85 connect(m_pBackButton, &ControlObject::valueChanged,
86 this, &RateControl::slotControlFastBack,
87 Qt::DirectConnection);
88 m_pBackButton->set(0);
89
90 m_pReverseRollButton = new ControlPushButton(ConfigKey(group, "reverseroll"));
91 connect(m_pReverseRollButton, &ControlObject::valueChanged,
92 this, &RateControl::slotReverseRollActivate,
93 Qt::DirectConnection);
94
95 m_pSlipEnabled = new ControlProxy(group, "slip_enabled", this);
96
97 m_pVCEnabled = ControlObject::getControl(ConfigKey(getGroup(), "vinylcontrol_enabled"));
98 m_pVCScratching = ControlObject::getControl(ConfigKey(getGroup(), "vinylcontrol_scratching"));
99 m_pVCMode = ControlObject::getControl(ConfigKey(getGroup(), "vinylcontrol_mode"));
100
101 // Permanent rate-change buttons
102 m_pButtonRatePermDown =
103 new ControlPushButton(ConfigKey(group,"rate_perm_down"));
104 connect(m_pButtonRatePermDown, &ControlObject::valueChanged,
105 this, &RateControl::slotControlRatePermDown,
106 Qt::DirectConnection);
107
108 m_pButtonRatePermDownSmall =
109 new ControlPushButton(ConfigKey(group,"rate_perm_down_small"));
110 connect(m_pButtonRatePermDownSmall, &ControlObject::valueChanged,
111 this, &RateControl::slotControlRatePermDownSmall,
112 Qt::DirectConnection);
113
114 m_pButtonRatePermUp =
115 new ControlPushButton(ConfigKey(group,"rate_perm_up"));
116 connect(m_pButtonRatePermUp, &ControlObject::valueChanged,
117 this, &RateControl::slotControlRatePermUp,
118 Qt::DirectConnection);
119
120 m_pButtonRatePermUpSmall =
121 new ControlPushButton(ConfigKey(group,"rate_perm_up_small"));
122 connect(m_pButtonRatePermUpSmall, &ControlObject::valueChanged,
123 this, &RateControl::slotControlRatePermUpSmall,
124 Qt::DirectConnection);
125
126 // Temporary rate-change buttons
127 m_pButtonRateTempDown =
128 new ControlPushButton(ConfigKey(group,"rate_temp_down"));
129 m_pButtonRateTempDownSmall =
130 new ControlPushButton(ConfigKey(group,"rate_temp_down_small"));
131 m_pButtonRateTempUp =
132 new ControlPushButton(ConfigKey(group,"rate_temp_up"));
133 m_pButtonRateTempUpSmall =
134 new ControlPushButton(ConfigKey(group,"rate_temp_up_small"));
135
136 // We need the sample rate so we can guesstimate something close
137 // what latency is.
138 m_pSampleRate = ControlObject::getControl(ConfigKey("[Master]","samplerate"));
139
140 // Wheel to control playback position/speed
141 m_pWheel = new ControlTTRotary(ConfigKey(group, "wheel"));
142
143 // Scratch controller, this is an accumulator which is useful for
144 // controllers that return individual +1 or -1s, these get added up and
145 // cleared when we read
146 m_pScratch2 = new ControlObject(ConfigKey(group, "scratch2"));
147
148 // Scratch enable toggle
149 m_pScratch2Enable = new ControlPushButton(ConfigKey(group, "scratch2_enable"));
150 m_pScratch2Enable->set(0);
151
152 m_pScratch2Scratching = new ControlPushButton(ConfigKey(group,
153 "scratch2_indicates_scratching"));
154 // Enable by default, because it was always scratching before introducing
155 // this control.
156 m_pScratch2Scratching->set(1.0);
157
158
159 m_pJog = new ControlObject(ConfigKey(group, "jog"));
160 m_pJogFilter = new Rotary();
161 // FIXME: This should be dependent on sample rate/block size or something
162 m_pJogFilter->setFilterLength(25);
163
164 // // Update Internal Settings
165 // // Set Pitchbend Mode
166 // m_eRateRampMode = static_cast<RampMode>(
167 // getConfig()->getValue(ConfigKey("[Controls]","RateRamp"),
168 // static_cast<int>(RampMode::Stepping)));
169
170 // // Set the Sensitivity
171 // m_iRateRampSensitivity =
172 // getConfig()->getValueString(ConfigKey("[Controls]","RateRampSensitivity")).toInt();
173
174 m_pSyncMode = new ControlProxy(group, "sync_mode", this);
175 }
176
~RateControl()177 RateControl::~RateControl() {
178 delete m_pRateRatio;
179 delete m_pRateSlider;
180 delete m_pRateRange;
181 delete m_pRateDir;
182 delete m_pSyncMode;
183
184 delete m_pRateSearch;
185
186 delete m_pReverseButton;
187 delete m_pReverseRollButton;
188 delete m_pForwardButton;
189 delete m_pBackButton;
190
191 delete m_pButtonRateTempDown;
192 delete m_pButtonRateTempDownSmall;
193 delete m_pButtonRateTempUp;
194 delete m_pButtonRateTempUpSmall;
195 delete m_pButtonRatePermDown;
196 delete m_pButtonRatePermDownSmall;
197 delete m_pButtonRatePermUp;
198 delete m_pButtonRatePermUpSmall;
199
200 delete m_pWheel;
201 delete m_pScratch2;
202 delete m_pScratch2Scratching;
203 delete m_pScratch2Enable;
204 delete m_pJog;
205 delete m_pJogFilter;
206 delete m_pScratchController;
207 }
208
setBpmControl(BpmControl * bpmcontrol)209 void RateControl::setBpmControl(BpmControl* bpmcontrol) {
210 m_pBpmControl = bpmcontrol;
211 }
212
213 //static
setRateRampMode(RampMode mode)214 void RateControl::setRateRampMode(RampMode mode) {
215 m_eRateRampMode = mode;
216 }
217
218 //static
getRateRampMode()219 RateControl::RampMode RateControl::getRateRampMode() {
220 return m_eRateRampMode;
221 }
222
223 //static
setRateRampSensitivity(int sense)224 void RateControl::setRateRampSensitivity(int sense) {
225 // Reverse the actual sensitivity value passed.
226 // That way the gui works in an intuitive manner.
227 sense = kRateSensitivityMax - sense + kRateSensitivityMin;
228 if (sense < kRateSensitivityMin) {
229 m_iRateRampSensitivity = kRateSensitivityMin;
230 } else if (sense > kRateSensitivityMax) {
231 m_iRateRampSensitivity = kRateSensitivityMax;
232 } else {
233 m_iRateRampSensitivity = sense;
234 }
235 }
236
237 //static
setTemporaryRateChangeCoarseAmount(double v)238 void RateControl::setTemporaryRateChangeCoarseAmount(double v) {
239 m_dTemporaryRateChangeCoarse.setValue(v);
240 }
241
242 //static
setTemporaryRateChangeFineAmount(double v)243 void RateControl::setTemporaryRateChangeFineAmount(double v) {
244 m_dTemporaryRateChangeFine.setValue(v);
245 }
246
247 //static
setPermanentRateChangeCoarseAmount(double v)248 void RateControl::setPermanentRateChangeCoarseAmount(double v) {
249 m_dPermanentRateChangeCoarse.setValue(v);
250 }
251
252 //static
setPermanentRateChangeFineAmount(double v)253 void RateControl::setPermanentRateChangeFineAmount(double v) {
254 m_dPermanentRateChangeFine.setValue(v);
255 }
256
257 //static
getTemporaryRateChangeCoarseAmount()258 double RateControl::getTemporaryRateChangeCoarseAmount() {
259 return m_dTemporaryRateChangeCoarse.getValue();
260 }
261
262 //static
getTemporaryRateChangeFineAmount()263 double RateControl::getTemporaryRateChangeFineAmount() {
264 return m_dTemporaryRateChangeFine.getValue();
265 }
266
267 //static
getPermanentRateChangeCoarseAmount()268 double RateControl::getPermanentRateChangeCoarseAmount() {
269 return m_dPermanentRateChangeCoarse.getValue();
270 }
271
272 //static
getPermanentRateChangeFineAmount()273 double RateControl::getPermanentRateChangeFineAmount() {
274 return m_dPermanentRateChangeFine.getValue();
275 }
276
slotRateRangeChanged(double)277 void RateControl::slotRateRangeChanged(double) {
278 // update RateSlider with the new Range value butdo not change m_pRateRatio
279 slotRateRatioChanged(m_pRateRatio->get());
280 }
281
slotRateSliderChanged(double v)282 void RateControl::slotRateSliderChanged(double v) {
283 double rateRatio = 1.0 + m_pRateDir->get() * m_pRateRange->get() * v;
284 m_pRateRatio->set(rateRatio);
285 }
286
slotRateRatioChanged(double v)287 void RateControl::slotRateRatioChanged(double v) {
288 double rateRange = m_pRateRange->get();
289 if (rateRange > 0.0) {
290 double newRate = m_pRateDir->get() * (v - 1) / rateRange;
291 m_pRateSlider->set(newRate);
292 } else {
293 m_pRateSlider->set(0);
294 }
295 }
296
slotReverseRollActivate(double v)297 void RateControl::slotReverseRollActivate(double v) {
298 if (v > 0.0) {
299 m_pSlipEnabled->set(1);
300 m_pReverseButton->set(1);
301 } else {
302 m_pReverseButton->set(0);
303 m_pSlipEnabled->set(0);
304 }
305 }
306
slotControlFastForward(double v)307 void RateControl::slotControlFastForward(double v) {
308 //qDebug() << "slotControlFastForward(" << v << ")";
309 if (v > 0.0) {
310 m_pRateSearch->set(4.0);
311 } else {
312 m_pRateSearch->set(0.0);
313 }
314 }
315
slotControlFastBack(double v)316 void RateControl::slotControlFastBack(double v) {
317 //qDebug() << "slotControlFastBack(" << v << ")";
318 if (v > 0.0) {
319 m_pRateSearch->set(-4.0);
320 } else {
321 m_pRateSearch->set(0.0);
322 }
323 }
324
slotControlRatePermDown(double v)325 void RateControl::slotControlRatePermDown(double v) {
326 // Adjusts temp rate down if button pressed
327 if (v > 0.0) {
328 m_pRateSlider->set(m_pRateSlider->get() -
329 m_pRateDir->get() * m_dPermanentRateChangeCoarse.getValue() / (100 * m_pRateRange->get()));
330 slotRateSliderChanged(m_pRateSlider->get());
331 }
332 }
333
slotControlRatePermDownSmall(double v)334 void RateControl::slotControlRatePermDownSmall(double v) {
335 // Adjusts temp rate down if button pressed
336 if (v > 0.0) {
337 m_pRateSlider->set(m_pRateSlider->get() -
338 m_pRateDir->get() * m_dPermanentRateChangeFine.getValue() / (100. * m_pRateRange->get()));
339 slotRateSliderChanged(m_pRateSlider->get());
340 }
341 }
342
slotControlRatePermUp(double v)343 void RateControl::slotControlRatePermUp(double v) {
344 // Adjusts temp rate up if button pressed
345 if (v > 0.0) {
346 m_pRateSlider->set(m_pRateSlider->get() +
347 m_pRateDir->get() * m_dPermanentRateChangeCoarse.getValue() / (100. * m_pRateRange->get()));
348 slotRateSliderChanged(m_pRateSlider->get());
349 }
350 }
351
slotControlRatePermUpSmall(double v)352 void RateControl::slotControlRatePermUpSmall(double v) {
353 // Adjusts temp rate up if button pressed
354 if (v > 0.0) {
355 m_pRateSlider->set(m_pRateSlider->get() +
356 m_pRateDir->get() * m_dPermanentRateChangeFine.getValue() / (100. * m_pRateRange->get()));
357 slotRateSliderChanged(m_pRateSlider->get());
358 }
359 }
360
getWheelFactor() const361 double RateControl::getWheelFactor() const {
362 return m_pWheel->get();
363 }
364
getJogFactor() const365 double RateControl::getJogFactor() const {
366 // FIXME: Sensitivity should be configurable separately?
367 const double jogSensitivity = 0.1; // Nudges during playback
368 double jogValue = m_pJog->get();
369
370 // Since m_pJog is an accumulator, reset it since we've used its value.
371 if (jogValue != 0.) {
372 m_pJog->set(0.);
373 }
374
375 double jogValueFiltered = m_pJogFilter->filter(jogValue);
376 double jogFactor = jogValueFiltered * jogSensitivity;
377
378 if (util_isnan(jogValue) || util_isnan(jogFactor)) {
379 jogFactor = 0.0;
380 }
381
382 return jogFactor;
383 }
384
getSyncMode() const385 SyncMode RateControl::getSyncMode() const {
386 return syncModeFromDouble(m_pSyncMode->get());
387 }
388
calculateSpeed(double baserate,double speed,bool paused,int iSamplesPerBuffer,bool * pReportScratching,bool * pReportReverse)389 double RateControl::calculateSpeed(double baserate, double speed, bool paused,
390 int iSamplesPerBuffer,
391 bool* pReportScratching,
392 bool* pReportReverse) {
393 *pReportScratching = false;
394 *pReportReverse = false;
395
396 processTempRate(iSamplesPerBuffer);
397
398 double rate;
399 const double searching = m_pRateSearch->get();
400 if (searching != 0) {
401 // If searching is in progress, it overrides everything else
402 rate = searching;
403 } else {
404 double wheelFactor = getWheelFactor();
405 double jogFactor = getJogFactor();
406 bool bVinylControlEnabled = m_pVCEnabled && m_pVCEnabled->toBool();
407 bool useScratch2Value = m_pScratch2Enable->toBool();
408
409 // By default scratch2_enable is enough to determine if the user is
410 // scratching or not. Moving platter controllers have to disable
411 // "scratch2_indicates_scratching" if they are not scratching,
412 // to allow things like key-lock.
413 if (useScratch2Value && m_pScratch2Scratching->toBool()) {
414 *pReportScratching = true;
415 }
416
417 if (bVinylControlEnabled) {
418 if (m_pVCScratching->toBool()) {
419 *pReportScratching = true;
420 }
421 rate = speed;
422 } else {
423 double scratchFactor = m_pScratch2->get();
424 // Don't trust values from m_pScratch2
425 if (util_isnan(scratchFactor)) {
426 scratchFactor = 0.0;
427 }
428 if (paused) {
429 // Stopped. Wheel, jog and scratch controller all scrub through audio.
430 if (useScratch2Value) {
431 rate = scratchFactor + jogFactor + wheelFactor * kWheelMultiplier;
432 } else {
433 rate = jogFactor * kPausedJogMultiplier + wheelFactor;
434 }
435 } else {
436 // The buffer is playing, so calculate the buffer rate.
437
438 // There are four rate effects we apply: wheel, scratch, jog and temp.
439 // Wheel: a linear additive effect (no spring-back)
440 // Scratch: a rate multiplier
441 // Jog: a linear additive effect whose value is filtered (springs back)
442 // Temp: pitch bend
443
444 // New scratch behavior - overrides playback speed (and old behavior)
445 if (useScratch2Value) {
446 rate = scratchFactor;
447 } else {
448 // add temp rate, but don't go backwards
449 rate = math_max(speed + getTempRate(), 0.0);
450 rate += wheelFactor;
451 }
452 rate += jogFactor;
453 }
454 }
455
456 double currentSample = getSampleOfTrack().current;
457 m_pScratchController->process(currentSample, rate, iSamplesPerBuffer, baserate);
458
459 // If waveform scratch is enabled, override all other controls
460 if (m_pScratchController->isEnabled()) {
461 rate = m_pScratchController->getRate();
462 *pReportScratching = true;
463 } else {
464 // If master sync is on, respond to it -- but vinyl and scratch mode always override.
465 if (getSyncMode() == SYNC_FOLLOWER && !paused &&
466 !bVinylControlEnabled && !useScratch2Value) {
467 if (m_pBpmControl == nullptr) {
468 qDebug() << "ERROR: calculateRate m_pBpmControl is null during master sync";
469 return 1.0;
470 }
471
472 double userTweak = 0.0;
473 if (!*pReportScratching) {
474 // Only report user tweak if the user is not scratching.
475 userTweak = getTempRate() + wheelFactor + jogFactor;
476 }
477 rate = m_pBpmControl->calcSyncedRate(userTweak);
478 }
479 // If we are reversing (and not scratching,) flip the rate. This is ok even when syncing.
480 // Reverse with vinyl is only ok if absolute mode isn't on.
481 int vcmode = m_pVCMode ? static_cast<int>(m_pVCMode->get()) : MIXXX_VCMODE_ABSOLUTE;
482 // TODO(owen): Instead of just ignoring reverse mode, should we
483 // disable absolute mode instead?
484 if (m_pReverseButton->toBool() && !m_pScratch2Enable->toBool() &&
485 (!bVinylControlEnabled ||
486 vcmode != MIXXX_VCMODE_ABSOLUTE)) {
487 rate = -rate;
488 *pReportReverse = true;
489 }
490 }
491 }
492 return rate;
493 }
494
processTempRate(const int bufferSamples)495 void RateControl::processTempRate(const int bufferSamples) {
496 // Code to handle temporary rate change buttons.
497 // We support two behaviors, the standard ramped pitch bending
498 // and pitch shift stepping, which is the old behavior.
499
500 RampDirection rampDirection = RampDirection::None;
501 if (m_pButtonRateTempUp->toBool()) {
502 rampDirection = RampDirection::Up;
503 } else if (m_pButtonRateTempDown->toBool()) {
504 rampDirection = RampDirection::Down;
505 } else if (m_pButtonRateTempUpSmall->toBool()) {
506 rampDirection = RampDirection::UpSmall;
507 } else if (m_pButtonRateTempDownSmall->toBool()) {
508 rampDirection = RampDirection::DownSmall;
509 }
510
511 if (rampDirection != RampDirection::None) {
512 if (m_eRateRampMode == RampMode::Stepping) {
513 if (!m_bTempStarted) {
514 m_bTempStarted = true;
515 // old temporary pitch shift behavior
516 double change = m_dTemporaryRateChangeCoarse.getValue() / 100.0;
517 double csmall = m_dTemporaryRateChangeFine.getValue() / 100.0;
518
519 switch (rampDirection) {
520 case RampDirection::Up:
521 setRateTemp(change);
522 break;
523 case RampDirection::Down:
524 setRateTemp(-change);
525 break;
526 case RampDirection::UpSmall:
527 setRateTemp(csmall);
528 break;
529 case RampDirection::DownSmall:
530 setRateTemp(-csmall);
531 break;
532 case RampDirection::None:
533 default:
534 DEBUG_ASSERT(false);
535 }
536 }
537 } else if (m_eRateRampMode == RampMode::Linear) {
538 if (!m_bTempStarted) {
539 m_bTempStarted = true;
540 double latrate = bufferSamples / m_pSampleRate->get();
541 m_dRateTempRampChange = latrate / (m_iRateRampSensitivity / 100.0);
542 }
543
544 switch (rampDirection) {
545 case RampDirection::Up:
546 case RampDirection::UpSmall:
547 addRateTemp(m_dRateTempRampChange * m_pRateRange->get());
548 break;
549 case RampDirection::Down:
550 case RampDirection::DownSmall:
551 subRateTemp(m_dRateTempRampChange * m_pRateRange->get());
552 break;
553 case RampDirection::None:
554 default:
555 DEBUG_ASSERT(false);
556 }
557 }
558 } else if (m_bTempStarted) {
559 m_bTempStarted = false;
560 resetRateTemp();
561 }
562 }
563
getTempRate()564 double RateControl::getTempRate() {
565 // qDebug() << m_tempRateRatio;
566 return m_tempRateRatio;
567 }
568
setRateTemp(double v)569 void RateControl::setRateTemp(double v) {
570 m_tempRateRatio = v;
571 if (m_tempRateRatio < -1.0) {
572 m_tempRateRatio = -1.0;
573 } else if (m_tempRateRatio > 1.0) {
574 m_tempRateRatio = 1.0;
575 } else if (util_isnan(m_tempRateRatio)) {
576 m_tempRateRatio = 0;
577 }
578 }
579
addRateTemp(double v)580 void RateControl::addRateTemp(double v)
581 {
582 setRateTemp(m_tempRateRatio + v);
583 }
584
subRateTemp(double v)585 void RateControl::subRateTemp(double v)
586 {
587 setRateTemp(m_tempRateRatio - v);
588 }
589
resetRateTemp(void)590 void RateControl::resetRateTemp(void)
591 {
592 setRateTemp(0.0);
593 }
594
notifySeek(double playPos)595 void RateControl::notifySeek(double playPos) {
596 m_pScratchController->notifySeek(playPos);
597 EngineControl::notifySeek(playPos);
598 }
599
isReverseButtonPressed()600 bool RateControl::isReverseButtonPressed() {
601 if (m_pReverseButton) {
602 return m_pReverseButton->toBool();
603 }
604 return false;
605 }
606