Speaker playback at an absolute level

2 views (last 30 days)
Brian
Brian on 4 Jan 2019
I'm trying to play signals from a speaker at varying absolute levels, however my current solution does not offer reproducible results.
First I use a B&K calibrator at 94 dB to obtain a calibration transfer factor from a microphone (assume X below is the recorded signal from an audiorecorder object):
RMS=sqrt(mean(X.^2)); % find the RMS value of the recorded signal in digital units (DU)
calGen=(10^(94/20))*2e-5; % calculate the level presented by the calibrator in Pa
transFactor=RMS/calGen; % conversion factor in DU/Pa
Then I generate and play a 1s long, 1kHz tone from a given speaker and record with the now-calibrated microphone.
fs=44100; nBits=16;
T=1; % 1 second long tone
target=80; % This is the tone amplitude in dB re 20 uPa that I want to play
t=0:1/fs:(T*fs-1)/fs; % Generate time vector
tone=sin(2.*pi.*1000.*t); % Generate tone
calGen=(10^(target/20))*2e-5; % Get target level in Pa
sensitivity=sensitivity; % This is the previous calibrated 'sensitivity' in dB
calLevel=CalibrationLevel; % This is the level at which the speaker was previously calibrated
A=getA(target,sensitivity,calLevel,tone); % Get A
tone=A.*tone;
% Load tone and populate audio objects
tonePlayObj=audioplayer(tone,fs,nBits,outputID);
toneRecObj=audiorecorder(fs,nBits,1,inputID);
play(tonePlayObj)
recordblocking(toneRecObj,length(tone)/fs)
toneRecData=getaudiodata(toneRecObj);
load([cd '\Calibration Files\1kHz_44100Hz_bpass.mat'],'Hd') % Load 1kHz bandpass filter
calToneRec=toneRecData./transFactor; % Convert measured DU -> Pa
filtToneRec=filter(Hd,calToneRec); % Apply 1kHz bandpass filter
RMS=sqrt(mean(filtToneRec(length(filtToneRec)/2+1:end).^2)); % find the RMS value of the calibrated signal
% only use the second half to avoid delay
RMSdb=20*log10(RMS/2e-5); % find the RMS value in dB
TF=A*calGen/RMS; % Adjust 'A' according to the ratio between the target level and the recorded RMS level
sensitivity=20*log10(TF); % Calculate new sensitivity
The TF or sensitivity in this case is essentially the A value for the given calLevel. My getA function is copied below.
function A=getA(target,sensitivity,calLevel,X)
volDiff=target-calLevel; % calculate difference between target level and level at calibration
volume=sensitivity+volDiff; % account for the difference above in the sensitivity
oldA=10^(sensitivity/20); % calculate the old A value (max amplitude for a rectangular window)
oldrms=(sqrt(2)/2)*10^(sensitivity/20); % calculate the old rms value (useful for non-rectangular windows)
newrms=sqrt(mean((oldA.*X).^2)); % calculate the rms value of the new signal with the old A value
% (again, this provides the ratio used below to account for signals with a non-flat envelope)
A=(10^(volume/20))*oldrms/newrms; % adjust A for the changed volume and different rms ratio
Again, this feels round-about, but seems to MOSTLY work, however it can fluctuate a dB or more when repeated. This is a part of a very large set of GUIs that I've been developing, but this has been stumping me. There's plenty of other info to go with this, but I've tried to boil it down to the basic parts. Let me know if there's any other info that might help in solving this issue. Thanks!

Answers (0)

Categories

Find more on Audio I/O and Waveform Generation in Help Center and File Exchange

Products


Release

R2018b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!