ita_NI_daq_run.m 8.47 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
function varargout = ita_NI_daq_run(varargin)
% ITA_NI_DAQ_RUN play and record sound with NI card and DAQ toolbox (adapted from ita_portaudio_run)
% see ita_portaudio_run for options and help

% Autor: Markus Mueller-Trapet -- Email: markus.mueller-trapet@nrc.ca
% Created:  13-Apr-2017

%% ITA Toolbox preferences for verbose level
verboseMode = ita_preferences('verboseMode');

%% Input checks
if isa(varargin{1},'itaAudio')
    % if a signal is the first input, then playback that signal
    sArgs.pos1_data = 'itaAudioTime';
    playback = true;
else
    % otherwise just record for the given number of samples
    sArgs.pos1_data = 'numeric';
    playback = false;
end

% second argument has to be the DAQ NI session
sArgs.pos2_niSession = 'anything';

% only do recording if requested
if nargout > 0
    record = true;
else
    record = false;
end

%% Init Settings
sArgs.normalizeinput    = false;
34
sArgs.normalizeoutput   = false;
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

sArgs.inputchannels     = [];
sArgs.outputchannels    = [];
sArgs.recsamples        = -1;
sArgs.repeats           = 1;
sArgs.samplingRate      = -1; % will always be taken from NI session
% cancel button and monitor not supported currently, but kept for compatibility
sArgs.cancelbutton      = ita_preferences('portAudioMonitor'); % -1:automatic; 0:off; 1:on
sArgs.latencysamples    = 0;
sArgs.singleprecision   = false;

[data, niSession, sArgs] = ita_parse_arguments(sArgs,varargin);

if ~playback && sArgs.recsamples == -1
    sArgs.recsamples = data;
end

52 53 54 55
if ~playback && ~record
    error('ITA_NI_DAQ_RUN:What do you want? Play or Record, or both? You need to specify an input and/or an output!')
end

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
sArgs.samplingRate = round(niSession.Rate); % NI rate is not exact

in_channel_vec   = sArgs.inputchannels;
out_channel_vec  = sArgs.outputchannels;
normalize_input  = sArgs.normalizeinput;
normalize_output = sArgs.normalizeoutput;

if playback
    % Extract channels to play
    % are there enough channels to play
    if data.nChannels == 1 && (length(out_channel_vec) > 1) %playback the same on alle channels activated
        ita_verbose_info('Oh Lord. I will playback the same on all channels.', 2)
        nChannelsToPlay = length(out_channel_vec);
        data = data.ch(ones(1,nChannelsToPlay)); %duplicated data to all channels
    elseif data.nChannels < length(out_channel_vec) %too many output channels for this input data
        ita_verbose_info('Not enough channels in data to play.',0)
        out_channel_vec = out_channel_vec(1:data.nChannels);
    elseif data.nChannels > length(out_channel_vec)
        ita_verbose_info('Too many channels in data file, I will only play the first ones',1);
        data = ita_split(data,1:length(out_channel_vec));
    end
    
    % Show channel data if requested
    if verboseMode==2
        ita_metainfo_show_channelnames(data);
    end
    
    % Check levels - Normalizing
84 85 86 87 88 89 90 91
    % determine clipping limit from NI session information
    outputClipping = 1; % standard
    for iChannel = numel(niSession.Channels)
        isOutput = ~isempty(strfind(niSession.Channels(iChannel).ID,'ao'));
        if isOutput
            outputClipping = max(outputClipping,max(abs(double(niSession.Channels(iChannel).Range))));
        end
    end    
92
    peak_value = max(max(abs(data.timeData)));
93
    if (peak_value > outputClipping) || (normalize_output)
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
        ita_verbose_info('Oh Lord! Levels too high for playback. Normalizing...',0)
        data = ita_normalize_dat(data);
    end
end

%% Extend excitation to compensate soundcard latency
if playback && record && ~isempty(sArgs.latencysamples) && (sArgs.latencysamples > 0)
    latencysamples = sArgs.latencysamples;
    data = ita_extend_dat(data,data.nSamples+latencysamples,'forcesamples');
end

% record as many samples as are in the playback signal
if playback
    sArgs.recsamples = data.nSamples;
end

110 111 112 113 114 115 116
if record
    % Full (double) precision
    recordDatadat = zeros(sArgs.recsamples,numel(in_channel_vec),sArgs.repeats);
    if sArgs.singleprecision
        % only single precision
        recordDatadat = single(recordDatadat);
    end
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
end

% run measurement, possibly repeated
for idrep = 1:sArgs.repeats
    if playback && record
        niSession.queueOutputData(double(data.time));
        ita_verbose_info('start playback and record',1)
    elseif record
        niSession.queueOutputData(zeros(sArgs.recsamples,1));
        ita_verbose_info('start record',1)
    elseif playback
        niSession.queueOutputData(double(data.time));
        ita_verbose_info('start playback',1)
    else
        error('ITA_NI_DAQ_run:No input and no output, what should I do?')
    end
    pause(0.01)
    % do the measurement
135 136 137 138 139 140
    if record
        if ~sArgs.singleprecision % Full (double) precision
            recordDatadat(:,:,idrep) = niSession.startForeground();
        else
            recordDatadat(:,:,idrep) = single(niSession.startForeground());
        end
141
    else
142
        niSession.startForeground();
143 144 145 146 147
    end
end % loop for repeats

ita_verbose_info('playback/record finished ',1);

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
if record
    recordData = itaAudio();
    recordData.dataType = class(recordDatadat);
    recordData.dataTypeOutput = class(recordDatadat);
    % Check if we need to average multiple measurements:
    if size(recordDatadat,3) > 1
        % average:
        recordData.timeData = mean(recordDatadat,3);
    else
        % no average: (This saves memory!)
        recordData.timeData = recordDatadat;
    end
    recordData.samplingRate = sArgs.samplingRate;
    
    for idx = 1:numel(in_channel_vec)
        recordData.channelNames{idx} = ['Ch ' int2str(in_channel_vec(idx))];
    end
165 166 167 168 169 170 171 172 173 174
end

%% Remove Latency
if playback && record && ~isempty(sArgs.latencysamples) && (sArgs.latencysamples > 0)
    recordData = ita_extract_dat(recordData,recordData.nSamples-latencysamples,'firstsample',latencysamples+1);
end

%% Check for clipping and other errors
clipping = false;
if record
175 176 177 178 179 180 181 182 183 184
    % determine clipping limit from NI session information
    clippingLimit = Inf;
    for iChannel = numel(niSession.Channels)
        isInput = ~isempty(strfind(niSession.Channels(iChannel).ID,'ai'));
        if isInput
            clippingLimit = min(clippingLimit,max(abs(double(niSession.Channels(iChannel).Range))));
        end
    end    
    
    if any(any(abs(recordData.timeData)>=clippingLimit)) % Check for clipping (NI card actually handles up to 10Vpk)
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        ita_verbose_info('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!',0);
        ita_verbose_info('!ITA_NI_DAQ_RUN:Careful, Clipping!',0);
        ita_verbose_info('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!',0);
        clipping = true;
    end
    % This check for singularities needs a lot of memory! TODO: find a
    % better solution!:
    if any(isnan(recordData.timeData(:))) || any(isinf(recordData.timeData(:))) %Check for singularities
        ita_verbose_info('There are singularities in the audio signal! You''d better check your settings!',0)
        clipping = true;
    end
    if any(all(recordData.timeData == 0,1)) && record
        ita_verbose_info('There are empty channels in the audio signal! You''d better check your settings!',0)
    end
    % maximum for each channel
    maxData = max(abs(recordData.timeData),[],1);
    [channelMax, indexMax] = max(maxData);
    
    % jri: to detect non working microphones etc, the minimum of the
    % maximums on the channels is also outputted
    if length(in_channel_vec) > 1
        [channelMin, indexMin] = min(maxData);
        ita_verbose_info(['Minimum digital level: ' int2str(20*log10(channelMin)) ' dBFS on channel: ' int2str(in_channel_vec(indexMin))],0);
    end
    ita_verbose_info(['Maximum digital level: ' int2str(20*log10(channelMax)) ' dBFS on channel: ' int2str(in_channel_vec(indexMax))],0);
210 211 212 213 214 215 216 217 218
    
    % Add history line
    infosforhistory = struct('PlayDevice','NI','Play_Channels',out_channel_vec,'RecDevice','NI','Rec_Channels',in_channel_vec,'Sampling_Rate',niSession.Rate,'Normalize_Input',normalize_input,'Normalize_Output',0,'Rec_Samples',sArgs.recsamples,'Block',1,'Repeats',sArgs.repeats);
    recordData = ita_metainfo_add_historyline(recordData,'ita_NI_daq_run',[{data}; ita_struct2arguments(infosforhistory)],'withsubs');
    
    if clipping
        recordData = ita_metainfo_add_historyline(recordData,'!!!ITA_NI_DAQ_RUN:Careful, clipping or something else went wrong!!!');
        recordData = ita_errorlog_add(recordData,'!!!ITA_NI_DAQ_RUN:Careful, clipping or something else went wrong!!!');
    end
219 220 221
end

%% Find output parameters
222
if nargout ~= 0 && record
223
    if normalize_input
224
        recordData = ita_normalize_dat(recordData);
225 226 227 228 229
    end
    varargout{1} = recordData;
end

end % function