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