Mercurial > hg > camir-aes2014
comparison toolboxes/bioakustik_tools/sp/seq_wavwrite.m @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e9a9cd732c1e |
---|---|
1 function seq_wavwrite(in_files,wavefile,varargin) | |
2 % Combines wav-data from folder or specific files in one file. | |
3 % | |
4 % seq_wavwrite(path_string,wavefile) combines all .wav files in | |
5 % path_string in the file wavefile. | |
6 % i.e.: seq_wavwrite('C:\wav\','C:\combined.wav') | |
7 % | |
8 % The file format will depend on the first file processed, the other | |
9 % files will be transfered to this format by resampling, dithering and | |
10 % channel repetition/rejection. You may use some of the extra-options 'Fs','nbits' | |
11 % or 'channels' to override these settings. | |
12 % i.e.: seq_wavwrite('C:\wav\','C:\combined.wav','Fs',44100,'nbits',16,'channels',1) | |
13 % will produce a mono file with 44.1 kHz samle rate and 16bits per sample | |
14 % | |
15 % seq_wavwrite(files_cellarray,wavefile) only combines the files specified | |
16 % in files_cellarray. | |
17 % i.e.: files={'C:\wav\test1.wav','C:\other_wav\test2.wav'}; | |
18 % seq_wavwrite(files,'C:\combined.wav'); | |
19 % | |
20 % You may want to copy only some parts of the files. | |
21 % Therefore use the extra-option 'sequences': | |
22 % seq_wavwrite(files,'C:\combined','sequences',segments); | |
23 % ,where segments is an cell array the same size as the files_cellarray, | |
24 % witch contains the position of the parts for every file. | |
25 % Every cell row contains a struct array with the following fields: | |
26 % abs_startspl and abs_stopspl | |
27 % or | |
28 % abs_startms and abs_stopms | |
29 % You may also specify the channels that are to be copied in the field | |
30 % channels | |
31 % i.e.: files={'C:\wav\test1.wav','C:\other_wav\test2.wav'}; | |
32 % segsforfile1(1).abs_startspl=1; | |
33 % segsforfile1(1).abs_stopspl=44100; | |
34 % segsforfile1(2).abs_startspl=88200; | |
35 % segsforfile1(2).abs_stopspl=200000; | |
36 % segsforfile2(1).abs_startms=1; | |
37 % segsforfile2(1).abs_stopms=2000; | |
38 % segsforfile2(1).channels=[1 2]; <- use the first two channels | |
39 % segments={segsforfile1,segsforfile2}; | |
40 % seq_wavwrite(files,'C:\combined','sequences',segments); | |
41 % | |
42 % If you want to copy specific files as a whole, just omit their abs_... | |
43 % values. | |
44 % | |
45 % seq_wavwrite uses blockwise file processing to be able to copy large | |
46 % amounts of data. The option 'max_chunksize' allows you to specify the | |
47 % blocksize in samples. Keep in mind that in multichannel mode the actual | |
48 % blocksize will be chunksize times channels. | |
49 % i.e.: seq_wavwrite('C:\wav\','C:\combined.wav','max_chunksize',44100*60) | |
50 | |
51 % Parse inputs: | |
52 | |
53 [slash,leftargs]=process_options(varargin,'systemslash','\'); | |
54 | |
55 if ischar(in_files) | |
56 data_names=dir(strcat(in_files,slash,'*.wav')); | |
57 in_files=strcat(in_files,{data_names.name}); | |
58 end | |
59 | |
60 [tmp_sig,tmp_Fs,tmp_nbits]=wavread(char(in_files{1}),[1 2]); | |
61 tmp_channels=size(tmp_sig,2); | |
62 | |
63 def_max_chunk_size = 44100*60*2;%chunksize nearly one minute at 44,1 khz sample rate | |
64 [sequences,Fs,nbits,channels,max_chunk_size]=process_options(leftargs,'sequences',[],'Fs',... | |
65 tmp_Fs,'nbits',tmp_nbits,'channels',tmp_channels,'max_chunksize',def_max_chunk_size); | |
66 | |
67 if ischar(in_files) && ~isempty(sequences) | |
68 warning('segment parameters ignored in directory-input mode') | |
69 sequences=[]; | |
70 end | |
71 | |
72 % Determine number of bytes in chunks | |
73 % (not including pad bytes, if needed): | |
74 % ---------------------------------- | |
75 % 'RIFF' 4 bytes | |
76 % size 4 bytes | |
77 % 'WAVE' 4 bytes | |
78 % 'fmt ' 4 bytes | |
79 % size 4 bytes | |
80 % <wave-format> 14 bytes | |
81 % <format_specific> 2 bytes (PCM) | |
82 % 'data' 4 bytes | |
83 % size 4 bytes | |
84 % <wave-data> N bytes | |
85 % ---------------------------------- | |
86 | |
87 bytes_per_sample = ceil(nbits/8); | |
88 fmt_cksize = 16; % Don't include 'fmt ' or its size field | |
89 | |
90 % Open file for output: | |
91 [fid,err] = OpenWaveWrite(wavefile); | |
92 error(err); | |
93 try | |
94 % Prepare basic chunk structure fields: | |
95 ck=[]; ck.fid=fid; ck.filename = wavefile; | |
96 | |
97 fwrite(fid,zeros(1,20),'uchar'); %skip previous chunks | |
98 % Write <wave-format>: | |
99 fmt.filename = wavefile; | |
100 if nbits == 32, | |
101 fmt.wFormatTag = 3; % Data encoding format (1=PCM, 3=Type 3 32-bit) | |
102 else | |
103 fmt.wFormatTag = 1; | |
104 end | |
105 fmt.nSamplesPerSec = Fs; % Samples per second | |
106 fmt.nAvgBytesPerSec = channels*bytes_per_sample*Fs; % Avg transfer rate | |
107 fmt.nBlockAlign = channels*bytes_per_sample; % Block alignment | |
108 fmt.nBitsPerSample = nbits; % standard <PCM-format-specific> info | |
109 fmt.nChannels = channels; % Number of channels | |
110 error(write_wavefmt(fid,fmt)); | |
111 | |
112 fwrite(fid,zeros(1,8),'uchar'); %skip following chunks | |
113 | |
114 % Write all audio data | |
115 sample_sum=0; | |
116 for filei=1:size(in_files,2) | |
117 resamplewarn=0; | |
118 channelwarn=0; | |
119 if ~isempty(sequences)&& ~isempty(sequences{filei}) | |
120 numsegs=size(sequences{filei},2); | |
121 else numsegs=1; | |
122 end | |
123 for seqi=1:numsegs; | |
124 tmp_fsiz=wavread(char(in_files{filei}),'size'); | |
125 tmp_fsiz=tmp_fsiz(1); | |
126 [y,tmp_fs,null]=wavread(char(in_files{filei}),[1 2]);%read data | |
127 if ~isempty(sequences) && ~isempty(sequences{filei}) | |
128 if isfield(sequences{filei}(seqi),'abs_startspl') | |
129 spl_seq=[sequences{filei}(seqi).abs_startspl sequences{filei}(seqi).abs_stopspl]; | |
130 elseif isfield(sequences{filei}(seqi),'abs_startms') | |
131 spl_seq=floor([sequences{filei}(seqi).abs_startms sequences{filei}(seqi).abs_stopms].*tmp_fs./1000); | |
132 else | |
133 spl_seq=[1 tmp_fsiz]; | |
134 end | |
135 if (spl_seq(1)< 1) || (spl_seq(2) > tmp_fsiz) | |
136 warning('correcting segment range, not necessary critical in miliseconds-mode') | |
137 spl_seq(1)=max(spl_seq(1),1); | |
138 spl_seq(2)=min(spl_seq(2),tmp_fsiz); | |
139 end | |
140 else | |
141 spl_seq=[1 tmp_fsiz]; | |
142 end | |
143 win_start=spl_seq(1); | |
144 win_stop=(min(spl_seq(2),spl_seq(1)+max_chunk_size-1)); | |
145 while win_stop <= spl_seq(2) | |
146 [y,tmp_fs,null]=wavread(char(in_files{filei}),[win_start win_stop]);%read data | |
147 if (size(y,2) > 1) && ~isempty(sequences) && isfield(sequences{filei}(seqi),'channels') %choose channel | |
148 if size(y,2) >= max(sequences{filei}(seqi).channels) | |
149 y=y(:,sequences{filei}(seqi).channels); | |
150 else | |
151 if ~channelwarn | |
152 warning('ignoring errorneous channel field'); | |
153 channelwarn=1; | |
154 end | |
155 end | |
156 end | |
157 if (tmp_fs ~= Fs) %resample data if necessary | |
158 if ~resamplewarn | |
159 fprintf('seq_wavwrite.m: resampling from %d to %d Hz. \n',tmp_fs,Fs); | |
160 resamplewarn=1; | |
161 end | |
162 y=resample(y,Fs,tmp_fs); | |
163 end | |
164 [samples,akt_channels] = size(y); | |
165 if akt_channels > channels % if necessary make equivalent channelnum | |
166 y=y(:,1:channels); | |
167 elseif akt_channels < channels | |
168 y=[y repmat(y(:,end),1,channels-akt_channels)]; | |
169 end | |
170 error(write_wavedat(fid,fmt,y)); | |
171 sample_sum=sample_sum+samples; | |
172 | |
173 if win_stop == spl_seq(2), break; | |
174 end | |
175 win_start=win_start+max_chunk_size; | |
176 win_stop=(min(spl_seq(2),win_stop+max_chunk_size)); | |
177 end | |
178 end | |
179 end | |
180 clear y; | |
181 | |
182 total_samples = sample_sum * channels; | |
183 total_bytes = total_samples * bytes_per_sample; | |
184 data_cksize = total_bytes; | |
185 | |
186 riff_cksize = 36+total_bytes; | |
187 | |
188 % Determine pad bytes: | |
189 % Determine if a pad-byte must be appended to data chunk: | |
190 if rem(data_cksize, 2) ~= 0, | |
191 fwrite(fid,0,'uchar'); | |
192 end | |
193 data_pad = rem(data_cksize,2); | |
194 riff_cksize = riff_cksize + data_pad; % + fmt_pad, always 0 | |
195 | |
196 % Write RIFF chunk: | |
197 fseek(fid,0,'bof'); | |
198 ck.ID = 'RIFF'; | |
199 ck.Size = riff_cksize; | |
200 error(write_ckinfo(ck)); | |
201 | |
202 % Write WAVE subchunk: | |
203 ck.ID = 'WAVE'; | |
204 ck.Size = []; % Indicate a subchunk (no chunk size) | |
205 error(write_ckinfo(ck)); | |
206 | |
207 % Write <fmt-ck>: | |
208 ck.ID = 'fmt '; | |
209 ck.Size = fmt_cksize; | |
210 error(write_ckinfo(ck)); | |
211 | |
212 % Write <data-ck>: | |
213 fseek(fid,36,'bof'); | |
214 ck.ID = 'data'; | |
215 ck.Size = data_cksize; | |
216 error(write_ckinfo(ck)); | |
217 err=''; | |
218 catch | |
219 err=lasterr; | |
220 end | |
221 % Close file: | |
222 fclose(fid); | |
223 | |
224 error(err); | |
225 % end of wavwrite() | |
226 | |
227 | |
228 % ------------------------------------------------------------------------ | |
229 % Private functions: | |
230 % ------------------------------------------------------------------------ | |
231 | |
232 | |
233 % ------------------------------------------------------------------------ | |
234 function [fid,err] = OpenWaveWrite(wavefile) | |
235 % OpenWaveWrite | |
236 % Open WAV file for writing. | |
237 % If filename does not contain an extension, add ".wav" | |
238 | |
239 fid = []; | |
240 err = ''; | |
241 if ~isstr(wavefile), | |
242 err='Wave file name must be a string.'; return; | |
243 end | |
244 if isempty(findstr(wavefile,'.')), | |
245 wavefile=[wavefile '.wav']; | |
246 end | |
247 % Open file, little-endian: | |
248 [fid,err] = fopen(wavefile,'wb','l'); | |
249 | |
250 return | |
251 | |
252 | |
253 % ------------------------------------------------------------------------ | |
254 function err = write_ckinfo(ck) | |
255 % WRITE_CKINFO: Writes next RIFF chunk, but not the chunk data. | |
256 % Assumes the following fields in ck: | |
257 % .fid File ID to an open file | |
258 % .ID 4-character string chunk identifier | |
259 % .Size Size of chunk (empty if subchunk) | |
260 % | |
261 % | |
262 % Expects an open FID pointing to first byte of chunk header, | |
263 % and a chunk structure. | |
264 % ck.fid, ck.ID, ck.Size, ck.Data | |
265 | |
266 errmsg = ['Failed to write ' ck.ID ' chunk to WAVE file: ' ck.filename]; | |
267 err = ''; | |
268 | |
269 if (fwrite(ck.fid, ck.ID, 'char') ~= 4), | |
270 err=errmsg; return; | |
271 end | |
272 | |
273 if ~isempty(ck.Size), | |
274 % Write chunk size: | |
275 if (fwrite(ck.fid, ck.Size, 'uint32') ~= 1), | |
276 err=errmsg; return; | |
277 end | |
278 end | |
279 | |
280 return | |
281 | |
282 % ------------------------------------------------------------------------ | |
283 function err = write_wavefmt(fid, fmt) | |
284 % WRITE_WAVEFMT: Write WAVE format chunk. | |
285 % Assumes fid points to the wave-format subchunk. | |
286 % Requires chunk structure to be passed, indicating | |
287 % the length of the chunk. | |
288 | |
289 errmsg = ['Failed to write WAVE format chunk to file' fmt.filename]; | |
290 err = ''; | |
291 | |
292 % Create <wave-format> data: | |
293 if (fwrite(fid, fmt.wFormatTag, 'uint16') ~= 1) | ... | |
294 (fwrite(fid, fmt.nChannels, 'uint16') ~= 1) | ... | |
295 (fwrite(fid, fmt.nSamplesPerSec, 'uint32' ) ~= 1) | ... | |
296 (fwrite(fid, fmt.nAvgBytesPerSec, 'uint32' ) ~= 1) | ... | |
297 (fwrite(fid, fmt.nBlockAlign, 'uint16') ~= 1), | |
298 err=errmsg; return; | |
299 end | |
300 | |
301 % Write format-specific info: | |
302 if fmt.wFormatTag==1 | fmt.wFormatTag==3, | |
303 % Write standard <PCM-format-specific> info: | |
304 if (fwrite(fid, fmt.nBitsPerSample, 'uint16') ~= 1), | |
305 err=errmsg; return; | |
306 end | |
307 | |
308 else | |
309 err='Unknown data format.'; | |
310 end | |
311 | |
312 return | |
313 | |
314 | |
315 % ----------------------------------------------------------------------- | |
316 function y = PCM_Quantize(x, fmt) | |
317 % PCM_Quantize: | |
318 % Scale and quantize input data, from [-1, +1] range to | |
319 % either an 8-, 16-, or 24-bit data range. | |
320 | |
321 % Clip data to normalized range [-1,+1]: | |
322 ClipMsg = ['Data clipped during write to file:' fmt.filename]; | |
323 ClipWarn = 0; | |
324 | |
325 % Determine slope (m) and bias (b) for data scaling: | |
326 nbits = fmt.nBitsPerSample; | |
327 m = 2.^(nbits-1); | |
328 | |
329 switch nbits | |
330 case 8, | |
331 b=128; | |
332 case {16,24}, | |
333 b=0; | |
334 otherwise, | |
335 error('Invalid number of bits specified.'); | |
336 end | |
337 | |
338 y = round(m .* x + b); | |
339 | |
340 % Determine quantized data limits, based on the | |
341 % presumed input data limits of [-1, +1]: | |
342 ylim = [-1 +1]; | |
343 qlim = m * ylim + b; | |
344 qlim(2) = qlim(2)-1; | |
345 | |
346 % Clip data to quantizer limits: | |
347 i = find(y < qlim(1)); | |
348 if ~isempty(i), | |
349 warning(ClipMsg); ClipWarn=1; | |
350 y(i) = qlim(1); | |
351 end | |
352 | |
353 i = find(y > qlim(2)); | |
354 if ~isempty(i), | |
355 if ~ClipWarn, warning(ClipMsg); end | |
356 y(i) = qlim(2); | |
357 end | |
358 | |
359 return | |
360 | |
361 | |
362 % ----------------------------------------------------------------------- | |
363 function err = write_wavedat(fid,fmt,data) | |
364 % WRITE_WAVEDAT: Write WAVE data chunk | |
365 % Assumes fid points to the wave-data chunk | |
366 % Requires <wave-format> structure to be passed. | |
367 | |
368 err = ''; | |
369 | |
370 if fmt.wFormatTag==1 | fmt.wFormatTag==3, | |
371 % PCM Format | |
372 | |
373 % 32-bit Type 3 is normalized, so no scaling needed. | |
374 if fmt.nBitsPerSample ~= 32, | |
375 data = PCM_Quantize(data, fmt); | |
376 end | |
377 | |
378 switch fmt.nBitsPerSample | |
379 case 8, | |
380 dtype='uchar'; % unsigned 8-bit | |
381 case 16, | |
382 dtype='int16'; % signed 16-bit | |
383 case 24, | |
384 dtype='bit24'; % signed 24-bit | |
385 case 32, | |
386 dtype='float'; % normalized 32-bit floating point | |
387 otherwise, | |
388 err = 'Invalid number of bits specified.'; return; | |
389 end | |
390 | |
391 % Write data, one row at a time (one sample from each channel): | |
392 [samples,channels] = size(data); | |
393 total_samples = samples*channels; | |
394 | |
395 if (fwrite(fid, reshape(data',total_samples,1), dtype) ~= total_samples), | |
396 err = 'Failed to write PCM data samples.'; return; | |
397 end | |
398 | |
399 | |
400 else | |
401 % Unknown wave-format for data. | |
402 err = 'Unsupported data format.'; | |
403 end | |
404 | |
405 return | |
406 | |
407 % end of wavwrite.m |