rmeddis@38: classdef cEssexAid rmeddis@38: %ESSEXAID_WRAPCLASS Wrapper for the EssexAid - Nick Clark July 2011 rmeddis@38: % This class wraps up the EssexAid algorithm function that processes rmeddis@38: % each block of samples. This wrapper closely emulates the GUI used rmeddis@38: % in the lab and runs stimuli through the exact same algorithm used rmeddis@38: % in the lab. It even includes a helper function to generate C code rmeddis@38: % from the algorithm for use in a real-time framework. rmeddis@38: rmeddis@38: rmeddis@38: %% ********************************************************* rmeddis@38: % properties _ _ rmeddis@38: % | | (_) rmeddis@38: % _ __ _ __ ___ _ __ ___ _ __| |_ _ ___ ___ rmeddis@38: % | '_ \| '__/ _ \| '_ \ / _ \ '__| __| |/ _ \/ __| rmeddis@38: % | |_) | | | (_) | |_) | __/ | | |_| | __/\__ \ rmeddis@38: % | .__/|_| \___/| .__/ \___|_| \__|_|\___||___/ rmeddis@38: % | | | | rmeddis@38: % |_| |_| rmeddis@38: %************************************************************ rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Public properties - can be set by user rmeddis@38: %************************************************************ rmeddis@38: properties(Access = public) rmeddis@38: sr = 48e3; rmeddis@38: numSamples = 1024; %MAX=6912, LAB_USE=48 rmeddis@38: stimulusUSER rmeddis@38: rmeddis@38: %------------------------------------------------------------------ rmeddis@38: % Params for audiometric freqs 250, 500, 1000, 2000, 4000, 8000 Hz rmeddis@38: %------------------------------------------------------------------ rmeddis@38: audiometry_dB= [ 0; 0; 0; 0; 0; 0]; %Pure tone threshold in dB SPL rmeddis@38: mainGain_dB = [ 0; 0; 0; 0; 0; 0]; %Gain applied at audiometric frequencies rmeddis@38: TC_dBHL = [40; 40; 40; 40; 40; 40]; %Compression thresholds (in dB HL from 2nd filt) rmeddis@38: TM_dBHL = [10; 10; 10; 10; 10; 10]; %MOC thresholds (in dB OUTPUT from 2nd filt) rmeddis@38: DRNLc = [ 0.2; 0.2; 0.2; 0.2; 0.2; 0.2]; %Compression exponent at audiometric frequencies rmeddis@38: rmeddis@38: %------------------------------------------------------------------ rmeddis@38: % Dynamic compression properties rmeddis@38: %------------------------------------------------------------------ rmeddis@38: ARtau = 60e-3; %decay time constant rmeddis@38: ARthreshold_dB = 85; %dB SPL (input signal level) =>200 to disable rmeddis@38: MOCtau = 450e-3; %Time constant in Seconds rmeddis@38: MOCfactor = 0.5; %dB attenuation applied to the input per dB exceeding output threshold rmeddis@38: rmeddis@38: %------------------------------------------------------------------ rmeddis@38: % Band filtering properties rmeddis@38: %------------------------------------------------------------------ rmeddis@38: bwOct = 1/2; %1/1, 1/2, 1/3, 1/4, 1/5 rmeddis@38: filterOrder = 2 %BUTTER=2, GTF=3 rmeddis@38: useGTF = false; %If false, revert to butterworth rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Read only properties that are not dependent rmeddis@38: %************************************************************ rmeddis@38: properties(SetAccess = private) rmeddis@38: MOCrecord rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Constant properties rmeddis@38: %************************************************************ rmeddis@38: properties(Constant = true, Hidden = true) rmeddis@38: numAudiometricFreqs = 6; rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Dependent visable properties - calculated at runtime rmeddis@38: %************************************************************ rmeddis@38: properties(Dependent = true, Hidden = false) rmeddis@38: channelBFs %= 250 * 2.^((0:fNmax)'*params.bwOct); rmeddis@38: numChannels %= numel(channelBFs); rmeddis@38: aidOPnice %aid output reformatted to be exactly the same dimensions as the input stimulus rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Dependent invisable properties - calculated at runtime rmeddis@38: %************************************************************ rmeddis@38: properties(Dependent = true, Hidden = true) rmeddis@38: TC_dBO_INTERP % Compression threshold in terms of 2nd filter o/p in dB SPL rmeddis@38: TM_dBO_INTERP % MOC threshold in terms of 2nd filter o/p in dB SPL rmeddis@38: bwOct_INTERP rmeddis@38: DRNLb_INTERP %= ( 2e-5 .* 10.^(TCdBO/20)) .^ (1-DRNLc) ; rmeddis@38: DRNLc_INTERP rmeddis@38: mainGain_INTERP %Interp'd and in linear units rmeddis@38: rmeddis@38: ARthresholdPa %= 20e-6*10^(ARthreshold_dB/20);% Pa thresh for triggering AR rmeddis@38: stimulusINTERNAL %input stimulus in correct format for the Aid algo rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Protected properties - The user never needs to set rmeddis@38: %************************************************************ rmeddis@38: properties(Access = protected) rmeddis@38: aidOP rmeddis@38: emlc_z rmeddis@38: rmeddis@38: %-------------------------------------------------------------- rmeddis@38: % ENUMERATIONS USED IN THE FRAME PROCESSOR rmeddis@38: %-------------------------------------------------------------- rmeddis@38: enumC_ARb = 0; rmeddis@38: enumC_ARa = 2; rmeddis@38: enumC_MOCb = 4; rmeddis@38: enumC_MOCa = 6; rmeddis@38: rmeddis@38: % enumC_BPb1 = 8; rmeddis@38: % enumC_BPa1 = 13; rmeddis@38: % enumC_BPb2 = 18; rmeddis@38: % enumC_BPa2 = 23; rmeddis@38: % enumC_BPb3 = 28; rmeddis@38: % enumC_BPa3 = 33; rmeddis@38: % enumC_BPb4 = 38; rmeddis@38: % enumC_BPa4 = 43; rmeddis@38: rmeddis@38: enumS_AR = 0; rmeddis@38: rmeddis@38: % enumS_MOC1 = 1; rmeddis@38: % enumS_BPin_1_1 = 2; rmeddis@38: % enumS_BPin_2_1 = 6; rmeddis@38: % enumS_BPout_1_1 = 10; rmeddis@38: % enumS_BPout_2_1 = 14; rmeddis@38: % rmeddis@38: % enumS_MOC2 = 18; rmeddis@38: % enumS_BPin_1_2 = 19; rmeddis@38: % enumS_BPin_2_2 = 23; rmeddis@38: % enumS_BPout_1_2 = 27; rmeddis@38: % enumS_BPout_2_2 = 31; rmeddis@38: % ... rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % methods _ _ _ rmeddis@38: % | | | | | | rmeddis@38: % _ __ ___ ___| |_| |__ ___ __| |___ rmeddis@38: %| '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __| rmeddis@38: %| | | | | | __/ |_| | | | (_) | (_| \__ \ rmeddis@38: %|_| |_| |_|\___|\__|_| |_|\___/ \__,_|___/ rmeddis@38: %************************************************************ rmeddis@38: rmeddis@38: methods rmeddis@38: %% ********************************************************** rmeddis@38: % Constructor rmeddis@38: %************************************************************ rmeddis@38: function obj = EssexAid_WrapClass(sr, stimulus) rmeddis@38: rmeddis@38: if nargin > 0 rmeddis@38: obj.sr = sr; rmeddis@38: end rmeddis@38: rmeddis@38: if nargin > 1 rmeddis@38: obj.stimulusUSER = stimulus; rmeddis@38: else rmeddis@38: obj.stimulusUSER = obj.pipSequence(obj.sr); rmeddis@38: end rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for channelBFs rmeddis@38: %************************************************************ rmeddis@38: function value = get.channelBFs(obj) rmeddis@38: fNmax = 5/obj.bwOct; rmeddis@38: value = 250 * 2.^((0:fNmax)'*obj.bwOct); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for numChannels rmeddis@38: %************************************************************ rmeddis@38: function value = get.numChannels(obj) rmeddis@38: value = numel(obj.channelBFs); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for ARthresholdPa rmeddis@38: %************************************************************ rmeddis@38: function value = get.ARthresholdPa(obj) rmeddis@38: value = 20e-6*10^(obj.ARthreshold_dB/20);% Pa thresh for triggering AR rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for TC_dBO_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.TC_dBO_INTERP(obj) rmeddis@38: TC_dBO = obj.audiometry_dB - obj.mainGain_dB + obj.TC_dBHL; rmeddis@38: value = obj.interpPars(TC_dBO, obj.numChannels); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for TM_dBO_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.TM_dBO_INTERP(obj) rmeddis@38: TM_dBO = obj.audiometry_dB - obj.mainGain_dB + obj.TM_dBHL; rmeddis@38: value = obj.interpPars(TM_dBO, obj.numChannels); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for bwOct_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.bwOct_INTERP(obj) rmeddis@38: value = repmat(obj.bwOct, 1, obj.numChannels); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for DRNLb_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.DRNLb_INTERP(obj) rmeddis@38: value = ( 2e-5 .* 10.^(obj.TC_dBO_INTERP/20)) .^ (1-obj.DRNLc_INTERP); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for DRNLc_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.DRNLc_INTERP(obj) rmeddis@38: value = obj.interpPars(obj.DRNLc, obj.numChannels); rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % Get method for mainGain_INTERP rmeddis@38: %************************************************************ rmeddis@38: function value = get.mainGain_INTERP(obj) rmeddis@38: mainGainLin = 10.^(obj.mainGain_dB/20); %lin units rmeddis@38: value = obj.interpPars(mainGainLin, obj.numChannels); rmeddis@38: end rmeddis@38: rmeddis@38: %% *********************************************************** rmeddis@38: % Get method for stimulus rmeddis@38: % ----------------------- rmeddis@38: % The hearing aid expects a stereo signal, as the MOC control is rmeddis@38: % linked for left and right channels. It would be more efficient to rmeddis@38: % use a mono version of the aid for simulation in Matlab. However, rmeddis@38: % I always want to use the exact same code for the hardware in the rmeddis@38: % lab and current simulations. This code will make a mono signal rmeddis@38: % stereo if needs be and/or rotate to 2xN array. rmeddis@38: %************************************************************* rmeddis@38: function value = get.stimulusINTERNAL(obj) rmeddis@38: [nRows, nCols] = size(obj.stimulusUSER); rmeddis@38: rmeddis@38: % Assume that the stimulus duration is greater than 2 samples. rmeddis@38: % Therefore the number of channels is the min dim. rmeddis@38: [nChans, I] = min([nRows nCols]); rmeddis@38: rmeddis@38: if nChans == 2 rmeddis@38: if I == 2 rmeddis@38: value = obj.stimulusUSER; rmeddis@38: else rmeddis@38: value = obj.stimulusUSER'; rmeddis@38: end rmeddis@38: elseif nChans == 1 %Just to be explicit rmeddis@38: if I == 2 rmeddis@38: value = [obj.stimulusUSER obj.stimulusUSER]; rmeddis@38: else rmeddis@38: value = [obj.stimulusUSER; obj.stimulusUSER]'; rmeddis@38: end rmeddis@38: end rmeddis@38: end rmeddis@38: rmeddis@38: %% *********************************************************** rmeddis@38: % Get method for aid output rmeddis@38: % ----------------------- rmeddis@38: % This get method is linked to the above internal stimulus method rmeddis@38: % and allows the user to extract the hearing aid output in exactly rmeddis@38: % the same shape and size as the original input stimulus. This is rmeddis@38: % very useful for the speech recognition work and presumably rmeddis@38: % for multithreshold also. rmeddis@38: %************************************************************* rmeddis@38: function value = get.aidOPnice(obj) rmeddis@38: if ~isempty(obj.aidOP) rmeddis@38: [nRows, nCols] = size(obj.stimulusUSER); rmeddis@38: rmeddis@38: % Assume that the stimulus duration is greater than 2 samples. rmeddis@38: % Therefore the number of channels is the min dim. rmeddis@38: [nChans, I] = min([nRows nCols]); rmeddis@38: rmeddis@38: %** The aid output will ALWAYS be a 2xN array ** rmeddis@38: %The fist job is to remove trailing zeros that may have been rmeddis@38: %introduced by the framing process rmeddis@38: aidOPtruncated = obj.aidOP(:, 1:max([nRows nCols])); rmeddis@38: rmeddis@38: %The next task is to arrange the op like the ip rmeddis@38: if nChans == 2 rmeddis@38: if I == 1 rmeddis@38: value = aidOPtruncated; rmeddis@38: else rmeddis@38: value = aidOPtruncated'; rmeddis@38: end rmeddis@38: elseif nChans == 1 %Just to be explicit rmeddis@38: if I == 1 rmeddis@38: value = aidOPtruncated(1,:); rmeddis@38: else rmeddis@38: value = aidOPtruncated(1,:)'; rmeddis@38: end rmeddis@38: end rmeddis@38: else % ---- of if isempty statement rmeddis@38: value = []; rmeddis@38: end rmeddis@38: end rmeddis@38: rmeddis@38: %% *********************************************************** rmeddis@38: % *** Set methods *** rmeddis@38: % ----------------------- rmeddis@38: % This is a bunch of unexciting error hunting functions. They also rmeddis@38: % flush the aid output if any parameters change. Therefore, rmeddis@38: % processStim will have to be called explicity by the user once rmeddis@38: % again. rmeddis@38: %************************************************************* rmeddis@38: function obj = set.stimulusUSER(obj,value) rmeddis@38: [nRows, nCols] = size(value); rmeddis@38: rmeddis@38: % Assume that the stimulus duration is greater than 2 samples. rmeddis@38: % Therefore the number of channels is the min dim. rmeddis@38: nChans = min([nRows nCols]); rmeddis@38: assert(nChans<3 && nChans, 'Number of stimulus channels must be 1 or 2') rmeddis@38: rmeddis@38: obj = obj.flushAidData; %flush any previous hearing aid data if the input stimulus changes rmeddis@38: obj.stimulusUSER = value; rmeddis@38: end rmeddis@38: function obj = set.sr(obj,value) rmeddis@38: assert(value>=20e3 && value<=192e3, 'sr must be between 20 and 192 kHz') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.sr = value; rmeddis@38: end rmeddis@38: function obj = set.numSamples(obj,value) rmeddis@38: assert(value>=48 && value<=6912, 'must be between 48 and 6912 samples') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.numSamples = value; rmeddis@38: end rmeddis@38: function obj = set.audiometry_dB(obj,value) rmeddis@38: [nRows,nCols] = size(value); rmeddis@38: assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.audiometry_dB = value; rmeddis@38: end rmeddis@38: function obj = set.mainGain_dB(obj,value) rmeddis@38: [nRows,nCols] = size(value); rmeddis@38: assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.mainGain_dB = value; rmeddis@38: end rmeddis@38: function obj = set.TC_dBHL(obj,value) rmeddis@38: [nRows,nCols] = size(value); rmeddis@38: assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.TC_dBHL = value; rmeddis@38: end rmeddis@38: function obj = set.TM_dBHL(obj,value) rmeddis@38: [nRows,nCols] = size(value); rmeddis@38: assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.TM_dBHL = value; rmeddis@38: end rmeddis@38: function obj = set.DRNLc(obj,value) rmeddis@38: [nRows,nCols] = size(value); rmeddis@38: assert(nRows==obj.numAudiometricFreqs && nCols==1, 'must be 6x1 column vector') %#ok rmeddis@38: assert(all(value)>=0 && all(value)<=1, 'all DRNLc values must be between 0 and 1') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.DRNLc = value; rmeddis@38: end rmeddis@38: function obj = set.ARtau(obj,value) rmeddis@38: assert(value>=1e-3 && value<=1, 'must be between 1e-3 and 1s') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.ARtau = value; rmeddis@38: end rmeddis@38: function obj = set.ARthreshold_dB(obj,value) rmeddis@38: assert(value>0, 'set AR to a high value to disable it') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.ARthreshold_dB = value; rmeddis@38: end rmeddis@38: function obj = set.MOCtau(obj,value) rmeddis@38: assert(value>=1e-3 && value<=2, 'must be between 1e-3 and 2s') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.MOCtau = value; rmeddis@38: end rmeddis@38: function obj = set.MOCfactor(obj,value) rmeddis@38: assert(value>=0 && value<=1, 'must be between 0 and 1') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.MOCfactor = value; rmeddis@38: end rmeddis@38: function obj = set.bwOct(obj,value) rmeddis@38: assert(value==1/1 || value==1/2 || value==1/3 || value==1/4 || value==1/5, 'must be one of 1./(1:5)') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.bwOct = value; rmeddis@38: end rmeddis@38: function obj = set.filterOrder(obj,value) rmeddis@38: assert(value>0 && value<5, 'must be one of 1:4') rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.filterOrder = value; rmeddis@38: end rmeddis@38: function obj = set.useGTF(obj,value) rmeddis@38: obj = obj.flushAidData; rmeddis@38: obj.useGTF = value; rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % flushAidData rmeddis@38: % This second function is a workaround allowing a set method to rmeddis@38: % change another property value. rmeddis@38: %************************************************************ rmeddis@38: function obj = flushAidData(obj) rmeddis@38: obj.aidOP = []; rmeddis@38: obj.MOCrecord = []; rmeddis@38: end rmeddis@38: rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % OVERLOADED plot method rmeddis@38: %************************************************************ rmeddis@38: function plot(obj) rmeddis@38: clf rmeddis@38: sig2dBSPL = @(sig)20*log10(abs(sig/20e-6)+(1/(2^32))); rmeddis@38: dt = 1/obj.sr; rmeddis@38: tAxis = dt:dt:dt*size(obj.stimulusINTERNAL,1); rmeddis@38: rmeddis@38: subplot(2,1,1) rmeddis@38: plot(tAxis(1:length(obj.stimulusUSER)), sig2dBSPL(obj.stimulusUSER), 'k') rmeddis@38: if ~isempty(obj.aidOPnice) rmeddis@38: hold on rmeddis@38: plot(tAxis(1:length(obj.stimulusUSER)), sig2dBSPL(obj.aidOPnice), 'r') rmeddis@38: end rmeddis@38: ylim([0 100]) rmeddis@38: xlim([0 tAxis(length(obj.stimulusUSER))]) rmeddis@38: title('Level response') rmeddis@38: xlabel('Time in seconds') rmeddis@38: ylabel('Level in dB SPL') rmeddis@38: rmeddis@38: subplot(2,1,2) rmeddis@38: if ~isempty(obj.MOCrecord) rmeddis@38: imagesc(tAxis, 1:obj.numChannels, flipud(-20*log10(obj.MOCrecord))) rmeddis@38: colorbar rmeddis@38: end rmeddis@38: title('MOC attenuation') rmeddis@38: xlabel('Time in seconds') rmeddis@38: ylabel('Band frequency in Hz') rmeddis@38: numSpacers = 1 + (obj.numChannels-numel(obj.DRNLc)) / (numel(obj.DRNLc)-1); rmeddis@38: set(gca, 'YTick', 1:numSpacers:obj.numChannels); rmeddis@38: set(gca, 'YTickLabel', num2str(flipud([250; 500; 1000; 2000; 4000; 8000]))); rmeddis@38: end% ------ OVERLOADED plot method rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % OVERLOADED soundsc method rmeddis@38: %************************************************************ rmeddis@38: function soundsc(obj) rmeddis@38: soundsc(obj.aidOPnice, obj.sr) rmeddis@38: end rmeddis@38: rmeddis@38: %% ********************************************************** rmeddis@38: % processStim rmeddis@38: %************************************************************ rmeddis@38: function obj = processStim(obj) rmeddis@38: %-------------------------------------------------------------- rmeddis@38: % EMULATION OF THE GUI PARAMETER CONVERSIONS rmeddis@38: %-------------------------------------------------------------- rmeddis@38: biggestNumSamples = obj.numSamples; rmeddis@38: rmeddis@38: filterStatesL = (zeros(3000,1)); rmeddis@38: filterStatesR = filterStatesL; rmeddis@38: filterCoeffs = (zeros(5000,1)); rmeddis@38: rmeddis@38: %filter coefficients rmeddis@38: ARcutOff=1/(2*pi*obj.ARtau); rmeddis@38: [b,a] = butter(1,ARcutOff/(obj.sr/2)); rmeddis@38: filterCoeffs(obj.enumC_ARb+1:obj.enumC_ARb+2) = b; rmeddis@38: filterCoeffs(obj.enumC_ARa+1:obj.enumC_ARa+2) = a; rmeddis@38: rmeddis@38: MOCcutOff=1/(2*pi*obj.MOCtau); rmeddis@38: [bMOC,aMOC] = butter(1,MOCcutOff/(obj.sr/2)); rmeddis@38: filterCoeffs(obj.enumC_MOCb+1:obj.enumC_MOCb+2) = bMOC; rmeddis@38: filterCoeffs(obj.enumC_MOCa+1:obj.enumC_MOCa+2) = aMOC; rmeddis@38: rmeddis@38: rmeddis@38: for filterCount = 1:obj.numChannels rmeddis@38: %----------------------------------- rmeddis@38: % nonlinear path - filter bws rmeddis@38: %----------------------------------- rmeddis@38: lowerCutOff=obj.channelBFs(filterCount)*2^(-obj.bwOct_INTERP(filterCount)/2); rmeddis@38: upperCutOff=obj.channelBFs(filterCount)*2^( obj.bwOct_INTERP(filterCount)/2); rmeddis@38: rmeddis@38: if obj.useGTF rmeddis@38: bwHz = upperCutOff - lowerCutOff; rmeddis@38: [b_DRNL,a_DRNL] = obj.gammatone(bwHz, obj.channelBFs(filterCount), 1/obj.sr); rmeddis@38: filterCoeffs(10*(filterCount-1)+9 :10*(filterCount-1)+10) = b_DRNL; rmeddis@38: filterCoeffs(10*(filterCount-1)+14:10*(filterCount-1)+16) = a_DRNL; rmeddis@38: else rmeddis@38: [b_DRNL,a_DRNL] = butter(2,[lowerCutOff upperCutOff]/(obj.sr/2)); rmeddis@38: filterCoeffs(10*(filterCount-1)+9 :10*(filterCount-1)+13) = b_DRNL; rmeddis@38: filterCoeffs(10*(filterCount-1)+14:10*(filterCount-1)+18) = a_DRNL; rmeddis@38: end rmeddis@38: end rmeddis@38: rmeddis@38: %-------------------------------------------------------------- rmeddis@38: % EMULATION OF THE IO CALLBACK THREAD rmeddis@38: %-------------------------------------------------------------- rmeddis@38: frameBufferL = buffer(obj.stimulusINTERNAL(:,1), obj.numSamples); rmeddis@38: frameBufferR = buffer(obj.stimulusINTERNAL(:,2), obj.numSamples); rmeddis@38: nFrames = size(frameBufferL,2); rmeddis@38: rmeddis@38: pad = zeros(1,biggestNumSamples-obj.numSamples); rmeddis@38: ARampL=ones(1,biggestNumSamples); rmeddis@38: ARampR = ARampL; rmeddis@38: MOCcontrol = ones(obj.numChannels, biggestNumSamples); rmeddis@38: rmeddis@38: peakIPL = zeros(5,1); rmeddis@38: peakOPL = peakIPL; rmeddis@38: rmsIPL = peakIPL; rmeddis@38: rmsOPL = peakIPL; rmeddis@38: rmeddis@38: peakIPR = peakIPL; rmeddis@38: peakOPR = peakIPL; rmeddis@38: rmsIPR = peakIPL; rmeddis@38: rmsOPR = peakIPL; rmeddis@38: rmeddis@38: MOCend = zeros(obj.numChannels,1); rmeddis@38: rmeddis@38: op = []; rmeddis@38: moc= []; rmeddis@38: for nn = 1:nFrames rmeddis@38: frameBufferPadL = [frameBufferL(:,nn)' pad]; rmeddis@38: frameBufferPadR = [frameBufferR(:,nn)' pad]; rmeddis@38: rmeddis@38: [ outBufferL, outBufferR, filterStatesL, filterStatesR, ARampL, ARampR, MOCend, peakIPL, peakOPL, rmsIPL, rmsOPL, peakIPR, peakOPR, rmsIPR, rmsOPR, MOCcontrol ] =... rmeddis@38: EssexAidProcessVFrameSwitchable( ... rmeddis@38: frameBufferPadL,... rmeddis@38: frameBufferPadR,... rmeddis@38: filterStatesL,... rmeddis@38: filterStatesR,... rmeddis@38: filterCoeffs,... rmeddis@38: obj.numChannels,... rmeddis@38: obj.numSamples,... rmeddis@38: ARampL,... rmeddis@38: ARampR,... rmeddis@38: obj.ARthresholdPa,... rmeddis@38: obj.filterOrder,... rmeddis@38: obj.DRNLb_INTERP,... rmeddis@38: obj.DRNLc_INTERP,... rmeddis@38: obj.TM_dBO_INTERP,... rmeddis@38: obj.MOCfactor,... rmeddis@38: peakIPL,... rmeddis@38: peakOPL,... rmeddis@38: rmsIPL,... rmeddis@38: rmsOPL,... rmeddis@38: peakIPR,... rmeddis@38: peakOPR,... rmeddis@38: rmsIPR,... rmeddis@38: rmsOPR,... rmeddis@38: MOCend,... rmeddis@38: MOCcontrol,... rmeddis@38: obj.mainGain_INTERP,... rmeddis@38: obj.useGTF); rmeddis@38: rmeddis@38: rmeddis@38: outBuffer = ( [outBufferL(:, 1:obj.numSamples); outBufferR(:, 1:obj.numSamples)] ); rmeddis@38: op = [op outBuffer]; %#ok rmeddis@38: moc= [moc MOCcontrol]; %#ok rmeddis@38: rmeddis@38: end %End of frame processing emulation loop rmeddis@38: obj.aidOP = op; rmeddis@38: obj.MOCrecord=moc; rmeddis@38: rmeddis@38: rmeddis@38: end %End of process stim method rmeddis@38: rmeddis@38: end %End of methods block rmeddis@38: rmeddis@38: %% ********************************************************* rmeddis@38: % _ _ _ _ _ _ rmeddis@38: % | | | | (_) | | | | | | rmeddis@38: % ___| |_ __ _| |_ _ ___ _ __ ___ ___| |_| |__ ___ __| |___ rmeddis@38: % / __| __/ _` | __| |/ __| | '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __| rmeddis@38: % \__ \ || (_| | |_| | (__ | | | | | | __/ |_| | | | (_) | (_| \__ \ rmeddis@38: % |___/\__\__,_|\__|_|\___| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|___/ rmeddis@38: %************************************************************ rmeddis@38: rmeddis@38: methods(Static) rmeddis@38: %% ******************************************************** rmeddis@38: % pipOut - sequence of tone pips at various levels rmeddis@38: %********************************************************** rmeddis@38: function pipOut = pipSequence(sampleRate, freq, dBlevs, pulseDur, silDur) rmeddis@38: if nargin < 5 rmeddis@38: silDur = 0.3; rmeddis@38: end rmeddis@38: if nargin < 4 rmeddis@38: pulseDur = 0.1; rmeddis@38: end rmeddis@38: if nargin < 3 rmeddis@38: dBlevs = 20:20:100; rmeddis@38: end rmeddis@38: if nargin < 2 rmeddis@38: freq = 500; rmeddis@38: end rmeddis@38: if nargin < 1 rmeddis@38: sampleRate = 48e3; rmeddis@38: end rmeddis@38: rmeddis@38: dt = 1/sampleRate; rmeddis@38: tAxis = dt:dt:pulseDur; rmeddis@38: sPulse = sin(2*pi*freq*tAxis); rmeddis@38: sPulse = sPulse./sqrt(mean(sPulse.^2)); rmeddis@38: rms2dBspl = @(dBspl)20e-6*10^(dBspl/20); %sneaky short-hand function by (ab)using function handles rmeddis@38: zPad = zeros(1,ceil(sampleRate*silDur)); rmeddis@38: rmeddis@38: pipOut = []; rmeddis@38: for nn = 1:numel(dBlevs) rmeddis@38: pipOut = [ pipOut sPulse*rms2dBspl(dBlevs(nn)) zPad]; %#ok rmeddis@38: end rmeddis@38: rmeddis@38: end% ------ OF pipSequence rmeddis@38: rmeddis@38: %% ******************************************************** rmeddis@38: % interpPars - Linear interpolation of given parameter to mimic GUI rmeddis@38: % fitting functionality. rmeddis@38: %********************************************************** rmeddis@38: function fullArray = interpPars(shortArray, numBands) rmeddis@38: nGUIbands = numel(shortArray); rmeddis@38: if numBands == nGUIbands rmeddis@38: fullArray = shortArray; rmeddis@38: else rmeddis@38: numSpacers = (numBands-nGUIbands) / (nGUIbands-1); rmeddis@38: fullArray = shortArray(1); rmeddis@38: for nn = 2:nGUIbands rmeddis@38: fullArray = [fullArray,... rmeddis@38: repmat(mean([shortArray(nn) shortArray(nn-1)]),1,numSpacers),... rmeddis@38: shortArray(nn)]; %#ok rmeddis@38: end rmeddis@38: end rmeddis@38: end% ----- OF interpPars rmeddis@38: rmeddis@38: %% ******************************************************** rmeddis@38: % gammatone - get filter coefficients rmeddis@38: %********************************************************** rmeddis@38: function [b,a] = gammatone(bw, cf, dt) rmeddis@38: phi = 2 * pi * bw * dt; rmeddis@38: theta = 2 * pi * cf * dt; rmeddis@38: cos_theta = cos(theta); rmeddis@38: sin_theta = sin(theta); rmeddis@38: alpha = -exp(-phi) * cos_theta; rmeddis@38: b0 = 1.0; rmeddis@38: b1 = 2 * alpha; rmeddis@38: b2 = exp(-2 * phi); rmeddis@38: z1 = (1 + alpha * cos_theta) - (alpha * sin_theta) * 1i; rmeddis@38: z2 = (1 + b1 * cos_theta) - (b1 * sin_theta) * 1i; rmeddis@38: z3 = (b2 * cos(2 * theta)) - (b2 * sin(2 * theta)) * 1i; rmeddis@38: tf = (z2 + z3) / z1; rmeddis@38: a0 = abs(tf); rmeddis@38: a1 = alpha * a0; rmeddis@38: rmeddis@38: a = [b0, b1, b2]; rmeddis@38: b = [a0, a1]; rmeddis@38: end% ------ OF gammatone rmeddis@38: end% ------ OF static methods rmeddis@38: rmeddis@38: end %End of classdef rmeddis@38: