@@ -38,10 +38,8 @@ AudioEngine::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32
38
38
const std::chrono::microseconds bufferBeginAtOutput = hostTime + latencyMuSec;
39
39
const auto microsPerSample = 1e6 / mSampleRate ;
40
40
41
-
42
41
int64_t timeoutNanos = 100 * kNanosPerMillisecond ;
43
42
44
-
45
43
if (audioStream->getFormat () == oboe::AudioFormat::Float) {
46
44
47
45
// Logic: successive renders are added
@@ -82,16 +80,25 @@ AudioEngine::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32
82
80
memset (static_cast <uint16_t *>(audioData), 0 ,
83
81
sizeof (int16_t ) * channelCount * numFrames);
84
82
83
+ if (mPlayStatus == playing || mPerformLatencyDetection ) {
85
84
86
- if (mPlayStatus == playing) {
87
- renderBarClick (static_cast <int16_t *>(audioData) + 1 , channelCount,
85
+ renderBarClick (static_cast <int16_t *>(audioData) , channelCount,
88
86
numFrames, sessionState, bufferBeginAtOutput, microsPerSample);
89
87
90
88
for (int i = 0 ; i < channelCount; ++i) {
91
-
92
89
mOscillators [i].render (static_cast <int16_t *>(audioData) + i, channelCount,
93
90
numFrames);
94
91
}
92
+
93
+ if (mPerformLatencyDetection ) {
94
+ // The callback does a non-blocking read from the input stream placing the data into the internal input buffer.
95
+ oboe::ErrorOrValue<int32_t > result = mRecStream ->read (mInputBuffer_int16 , numFrames, timeoutNanos);
96
+
97
+ if (!result) {
98
+ LOGE (" Error reading input stream %s" , convertToText (result.error ()));
99
+ }
100
+ processInput (static_cast <int16_t *>(audioData) , channelCount, numFrames);
101
+ }
95
102
}
96
103
}
97
104
@@ -147,23 +154,27 @@ void AudioEngine::renderBarClick(int16_t *buffer,
147
154
ableton::Link::SessionState sessionState,
148
155
std::chrono::microseconds bufferBeginAtOutput,
149
156
double microsPerSample) {
150
- for (int i = 0 , sampleIndex = 0 ; i < numFrames; i++) {
157
+ for (int i = 0 ; i < numFrames; i++) {
151
158
152
159
const auto sampleHostTime = bufferBeginAtOutput + std::chrono::microseconds (llround (i * microsPerSample));
153
- int16_t sample = 0 ;
160
+
154
161
const double barPhase = sessionState.phaseAtTime (sampleHostTime, mQuantum );
155
- if ((barPhase - mLastBarPhase ) < -(mQuantum /40 )){
156
- // uncomment if you want to render a clik on each bar.
157
- sample = INT16_MAX * 1.0 ;
162
+
163
+ if ((barPhase - mLastBarPhase ) < -(mQuantum /2 )) {
158
164
timeAtLastBar = sampleHostTime;
165
+ for (int j = 0 ; j< channelStride; j++){
166
+ buffer[i*channelStride + j] = INT16_MAX * 1.0 ;
167
+ }
168
+ mLatencySampleCount = 0 ;
169
+ } else {
170
+ mLatencySampleCount ++;
159
171
}
160
- buffer[sampleIndex] += sample;
161
- sampleIndex += channelStride;
162
172
mLastBarPhase = barPhase;
163
173
}
164
174
}
165
175
166
176
177
+
167
178
// the buffer param refers to the output audio buffer. the input buffer is a private property.
168
179
void AudioEngine::processInput (float *buffer,
169
180
int32_t channelStride,
@@ -176,14 +187,28 @@ void AudioEngine::processInput(float *buffer,
176
187
177
188
// crude onset detection
178
189
if ( (fabsf (mInputBuffer [i]) > 0.5 ) && !foundOnsetInBuffer){
179
- // LOGD("ONSET %i ", mLatencySampleCount);
180
- // LOGD("round trip latency %f", (double) mLatencySampleCount / mPlayStream->getSampleRate());
181
-
182
- // set output latency to half round trip latency?
183
- mCurrentOutputLatencyMillis = ((double ) mLatencySampleCount / mPlayStream ->getSampleRate () * 1000 ) / 2.0 ;
190
+ mDetectedOutputLatencyMillis = ((double ) mLatencySampleCount / mPlayStream ->getSampleRate () * 1000 );
184
191
foundOnsetInBuffer = true ;
185
192
}
193
+ sampleIndex += channelStride;
194
+ }
195
+ }
186
196
197
+ // the buffer param refers to the output audio buffer. the input buffer is a private property.
198
+ void AudioEngine::processInput (int16_t *buffer,
199
+ int32_t channelStride,
200
+ int32_t numFrames) {
201
+ bool foundOnsetInBuffer = false ;
202
+ for (int i = 0 , sampleIndex = 0 ; i < numFrames; i++) {
203
+
204
+ // Uncomment to copy input into output buffer
205
+ // buffer[sampleIndex] += mInputBuffer[i];
206
+
207
+ // crude onset detection
208
+ if ( (abs (mInputBuffer_int16 [i]) > (INT16_MAX * 0.5 )) && !foundOnsetInBuffer){
209
+ mDetectedOutputLatencyMillis = ((double ) mLatencySampleCount / mPlayStream ->getSampleRate () * 1000 );
210
+ foundOnsetInBuffer = true ;
211
+ }
187
212
sampleIndex += channelStride;
188
213
}
189
214
}
@@ -273,6 +298,7 @@ void AudioEngine::closeInputStream() {
273
298
}
274
299
275
300
delete[] mInputBuffer ;
301
+ delete[] mInputBuffer_int16 ;
276
302
}
277
303
}
278
304
@@ -305,6 +331,10 @@ void AudioEngine::createRecStream() {
305
331
if (mInputBuffer == nullptr ) {
306
332
mInputBuffer = new float [mChannelCount * mRecStream ->getBufferSizeInFrames ()];
307
333
}
334
+ if (mInputBuffer_int16 == nullptr ) {
335
+ mInputBuffer_int16 = new int16_t [mChannelCount * mRecStream ->getBufferSizeInFrames ()];
336
+ }
337
+
308
338
result = mRecStream ->requestStart ();
309
339
if (result != oboe::Result::OK) {
310
340
LOGE (" Error starting stream. %s" , oboe::convertToText (result));
@@ -457,5 +487,5 @@ void AudioEngine::detectLatency(bool flag){
457
487
}
458
488
459
489
double AudioEngine::getLatencyMs (){
460
- return mCurrentOutputLatencyMillis ;
490
+ return mDetectedOutputLatencyMillis ;
461
491
}
0 commit comments