Mercurial > hg > camir-aes2014
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolboxes/bioakustik_tools/sp/seq_wavwrite.m Tue Feb 10 15:05:51 2015 +0000 @@ -0,0 +1,407 @@ +function seq_wavwrite(in_files,wavefile,varargin) +% Combines wav-data from folder or specific files in one file. +% +% seq_wavwrite(path_string,wavefile) combines all .wav files in +% path_string in the file wavefile. +% i.e.: seq_wavwrite('C:\wav\','C:\combined.wav') +% +% The file format will depend on the first file processed, the other +% files will be transfered to this format by resampling, dithering and +% channel repetition/rejection. You may use some of the extra-options 'Fs','nbits' +% or 'channels' to override these settings. +% i.e.: seq_wavwrite('C:\wav\','C:\combined.wav','Fs',44100,'nbits',16,'channels',1) +% will produce a mono file with 44.1 kHz samle rate and 16bits per sample +% +% seq_wavwrite(files_cellarray,wavefile) only combines the files specified +% in files_cellarray. +% i.e.: files={'C:\wav\test1.wav','C:\other_wav\test2.wav'}; +% seq_wavwrite(files,'C:\combined.wav'); +% +% You may want to copy only some parts of the files. +% Therefore use the extra-option 'sequences': +% seq_wavwrite(files,'C:\combined','sequences',segments); +% ,where segments is an cell array the same size as the files_cellarray, +% witch contains the position of the parts for every file. +% Every cell row contains a struct array with the following fields: +% abs_startspl and abs_stopspl +% or +% abs_startms and abs_stopms +% You may also specify the channels that are to be copied in the field +% channels +% i.e.: files={'C:\wav\test1.wav','C:\other_wav\test2.wav'}; +% segsforfile1(1).abs_startspl=1; +% segsforfile1(1).abs_stopspl=44100; +% segsforfile1(2).abs_startspl=88200; +% segsforfile1(2).abs_stopspl=200000; +% segsforfile2(1).abs_startms=1; +% segsforfile2(1).abs_stopms=2000; +% segsforfile2(1).channels=[1 2]; <- use the first two channels +% segments={segsforfile1,segsforfile2}; +% seq_wavwrite(files,'C:\combined','sequences',segments); +% +% If you want to copy specific files as a whole, just omit their abs_... +% values. +% +% seq_wavwrite uses blockwise file processing to be able to copy large +% amounts of data. The option 'max_chunksize' allows you to specify the +% blocksize in samples. Keep in mind that in multichannel mode the actual +% blocksize will be chunksize times channels. +% i.e.: seq_wavwrite('C:\wav\','C:\combined.wav','max_chunksize',44100*60) + +% Parse inputs: + +[slash,leftargs]=process_options(varargin,'systemslash','\'); + +if ischar(in_files) + data_names=dir(strcat(in_files,slash,'*.wav')); + in_files=strcat(in_files,{data_names.name}); +end + +[tmp_sig,tmp_Fs,tmp_nbits]=wavread(char(in_files{1}),[1 2]); +tmp_channels=size(tmp_sig,2); + +def_max_chunk_size = 44100*60*2;%chunksize nearly one minute at 44,1 khz sample rate +[sequences,Fs,nbits,channels,max_chunk_size]=process_options(leftargs,'sequences',[],'Fs',... + tmp_Fs,'nbits',tmp_nbits,'channels',tmp_channels,'max_chunksize',def_max_chunk_size); + +if ischar(in_files) && ~isempty(sequences) + warning('segment parameters ignored in directory-input mode') + sequences=[]; +end + +% Determine number of bytes in chunks +% (not including pad bytes, if needed): +% ---------------------------------- +% 'RIFF' 4 bytes +% size 4 bytes +% 'WAVE' 4 bytes +% 'fmt ' 4 bytes +% size 4 bytes +% <wave-format> 14 bytes +% <format_specific> 2 bytes (PCM) +% 'data' 4 bytes +% size 4 bytes +% <wave-data> N bytes +% ---------------------------------- + +bytes_per_sample = ceil(nbits/8); +fmt_cksize = 16; % Don't include 'fmt ' or its size field + +% Open file for output: +[fid,err] = OpenWaveWrite(wavefile); +error(err); +try + % Prepare basic chunk structure fields: + ck=[]; ck.fid=fid; ck.filename = wavefile; + + fwrite(fid,zeros(1,20),'uchar'); %skip previous chunks + % Write <wave-format>: + fmt.filename = wavefile; + if nbits == 32, + fmt.wFormatTag = 3; % Data encoding format (1=PCM, 3=Type 3 32-bit) + else + fmt.wFormatTag = 1; + end + fmt.nSamplesPerSec = Fs; % Samples per second + fmt.nAvgBytesPerSec = channels*bytes_per_sample*Fs; % Avg transfer rate + fmt.nBlockAlign = channels*bytes_per_sample; % Block alignment + fmt.nBitsPerSample = nbits; % standard <PCM-format-specific> info + fmt.nChannels = channels; % Number of channels + error(write_wavefmt(fid,fmt)); + + fwrite(fid,zeros(1,8),'uchar'); %skip following chunks + + % Write all audio data + sample_sum=0; + for filei=1:size(in_files,2) + resamplewarn=0; + channelwarn=0; + if ~isempty(sequences)&& ~isempty(sequences{filei}) + numsegs=size(sequences{filei},2); + else numsegs=1; + end + for seqi=1:numsegs; + tmp_fsiz=wavread(char(in_files{filei}),'size'); + tmp_fsiz=tmp_fsiz(1); + [y,tmp_fs,null]=wavread(char(in_files{filei}),[1 2]);%read data + if ~isempty(sequences) && ~isempty(sequences{filei}) + if isfield(sequences{filei}(seqi),'abs_startspl') + spl_seq=[sequences{filei}(seqi).abs_startspl sequences{filei}(seqi).abs_stopspl]; + elseif isfield(sequences{filei}(seqi),'abs_startms') + spl_seq=floor([sequences{filei}(seqi).abs_startms sequences{filei}(seqi).abs_stopms].*tmp_fs./1000); + else + spl_seq=[1 tmp_fsiz]; + end + if (spl_seq(1)< 1) || (spl_seq(2) > tmp_fsiz) + warning('correcting segment range, not necessary critical in miliseconds-mode') + spl_seq(1)=max(spl_seq(1),1); + spl_seq(2)=min(spl_seq(2),tmp_fsiz); + end + else + spl_seq=[1 tmp_fsiz]; + end + win_start=spl_seq(1); + win_stop=(min(spl_seq(2),spl_seq(1)+max_chunk_size-1)); + while win_stop <= spl_seq(2) + [y,tmp_fs,null]=wavread(char(in_files{filei}),[win_start win_stop]);%read data + if (size(y,2) > 1) && ~isempty(sequences) && isfield(sequences{filei}(seqi),'channels') %choose channel + if size(y,2) >= max(sequences{filei}(seqi).channels) + y=y(:,sequences{filei}(seqi).channels); + else + if ~channelwarn + warning('ignoring errorneous channel field'); + channelwarn=1; + end + end + end + if (tmp_fs ~= Fs) %resample data if necessary + if ~resamplewarn + fprintf('seq_wavwrite.m: resampling from %d to %d Hz. \n',tmp_fs,Fs); + resamplewarn=1; + end + y=resample(y,Fs,tmp_fs); + end + [samples,akt_channels] = size(y); + if akt_channels > channels % if necessary make equivalent channelnum + y=y(:,1:channels); + elseif akt_channels < channels + y=[y repmat(y(:,end),1,channels-akt_channels)]; + end + error(write_wavedat(fid,fmt,y)); + sample_sum=sample_sum+samples; + + if win_stop == spl_seq(2), break; + end + win_start=win_start+max_chunk_size; + win_stop=(min(spl_seq(2),win_stop+max_chunk_size)); + end + end + end + clear y; + + total_samples = sample_sum * channels; + total_bytes = total_samples * bytes_per_sample; + data_cksize = total_bytes; + + riff_cksize = 36+total_bytes; + + % Determine pad bytes: + % Determine if a pad-byte must be appended to data chunk: + if rem(data_cksize, 2) ~= 0, + fwrite(fid,0,'uchar'); + end + data_pad = rem(data_cksize,2); + riff_cksize = riff_cksize + data_pad; % + fmt_pad, always 0 + + % Write RIFF chunk: + fseek(fid,0,'bof'); + ck.ID = 'RIFF'; + ck.Size = riff_cksize; + error(write_ckinfo(ck)); + + % Write WAVE subchunk: + ck.ID = 'WAVE'; + ck.Size = []; % Indicate a subchunk (no chunk size) + error(write_ckinfo(ck)); + + % Write <fmt-ck>: + ck.ID = 'fmt '; + ck.Size = fmt_cksize; + error(write_ckinfo(ck)); + + % Write <data-ck>: + fseek(fid,36,'bof'); + ck.ID = 'data'; + ck.Size = data_cksize; + error(write_ckinfo(ck)); + err=''; +catch + err=lasterr; +end +% Close file: +fclose(fid); + +error(err); +% end of wavwrite() + + +% ------------------------------------------------------------------------ +% Private functions: +% ------------------------------------------------------------------------ + + +% ------------------------------------------------------------------------ +function [fid,err] = OpenWaveWrite(wavefile) +% OpenWaveWrite +% Open WAV file for writing. +% If filename does not contain an extension, add ".wav" + +fid = []; +err = ''; +if ~isstr(wavefile), + err='Wave file name must be a string.'; return; +end +if isempty(findstr(wavefile,'.')), + wavefile=[wavefile '.wav']; +end +% Open file, little-endian: +[fid,err] = fopen(wavefile,'wb','l'); + +return + + +% ------------------------------------------------------------------------ +function err = write_ckinfo(ck) +% WRITE_CKINFO: Writes next RIFF chunk, but not the chunk data. +% Assumes the following fields in ck: +% .fid File ID to an open file +% .ID 4-character string chunk identifier +% .Size Size of chunk (empty if subchunk) +% +% +% Expects an open FID pointing to first byte of chunk header, +% and a chunk structure. +% ck.fid, ck.ID, ck.Size, ck.Data + +errmsg = ['Failed to write ' ck.ID ' chunk to WAVE file: ' ck.filename]; +err = ''; + +if (fwrite(ck.fid, ck.ID, 'char') ~= 4), + err=errmsg; return; +end + +if ~isempty(ck.Size), + % Write chunk size: + if (fwrite(ck.fid, ck.Size, 'uint32') ~= 1), + err=errmsg; return; + end +end + +return + +% ------------------------------------------------------------------------ +function err = write_wavefmt(fid, fmt) +% WRITE_WAVEFMT: Write WAVE format chunk. +% Assumes fid points to the wave-format subchunk. +% Requires chunk structure to be passed, indicating +% the length of the chunk. + +errmsg = ['Failed to write WAVE format chunk to file' fmt.filename]; +err = ''; + +% Create <wave-format> data: +if (fwrite(fid, fmt.wFormatTag, 'uint16') ~= 1) | ... + (fwrite(fid, fmt.nChannels, 'uint16') ~= 1) | ... + (fwrite(fid, fmt.nSamplesPerSec, 'uint32' ) ~= 1) | ... + (fwrite(fid, fmt.nAvgBytesPerSec, 'uint32' ) ~= 1) | ... + (fwrite(fid, fmt.nBlockAlign, 'uint16') ~= 1), + err=errmsg; return; +end + +% Write format-specific info: +if fmt.wFormatTag==1 | fmt.wFormatTag==3, + % Write standard <PCM-format-specific> info: + if (fwrite(fid, fmt.nBitsPerSample, 'uint16') ~= 1), + err=errmsg; return; + end + +else + err='Unknown data format.'; +end + +return + + +% ----------------------------------------------------------------------- +function y = PCM_Quantize(x, fmt) +% PCM_Quantize: +% Scale and quantize input data, from [-1, +1] range to +% either an 8-, 16-, or 24-bit data range. + +% Clip data to normalized range [-1,+1]: +ClipMsg = ['Data clipped during write to file:' fmt.filename]; +ClipWarn = 0; + +% Determine slope (m) and bias (b) for data scaling: +nbits = fmt.nBitsPerSample; +m = 2.^(nbits-1); + +switch nbits +case 8, + b=128; +case {16,24}, + b=0; +otherwise, + error('Invalid number of bits specified.'); +end + +y = round(m .* x + b); + +% Determine quantized data limits, based on the +% presumed input data limits of [-1, +1]: +ylim = [-1 +1]; +qlim = m * ylim + b; +qlim(2) = qlim(2)-1; + +% Clip data to quantizer limits: +i = find(y < qlim(1)); +if ~isempty(i), + warning(ClipMsg); ClipWarn=1; + y(i) = qlim(1); +end + +i = find(y > qlim(2)); +if ~isempty(i), + if ~ClipWarn, warning(ClipMsg); end + y(i) = qlim(2); +end + +return + + +% ----------------------------------------------------------------------- +function err = write_wavedat(fid,fmt,data) +% WRITE_WAVEDAT: Write WAVE data chunk +% Assumes fid points to the wave-data chunk +% Requires <wave-format> structure to be passed. + +err = ''; + +if fmt.wFormatTag==1 | fmt.wFormatTag==3, + % PCM Format + + % 32-bit Type 3 is normalized, so no scaling needed. + if fmt.nBitsPerSample ~= 32, + data = PCM_Quantize(data, fmt); + end + + switch fmt.nBitsPerSample + case 8, + dtype='uchar'; % unsigned 8-bit + case 16, + dtype='int16'; % signed 16-bit + case 24, + dtype='bit24'; % signed 24-bit + case 32, + dtype='float'; % normalized 32-bit floating point + otherwise, + err = 'Invalid number of bits specified.'; return; + end + + % Write data, one row at a time (one sample from each channel): + [samples,channels] = size(data); + total_samples = samples*channels; + + if (fwrite(fid, reshape(data',total_samples,1), dtype) ~= total_samples), + err = 'Failed to write PCM data samples.'; return; + end + + +else + % Unknown wave-format for data. + err = 'Unsupported data format.'; +end + +return + +% end of wavwrite.m