function MYwavwrite(y,Fs,nbits,wavefile,comStr,wavlist,dispname)
%WAVWRITE Write Microsoft WAVE (".wav") sound file.
%   WAVWRITE(Y,FS,NBITS,WAVEFILE,FACTSTRING,LISTARRY,DISPSTRING) 
%   writes data Y to a Windows WAVE
%   file specified by the file name WAVEFILE, with a sample rate
%   of FS Hz and with NBITS number of bits.  NBITS must be 8 or 16.
%   Stereo data should be specified as a matrix with two columns.
%   Amplitude values outside the range [-1,+1] are clipped.
%
%   The comStr STRING value is appended the the WAV file as a FACT chunk.
%   If omitted, comStr defaults to a couple of spaces.
%
%   The wavlist specifies a series of fields to store. Wavlist must be a cell
%   array, with each cell containing a four-character ID, followed by a string.
%   Microsoft and others define a standard set of IDs. 
%   e.g.
%   wavlist{1} = ['inam','Bruce Land'] ;
%   wavlist{2} = ['icrd',datestr(datenum(now))] ;
%   If wavlist is omitted, 
%   then one entry is added: a standard 'icmt' with value 'No info stored'.
%
%   The dispname specifies a standard WAV display title in the file. If
%   dispname is omitted, then the string 'No Title' is stored.
%
%   If you use ANY of the optional chunks (FACT,LIST,DISP) , then you MUST
%   specify all parameters preceeding the chunk input parameters.
%
%   WAVWRITE(Y,FS,WAVEFILE) assumes NBITS=16 bits.
%   WAVWRITE(Y,WAVEFILE) assumes NBITS=16 bits and FS=8000 Hz.
%   WAVWRITE(Y,FS,NBITS,FACTSTRING,LISTARRAY,DISPSTRING) 
%
%   See also WAVREAD, AUWRITE.
%
% The following is a program to test the options 
% %test modified wavewrite
% 
% clear all
% 
% %read a small wav file
% [y,Fs,nbits] = wavread('c:\windows\media\ding.wav');
% 
% %file name to write new wav file
% wavefile = 'c:\my documents\junk.wav';
% 
% %String to write as a FACT chunk
% comStr = 'Testing the fact chunk!';
% 
% %cell array to pass into MYwavwrite as a LIST chunk
% wavlist{1} = ['icmt','Date Species Loc'] ; 
% wavlist{2} = ['inam','Bruce Land'] ;
% wavlist{3} = ['icrd',datestr(datenum(now))] ;
% wavlist{4} = ['ignr','EOD file'] ;
% wavlist{5} = ['imed','dat tape'];
% wavlist{6} = ['isft','matlab',version];
% wavlist{7} = ['itch','digitizer'] ;
% wavlist{8} = ['isrf','record #01'] ;
% wavlist{9} = ['bozo','any 4-character tag will work'];
% 
% %specify a display title
% dispTitle = 'EOD file';
% 
% %build the new wav file
% MYwavwrite2(y,Fs,nbits,wavefile,comStr, wavlist, dispTitle);
% 
% %now read it again to check
% [y,Fs,nbits,opts] = wavread(wavefile);
% 
% opts
% opts.info
% opts.disp

%   Copyright 1984-2000 The MathWorks, Inc. 
%   $Revision: 5.10 $  $Date: 2000/06/01 17:29:55 $

%   D. Orofino, 11/95
%
%Modified by Bruce Land, 
%Cornell University June2003
%To include optional chunks: list-info, disp, fact

% Parse inputs:
error(nargchk(2,7,nargin)); %modified to check for 6 parameters
if nargin < 3,
  wavefile = Fs;
  Fs       = 8000;
  nbits    = 16;
elseif nargin < 4,
  wavefile = nbits;
  nbits    = 16;
 elseif nargin < 5, %test for a comment
  comStr='  '; %Just print blanks if no input
elseif nargin < 6
    wavlist{1}=['icmt','No info stored'];
elseif nargin < 7
    dispname = 'No Title' ;
end

% If input is a vector, force it to be a column:
if ndims(y) > 2,
  error('Data array cannot be an N-D array.');
end
if size(y,1)==1,
   y = y(:);
end
[samples, channels] = size(y);


% Determine number of bytes in chunks
% (not including pad bytes, if needed):
% ----------------------------------
%  'RIFF'           4 bytes
%  size             4 bytes (ulong)
%  'WAVE'           4 bytes
%  'fmt '           4 bytes
%  size             4 bytes (ulong)
% <wave-format>     14 bytes
% <format_specific> 2 bytes (PCM)
%  'data'           4 bytes
%  size             4 bytes (ulong)
% <wave-data>       N bytes
%-----------------------------------
%add the following: BRL 1June2003
%'fact'             4
%size               4 (unsigned long)
%<comment string>   length(string)
%
%'list'             4
%size               4
%list 'type' (info) 4
%<list of tags, sizes and values>
%'disp'             4
%Title              length(dispname)
% ----------------------------------

%================================================
%add FACT chunk size determination
%Make sure comStr is at least 4 char long
comStr = [datestr(datenum(now)),':  ', comStr] ; 
%Header size plus string size
comment_size     = 8 + length(comStr);
%get padding requirment--file must have an even number of bytes
comment_pad    = rem(comment_size,2); 
%pad the size and string
if comment_pad 
    comment_size = comment_size+1;
    comStr = [comStr,' ']; %pad to an even # of bytes
