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