%% Measure Audio Latency
% This example shows how to measure the latency of an audio device. The
% example uses audioLatencyMeasurementExampleApp which in turn uses
% audioPlayerRecorder along with a test signal and cross correlation to
% determine latency. To avoid disk access interference, the test signal is
% loaded into a dsp.AsyncBuffer object first, and frames are streamed from
% that object through the audio device.

% Copyright 2014-2019 The MathWorks, Inc.

%% Introduction
% In general terms, *latency* is defined as the time from when the audio
% signal enters a system until it exits. In a digital audio processing
% chain, there are multiple parameters that cause latency:
% 
% # Hardware (including A/D and D/A conversion) 
% # Audio drivers that communicate with the system's sound card
% # Sampling rate
% # Samples per frame (buffer size)
% # Algorithmic latency (e.g. delay introduced by a filter or audio effect)
%
% This example shows how to measure round trip latency. That is, the
% latency incurred when playing audio through a device, looping back the
% audio with a physical loopback cable, and recording the loopback audio
% with the same audio device. In order to compute latency for your own
% audio device, you need to connect the audio out and audio in ports using
% a loopback cable.
%
% <<../MeasuringLatency1.png>>
%
% Roundtrip latency does not break down the measurement between output
% latency and input latency. It measures only the combined effect of the
% two. Also, most practical applications will not use a loopback setup.
% Typically the processing chain consists of recording audio, processing
% it, and playing the processed audio. However, the latency involved should
% be the same either way provided the other factors (frame size, sampling
% rate, algorithm latency) don't change.
%% Hardware Latency
% Smaller frame sizes and higher sampling rates reduce the roundtrip
% latency. However, the tradeoff is a higher chance of dropouts occurring
% (overruns/underruns).
%
% In addition to potentially increasing latency, the amount of processing
% involved in the audio algorithm can also cause dropouts.
%
%% Measuring Latency with audioLatencyMeasurementExampleApp.m
% The function audioLatencyMeasurementExampleApp computes roundtrip latency
% in milliseconds for a given setup. Overruns and underruns are also
% presented. If the overruns/underruns are not zero, the results are likely
% invalid. For example:

audioLatencyMeasurementExampleApp('SamplesPerFrame',64,'SampleRate',48e3)

% The measurements in this example were done on macOS. For most
% measurements, a Steinberg UR22 external USB device was used. For the
% measurements with custom I/O channels, an RME Fireface UFX+ device was
% used. This RME device has lower latency than the Steinberg device for a
% given sample rate/frame size combination. Measurements on Windows using
% ASIO drivers should result in similar values.

%% Some Tips When Measuring Latency
% Real-time processing on a general purpose operating system is only
% possible if you minimize other tasks being performed by the computer. It
% is recommended to:
% 
% # Close all other programs  
% # Ensure no underruns/overruns occur 
% # Use a large enough buffer size (SamplesPerFrame) to ensure consistent
% dropout-free behavior
% # Ensure your hardware settings (buffer size, sampling rate) match the
% inputs to measureLatency
%
% On Windows, you can use the asiosettings function to launch the dialog to
% control the hardware settings. On macOS, you should launch the Audio MIDI
% Setup. 
%
% When using ASIO (or CoreAudio with Mac OS), the latency measurements are
% consistent as long as no dropouts occur. For small buffer sizes, it is
% possible to get a clean measurement in one instance and dropouts the
% next. The Ntrials option can be used to ensure consistent dropout
% behavior when measuring latency. For example, to perform 3 measurements,
% use:

audioLatencyMeasurementExampleApp('SamplesPerFrame',96,...
    'SampleRate',48e3,'Ntrials',3)

%% Measurements For Different Buffer Sizes
% On macOS, it is also possible to try different frame sizes without
% changing the hardware settings. To make this convenient, you can specify
% a vector of SamplesPerFrame:  
BufferSizes = [64;96;128];
t = audioLatencyMeasurementExampleApp('SamplesPerFrame',BufferSizes)

% Notice that for every sample increment in the buffer size, the additional
% latency is 3*SamplesPerFrameIncrement/SampleRate (macOS only). 

%%
% Specifically, in the previous example, the increment is

3*[128-96, 96-64]/48e3

% In addition, notice that the actual buffering latency is also determined
% by 3*SamplesPerFrame/SampleRate. Subtracting this value from the measured
% latency gives a measure of the latency introduced by the device (combined
% effect of A/D conversion, D/A conversion, and drivers). The numbers above
% indicate about 4.3125 ms latency due to device-specific factors.

t.Latency_ms - 3*BufferSizes/48
%% Specifying Custom Input/Output Channels
% The measurements performed so far assume that channel #1 is used for both
% input and output. If your device has a loopback cable connected to other
% channels, you can specify them using the IOChannels option to
% measureLatency. This is specified as a 2-element vector, corresponding to
% the input and output channels to be used (the measurement is always on a
% mono signal). For example for an RME Fireface UFX+:

audioLatencyMeasurementExampleApp('SamplesPerFrame',[32 64 96],...
    'SampleRate',96e3,'Device','Fireface UFX+ (23767940)',...
    'IOChannels',[1 3])

%% Algorithmic Latency
% The measurements so far have not included algorithm latency. Therefore,
% they represent the minimal roundtrip latency that can be achieved for a
% given device, buffer size, and sampling rate. You can add a linear phase FIR
% filter the processing chain to verify that the latency
% measurements are as expected. Moreover, it provides
% a way of verifying robustness of the real-time audio processing under a
% given workload. For example,

L  = 961;
Fs = 48e3;
audioLatencyMeasurementExampleApp('SamplesPerFrame',128,...
    'SampleRate',Fs,'FilterLength',L,'Ntrials',3)

% The latency introduced by the filter is given by the filter's
% group-delay.

GroupDelay = (L-1)/2/Fs

% The group delay accounts for the 10 ms of additional latency when using a
% 961-tap linear-phase FIR filter vs. the minimal achievable latency.
%% Plotting the Original and Recorded Signal
%The latency measurements are determined by cross-correlating a source
%audio signal with a delayed version of the signal that results after
%loopback through the audio device. You can use the Plot option in
%measureLatency to plot the original and delayed signal along with the
%cross correlation:
audioLatencyMeasurementExampleApp('SamplesPerFrame',128,'Plot',true)

% If the optional FIR filtering is used, the waveforms are not affected
% because the filter used has a broader bandwidth than the test audio
% signal.
