
function fmt = wavwrite_Header(samples, channels, Fs, nbits, wavefile)
%WAVWRITE Write Microsoft WAVE (".wav") sound file header.

%   WAVWRITE(SAMPLES, CHANNELS, FS, NBITS, WAVEFILE) writes a header for a
%   Windows WAVE file specified by the file name WAVEFILE, with a total
%   number of SAMPLES samples, for CHANNELS channels, 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.
%
%   See also WAVWRITE, WAVREAD, AUWRITE.

%   Copyright (c) 2001 by Fraunhofer-IGD
%   $Revision: 1.0 $  $Date: 2001/07/12 $

%   M.Arnold, 07/01

% Parse inputs: Returns an error message if number of input arguments is
% not exactly 5. Display error message.
error(nargchk(5,5,nargin));

% The WAVE file format is a subset of Microsoft's RIFF specification for the
% storage of multimedia files. A RIFF file starts out with a file header
% followed by a sequence of data chunks. A WAVE file is often just a RIFF
% file with a single "WAVE" chunk which consists of two sub-chunks -- a "fmt
% " chunk specifying the data format and a "data" chunk containing the
% actual sample data.

% Determine number of bytes in chunks
% (not including pad bytes, if needed):
% ----------------------------------
%  'RIFF'           4 bytes (big-endian form)
%  size             4 bytes (ulong)
%  'WAVE'           4 bytes (big-endian form)
%  'fmt '           4 bytes (big-endian form)
%  size             4 bytes (ulong)
% <format_specific> 2 bytes (PCM)
% <wave-format>     14 bytes
%  'data'           4 bytes (big-endian form)
%  size             4 bytes (ulong)
% <wave-data>       N bytes
% ----------------------------------
% Round to the nearest integer (-1.2 -> -1, 1.2 -> 2, 3.6 -> 4) towards infinity
bytes_per_sample = ceil(nbits/8);
total_samples    = samples * channels;
total_bytes      = total_samples * bytes_per_sample;

% 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
riff_cksize = 36+total_bytes;   % Don't include 'RIFF' or its size field
% 16 for PCM. This is the size of the Subchunk, which follows this number.
fmt_cksize  = 16;               % Don't include 'fmt ' or its size field
% Number of bytes in the data.
data_cksize = total_bytes;      % Don't include 'data' or its size field

% Determine pad bytes, if data does not end on an even byte
% boundary. Don't know exactly why!
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
% Number of bytes for one sample including all channels.
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));

% Close file:
fclose(fid);

% end of wavwrite_Header()


% ------------------------------------------------------------------------
% 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 and check if PCM.
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

% end of wavwrite_Header.m
