1 #include "waveform/visualplayposition.h"
2
3 #include <QtDebug>
4
5 #include "control/controlobject.h"
6 #include "control/controlproxy.h"
7 #include "moc_visualplayposition.cpp"
8 #include "util/math.h"
9 #include "waveform/vsyncthread.h"
10
11 namespace {
12 // The offset is limited to two callback intervals.
13 // This should be sufficiant to compensate jitter,
14 // but does not continue in case of underflows.
15 constexpr int kMaxOffsetBufferCnt = 2;
16 constexpr int kMicrosPerMillis = 1000; // 1 ms contains 1000 µs
17 } // anonymous namespace
18
19
20 //static
21 QMap<QString, QWeakPointer<VisualPlayPosition> > VisualPlayPosition::m_listVisualPlayPosition;
22 PerformanceTimer VisualPlayPosition::m_timeInfoTime;
23 double VisualPlayPosition::m_dCallbackEntryToDacSecs = 0;
24
VisualPlayPosition(const QString & key)25 VisualPlayPosition::VisualPlayPosition(const QString& key)
26 : m_valid(false),
27 m_key(key) {
28 m_audioBufferSize = new ControlProxy(
29 "[Master]", "audio_buffer_size", this);
30 m_audioBufferSize->connectValueChanged(this, &VisualPlayPosition::slotAudioBufferSizeChanged);
31 m_audioBufferMicros = static_cast<int>(m_audioBufferSize->get() * kMicrosPerMillis);
32 }
33
~VisualPlayPosition()34 VisualPlayPosition::~VisualPlayPosition() {
35 m_listVisualPlayPosition.remove(m_key);
36 }
37
set(double playPos,double rate,double positionStep,double slipPosition,double tempoTrackSeconds)38 void VisualPlayPosition::set(double playPos, double rate, double positionStep,
39 double slipPosition, double tempoTrackSeconds) {
40 VisualPlayPositionData data;
41 data.m_referenceTime = m_timeInfoTime;
42 data.m_callbackEntrytoDac = static_cast<int>(m_dCallbackEntryToDacSecs * 1000000); // s to µs
43 data.m_enginePlayPos = playPos;
44 data.m_rate = rate;
45 data.m_positionStep = positionStep;
46 data.m_slipPosition = slipPosition;
47 data.m_tempoTrackSeconds = tempoTrackSeconds;
48
49 // Atomic write
50 m_data.setValue(data);
51 m_valid = true;
52 }
53
getAtNextVSync(VSyncThread * vSyncThread)54 double VisualPlayPosition::getAtNextVSync(VSyncThread* vSyncThread) {
55 //static double testPos = 0;
56 //testPos += 0.000017759; //0.000016608; // 1.46257e-05;
57 //return testPos;
58
59 if (m_valid) {
60 VisualPlayPositionData data = m_data.getValue();
61 int refToVSync = vSyncThread->fromTimerToNextSyncMicros(data.m_referenceTime);
62 int offset = refToVSync - data.m_callbackEntrytoDac;
63 offset = math_min(offset, m_audioBufferMicros * kMaxOffsetBufferCnt);
64 double playPos = data.m_enginePlayPos; // load playPos for the first sample in Buffer
65 // add the offset for the position of the sample that will be transferred to the DAC
66 // When the next display frame is displayed
67 playPos += data.m_positionStep * offset * data.m_rate / m_audioBufferMicros;
68 //qDebug() << "playPos" << playPos << offset;
69 return playPos;
70 }
71 return -1;
72 }
73
getPlaySlipAtNextVSync(VSyncThread * vSyncThread,double * pPlayPosition,double * pSlipPosition)74 void VisualPlayPosition::getPlaySlipAtNextVSync(VSyncThread* vSyncThread, double* pPlayPosition, double* pSlipPosition) {
75 //static double testPos = 0;
76 //testPos += 0.000017759; //0.000016608; // 1.46257e-05;
77 //return testPos;
78
79 if (m_valid) {
80 VisualPlayPositionData data = m_data.getValue();
81 int refToVSync = vSyncThread->fromTimerToNextSyncMicros(data.m_referenceTime);
82 int offset = refToVSync - data.m_callbackEntrytoDac;
83 offset = math_min(offset, m_audioBufferMicros * kMaxOffsetBufferCnt);
84 double playPos = data.m_enginePlayPos; // load playPos for the first sample in Buffer
85 playPos += data.m_positionStep * offset * data.m_rate / m_audioBufferMicros;
86 *pPlayPosition = playPos;
87 *pSlipPosition = data.m_slipPosition;
88 }
89 }
90
getEnginePlayPos()91 double VisualPlayPosition::getEnginePlayPos() {
92 if (m_valid) {
93 VisualPlayPositionData data = m_data.getValue();
94 return data.m_enginePlayPos;
95 } else {
96 return -1;
97 }
98 }
99
getTrackTime(double * pPlayPosition,double * pTempoTrackSeconds)100 void VisualPlayPosition::getTrackTime(double* pPlayPosition, double* pTempoTrackSeconds) {
101 if (m_valid) {
102 VisualPlayPositionData data = m_data.getValue();
103 *pPlayPosition = data.m_enginePlayPos;
104 *pTempoTrackSeconds = data.m_tempoTrackSeconds;
105 } else {
106 *pPlayPosition = 0;
107 *pTempoTrackSeconds = 0;
108 }
109 }
110
slotAudioBufferSizeChanged(double sizeMillis)111 void VisualPlayPosition::slotAudioBufferSizeChanged(double sizeMillis) {
112 m_audioBufferMicros = static_cast<int>(sizeMillis * kMicrosPerMillis);
113 }
114
115 //static
getVisualPlayPosition(const QString & group)116 QSharedPointer<VisualPlayPosition> VisualPlayPosition::getVisualPlayPosition(const QString& group) {
117 QSharedPointer<VisualPlayPosition> vpp = m_listVisualPlayPosition.value(group);
118 if (vpp.isNull()) {
119 vpp = QSharedPointer<VisualPlayPosition>(new VisualPlayPosition(group));
120 m_listVisualPlayPosition.insert(group, vpp);
121 }
122 return vpp;
123 }
124
125 //static
setCallbackEntryToDacSecs(double secs,const PerformanceTimer & time)126 void VisualPlayPosition::setCallbackEntryToDacSecs(double secs, const PerformanceTimer& time) {
127 // the time is valid only just NOW, so measure the time from NOW for
128 // later correction
129 m_timeInfoTime = time;
130 m_dCallbackEntryToDacSecs = secs;
131 }
132