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