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