end
%
%================================================
%add LIST chunk size determination
%
list_size = 0;
for i=1:length(wavlist)
    list_pad = rem(length(wavlist{i}),2);
    if list_pad 
        %The 4 is for the data length specifier
        list_size = list_size + length(wavlist{i}) + 5 ;
        wavlist{i} = [wavlist{i},' '];
    else
        list_size = list_size + length(wavlist{i}) + 4 ;
    end
end

%=================================================
%Add disp chunk size determination
dispname = ['    ',dispname]; %Pad blanks because min size is 4
disp_size = length(dispname);
disp_pad    = rem(disp_size,2);
if disp_pad 
    disp_size = disp_size + 1 ;
    dispname = [dispname,' '];
end
%=================================================
bytes_per_sample = ceil(nbits/8);
total_samples    = samples * channels;
total_bytes      = total_samples * bytes_per_sample;

% Don't include 'RIFF' or its size field
%add 'fact' size and 'list' size and 'disp' size
%================================================
riff_cksize = 36 +... %all of the RIFF header bytes
    total_bytes + ... %the actual payload waveform
    comment_size + ...  %'fact' header + string payload
    list_size + 12 + ...      %'list' header + length + 'info' header + list payload
    disp_size + 8;          %'disp' header + length + title payload
%================================================
fmt_cksize  = 16;               % Don't include 'fmt ' or its size field
data_cksize = total_bytes;      % Don't include 'data' or its size field

% Determine pad bytes:
data_pad    = rem(data_cksize,2);
riff_cksize = riff_cksize + data_pad; % + fmt_pad, always 0

% Open file for output:
[fid,err] = OpenWaveWrite(wavefile);
error(err);

% Prepare basic chunk structure fields:
ck=[]; ck.fid=fid; ck.filename = wavefile;

% Write RIFF chunk:
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 <wave-format>:
fmt.filename        = wavefile;
fmt.wFormatTag      = 1;            % Data encoding format (1=PCM)
fmt.nChannels       = channels;     % Number of channels
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
error(write_wavefmt(fid,fmt));

% Write <data-ck>:
ck.ID   = 'data';
ck.Size = data_cksize;
error(write_ckinfo(ck));
% Write <wave-data>, and its pad byte if needed:
error(write_wavedat(fid,fmt,y));

%================================================
%add--Write FACT chunk
ck.ID = 'FACT';
ck.Size = length(comStr);
error(write_ckinfo(ck));
error(write_factdata(fid,comStr));
%================================================
%add--Write LIST chunk
ck.ID = 'LIST';
ck.Size = list_size + 4  ; %the 4 is the length of 'info'
error(write_ckinfo(ck));
error(write_factdata(fid,'INFO'));
%now do each member of the cell array
for i=1:length(wavlist)
    ck.ID = wavlist{i}(1:4);
    ck.Size = length(wavlist{i})-4 ;
    error(write_ckinfo(ck));
    error(write_factdata(fid,wavlist{i}(5:end)));
end
%================================================
%add--Write DISP chunk
ck.ID = 'DISP' ;
ck.Size = disp_size 
error(write_ckinfo(ck));
error(write_factdata(fid,dispname));
%================================================

% Close file:
fclose(fid);

% 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, 'ulong') ~= 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,      'ushort') ~= 1) | ...
   (fwrite(fid, fmt.nChannels,       'ushort') ~= 1) | ...
   (fwrite(fid, fmt.nSamplesPerSec,  'ulong' ) ~= 1) | ...
   (fwrite(fid, fmt.nAvgBytesPerSec, 'ulong' ) ~= 1) | ...
   (fwrite(fid, fmt.nBlockAlign,     'ushort') ~= 1),
   err=errmsg; return;
end

% Write format-specific info:
if fmt.wFormatTag==1,
  % Write standard <PCM-format-specific> info:
  if (fwrite(fid, fmt.nBitsPerSample, 'ushort') ~= 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- or 16-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,
   b=0;
otherwise,
   error('Invalid number of bits specified - must be 8 or 16.');
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,
   % PCM Format
   
   data = PCM_Quantize(data, fmt);
   
   switch fmt.nBitsPerSample
   case 8,
      dtype='uchar'; % unsigned 8-bit
   case 16,
      dtype='short'; % signed 16-bit
   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
   
   % Determine # bytes/sample - format requires rounding
   %  to next integer number of bytes:
   BytesPerSample = ceil(fmt.nBitsPerSample/8);
   
   % Determine if a pad-byte must be appended to data chunk:
   if rem(total_samples*BytesPerSample, 2) ~= 0,
      fwrite(fid,0,'uchar');
   end
   
else
  % Unknown wave-format for data.
  err = 'Unsupported data format.';
end

return

%=====================================================
%Add--FACT writing routine
%actually this just writes a string to the file
function err = write_factdata(fid,comStr)
% WRITE_FACTDAT: Write  data chunk
%   Assumes fid points to the next chunk

err = '';

if (fwrite(fid, comStr, 'char') ~= length(comStr)),
      err = 'Failed to write string.'; return;
   end
   
return
%=====================================================

% end of MODIFIED wavwrite.m
