# v_writewav

## PURPOSE

V_WRITEWAV Creates .WAV format sound files FIDX=(D,FS,FILENAME,MODE,NSKIP,MASK,MAD)

## SYNOPSIS

function fidx=v_writewav(d,fs,filename,mode,nskip,mask,mad)

## DESCRIPTION

    V_WRITEWAV Creates .WAV format sound files FIDX=(D,FS,FILENAME,MODE,NSKIP,MASK,MAD)

       The input arguments for WRITEWAV are as follows (use [] for null arguments):

           D           The sampled data to save
           FS          The rate at which the data was sampled
           FILENAME    A string containing the name of the .WAV file to create or
                            alternatively the FIDX output from a previous v_writewav call
           MODE        String containing any reasonable mixture of flags below (*=default):
           NSKIP       Number of samples to skip before writing or -1[default] to continue from previous write
                       Only valid if FIDX is specified for FILENAME
           MASK        specifies the speaker positions included as a bit mask giving the channels present in the following order (0 is LSB):
                       0=FL, 1=FR, 2=FC, 3=W, 4=BL, 5=BR, 6=FLC, 7=FRC, 8=BC, 9=SL, 10=SR, 11=TC, 12=TFL, 13=TFC, 14=TFR, 15=TBL, 16=TBC, 17=TBR
                       where F=front, L=left, C=centre, W=woofer (low frequency), B=back, LC=left of centre, RC=right of centre, S=side, T=top
           MAD         specifies the input scale for 's' option [default: max(abs(d(:)))]

     MODE flags (*=default):
      Precision: 'a'    for 8-bit A-law PCM
                 'u'    for 8-bit mu-law PCM
                '16' *     for 16 bit PCM data
                 '8'    for 8 bit PCM data
                 ...    any number in the range 2 to 32 for PCM
                 'v'    32-bit floating point
                 'V'    64-bit floating point
                 'c'    embed in 16 bits
                 'C'    embed in 24 bits
                 'L'    embed in 32 bits
          Dither: 'w'    White triangular dither of amplitude +-1 LSB (PCM modes only)
                 'h'    High pass dither (filtered by 1-1/z) (PCM modes only)
                 'l'    Low pass dither (filtered by 1+1/z) (PCM modes only)
        Scaling: 's' *  Auto scale to make data peak (or MAD input value) = +-1
                 'r'    Raw unscaled data (integer values)
                 'q'    Scaled to make unity mean square correspond to 0dBm according to G.711
                 'p'       Scaled to make +-1 equal full scale
                 'o'    Scale to bin centre rather than bin edge (e.g. 127 rather than 127.5 for 8 bit values)
                         (can be combined with n+p,r,s modes)
                 'n'    Scale to negative peak rather than positive peak (e.g. 128.5 rather than 127.5 for 8 bit values)
                         (can be combined with o+p,r,s modes)
                 'g'    Include a gain factor so that "v_readwav" will restore the correct level
         Offset: 'y' *     Correct for offset in <=8 bit PCM data
                 'z'    Do not apply offset correction
         Format: 'x'    use WAVEFORMATEX format (default for non PCM)
                 'X'    use WAVEFORMATEXTENSIBLE (default if MASK input is non-zero)
                 'e'    use original WAVEFORMAT (default for PCM)
                 'E'    include a 'fact' chunk (default for non-PCM)
       File I/O: 'f'    Do not close file on exit
                 'd'    Look in data directory: v_voicebox('dir_data')

     Output Parameter:

        FIDX     Information row vector containing the element listed below.

               (1)  file id
                (2)  current position in file (in samples, 0=start of file)
               (3)  dataoff    length of file header in bytes
               (4)  nsamp    number of samples
               (5)  nchan    number of channels
               (6)  nbyte    bytes per data value
               (7)  bits    number of bits of precision
               (8)  code    Data format: 1=PCM, 2=ADPCM, 6=A-law, 7=Mu-law
               (9)  fs    sample frequency
               (10) dither state variable
               (11) gain in dB (in INST chunk)

       Note: WRITEWAV will create an 16-bit PCM, auto-scaled wave file by default.
       For stereo data, d(:,1) is the left channel and d(:,2) the right

       See also READWAV

## CROSS-REFERENCE INFORMATION

This function calls:

- v_ditherq V_DITHERQ add dither and quantize [Y,ZF]=(X,M,ZI)
- v_lin2pcma V_LIN2PCMA Convert linear PCM to A-law P=(X,M,S)
- v_lin2pcmu V_LIN2PCMU Convert linear to Mu-law PCM P=(X,S)
- v_voicebox V_VOICEBOX set global parameters for Voicebox functions Y=(FIELD,VAL)

This function is called by:

- v_psycdigit V_PSYCDIGIT measures psychometric function using TIDIGITS stimuli
- wavwrite WAVREAD Legacy MATLAB function to write .WAV file (Y,Fs,N,FILENAME)

## SOURCE CODE

    0001 function fidx=v_writewav(d,fs,filename,mode,nskip,mask,mad)
    0002 %V_WRITEWAV Creates .WAV format sound files FIDX=(D,FS,FILENAME,MODE,NSKIP,MASK,MAD)
    0003 %
    0004 %   The input arguments for WRITEWAV are as follows (use [] for null arguments):
    0005 %
    0006 %       D           The sampled data to save
    0007 %       FS          The rate at which the data was sampled
    0008 %       FILENAME    A string containing the name of the .WAV file to create or
    0009 %                        alternatively the FIDX output from a previous v_writewav call
    0010 %       MODE        String containing any reasonable mixture of flags below (*=default):
    0011 %       NSKIP       Number of samples to skip before writing or -1[default] to continue from previous write
    0012 %                   Only valid if FIDX is specified for FILENAME
    0013 %       MASK        specifies the speaker positions included as a bit mask giving the channels present in the following order (0 is LSB):
    0014 %                   0=FL, 1=FR, 2=FC, 3=W, 4=BL, 5=BR, 6=FLC, 7=FRC, 8=BC, 9=SL, 10=SR, 11=TC, 12=TFL, 13=TFC, 14=TFR, 15=TBL, 16=TBC, 17=TBR
    0015 %                   where F=front, L=left, C=centre, W=woofer (low frequency), B=back, LC=left of centre, RC=right of centre, S=side, T=top
    0016 %       MAD         specifies the input scale for 's' option [default: max(abs(d(:)))]
    0017 %
    0018 % MODE flags (*=default):
    0019 %  Precision: 'a'    for 8-bit A-law PCM
    0020 %             'u'    for 8-bit mu-law PCM
    0021 %            '16' *     for 16 bit PCM data
    0022 %             '8'    for 8 bit PCM data
    0023 %             ...    any number in the range 2 to 32 for PCM
    0024 %             'v'    32-bit floating point
    0025 %             'V'    64-bit floating point
    0026 %             'c'    embed in 16 bits
    0027 %             'C'    embed in 24 bits
    0028 %             'L'    embed in 32 bits
    0029 %      Dither: 'w'    White triangular dither of amplitude +-1 LSB (PCM modes only)
    0030 %             'h'    High pass dither (filtered by 1-1/z) (PCM modes only)
    0031 %             'l'    Low pass dither (filtered by 1+1/z) (PCM modes only)
    0032 %    Scaling: 's' *  Auto scale to make data peak (or MAD input value) = +-1
    0033 %             'r'    Raw unscaled data (integer values)
    0034 %             'q'    Scaled to make unity mean square correspond to 0dBm according to G.711
    0035 %             'p'       Scaled to make +-1 equal full scale
    0036 %             'o'    Scale to bin centre rather than bin edge (e.g. 127 rather than 127.5 for 8 bit values)
    0037 %                     (can be combined with n+p,r,s modes)
    0038 %             'n'    Scale to negative peak rather than positive peak (e.g. 128.5 rather than 127.5 for 8 bit values)
    0039 %                     (can be combined with o+p,r,s modes)
    0040 %             'g'    Include a gain factor so that "v_readwav" will restore the correct level
    0041 %     Offset: 'y' *     Correct for offset in <=8 bit PCM data
    0042 %             'z'    Do not apply offset correction
    0043 %     Format: 'x'    use WAVEFORMATEX format (default for non PCM)
    0044 %             'X'    use WAVEFORMATEXTENSIBLE (default if MASK input is non-zero)
    0045 %             'e'    use original WAVEFORMAT (default for PCM)
    0046 %             'E'    include a 'fact' chunk (default for non-PCM)
    0047 %   File I/O: 'f'    Do not close file on exit
    0048 %             'd'    Look in data directory: v_voicebox('dir_data')
    0049 %
    0050 %
    0051 % Output Parameter:
    0052 %
    0053 %    FIDX     Information row vector containing the element listed below.
    0054 %
    0055 %           (1)  file id
    0056 %            (2)  current position in file (in samples, 0=start of file)
    0057 %           (3)  dataoff    length of file header in bytes
    0058 %           (4)  nsamp    number of samples
    0059 %           (5)  nchan    number of channels
    0060 %           (6)  nbyte    bytes per data value
    0061 %           (7)  bits    number of bits of precision
    0062 %           (8)  code    Data format: 1=PCM, 2=ADPCM, 6=A-law, 7=Mu-law
    0063 %           (9)  fs    sample frequency
    0064 %           (10) dither state variable
    0065 %           (11) gain in dB (in INST chunk)
    0066 %
    0067 %   Note: WRITEWAV will create an 16-bit PCM, auto-scaled wave file by default.
    0068 %   For stereo data, d(:,1) is the left channel and d(:,2) the right
    0069 %
    0070 %   See also READWAV
    0071
    0072 %   *** Note on scaling ***
    0073 %   If we want to scale signal values in the range +-1 to an integer in the
    0074 %   range [-128,127] then we have four plausible choices corresponding to
    0075 %   scale factors of (a) 127, (b) 127.5, (c) 128 or (d) 128.5 but each choice
    0076 %   has disadvantages.
    0077 %   For forward scaling: (c) and (d) cause clipping on inputs of +1.
    0078 %   For reverse scaling: (a) and (b) can generate output values < -1.
    0079 %   Any of these scalings can be selected via the mode input: (a) 'o', (b) default, (c) 'on', (d) 'n'
    0080
    0081 %       Copyright (C) Mike Brookes 1998-2011
    0082 %      Version: $Id: v_writewav.m 10865 2018-09-21 17:22:45Z dmb $
    0083 %
    0084 %   VOICEBOX is a MATLAB toolbox for speech processing.
    0085 %   Home page: http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/voicebox.html
    0086 %
    0087 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    0088 %   This program is free software; you can redistribute it and/or modify
    0089 %   it under the terms of the GNU Lesser General Public License as published by
    0090 %   the Free Software Foundation; either version 3 of the License, or
    0091 %   (at your option) any later version.
    0092 %
    0093 %   This program is distributed in the hope that it will be useful,
    0094 %   but WITHOUT ANY WARRANTY; without even the implied warranty of
    0095 %   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    0096 %   GNU Lesser General Public License for more details.
    0097 %
    0098 %   You can obtain a copy of the GNU Lesser General Public License from
    0099 %   https://www.gnu.org/licenses/ .
    0100 %
    0101 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    0102
    0103 % Acknowledgements
    0104 %   Thanks to Hugh Barnes for sorting out seek problems with MATLAB 6.5
    0105
    0106 % Bugs/suggestions
    0107 %  Save the following factors in FIDX: (a) scale factor, (b) offset (c) low/high clip limits
    0108 %       (d) dither position  (e) rounding position
    0109
    0110
    0111 if nargin<3
    0112     error('Usage: WRITEWAV(data,fs,filename,mode,nskip)');
    0113 end
    0114 if nargin<6
    0115     mask=0;
    0116 end
    0117 info=zeros(1,11);
    0118 info(9)=fs;
    0119 if nargin<4
    0120     mode='s';
    0121 else
    0122     mode = [mode(:).' 's'];  % ensure that there is always a scaling option specified
    0123 end
    0124 info(8)=1;     % default mode is PCM
    0125 mno=all(mode~='o');                      % scale to input limits not output limits
    0126 k=find((mode>='0') & (mode<='9'),1);
    0127 if k
    0128     info(7)=sscanf(mode(k:end),'%d');  % valid bits per data value
    0129 else
    0130     info(7)=16;
    0131 end
    0132 if any(mode=='c')
    0133     info(6)=2;       % bytes per data value = 2
    0134 elseif any(mode=='C')
    0135     info(6)=3;       % bytes per data value = 3
    0136 elseif any(mode=='L')
    0137     info(6)=4;       % bytes per data value = 4
    0138 else
    0139     info(6)=ceil(info(7)/8);       % bytes per data value
    0140 end
    0141 lo=-pow2(0.5,info(7));
    0142 hi=-1-lo;
    0143 pk=pow2(0.5,8*info(6))*(1-(mno/2-all(mode~='n'))/lo);  % use modes o and n to determine effective peak
    0144 % should perhaps have another variable besides info(7) to control dither position (or set info(7) later)
    0145 % for A and mu law the dither position is not the same as the number of bits.
    0146 if any(mode=='a')
    0147     info(8)=6;
    0148     pk=4032+mno*64;
    0149     info(7)=8;  % Some sources say this should be listed as 16 valid bits
    0150     info(6)=1;
    0151 elseif any(mode=='u')
    0152     info(8)=7;
    0153     pk=8031+mno*128;
    0154     info(7)=8;  % Some sources say this should be listed as 16 valid bits
    0155     info(6)=1;
    0156 elseif any(mode=='v')
    0157     pk=1;
    0158     mode(end)='r';  % default scaling is 'r'
    0159     info(6)=4;  % bytes
    0160     info(7)=32; % bits
    0161     info(8)=3;  % WAVE type
    0162 elseif any(mode=='V')
    0163     pk=1;
    0164     mode(end)='r';   % default scaling is 'r'
    0165     info(6)=8; % bytes
    0166     info(7)=64; % bits
    0167     info(8)=3; % WAVE type
    0168 end            % is this pk value correct ?
    0169 sc=mode(find((mode>='p') & (mode<='s'),1)); % find the first scaling option (always exists)
    0170 z=128*all(mode~='z');
    0171 if any(mode=='w')
    0172     di='w';                       % select dither mode
    0173 elseif any(mode=='h')
    0174     di='h';
    0175 elseif any(mode=='l')
    0176     di='l';
    0177 else
    0178     di='n';
    0179 end
    0180
    0181 % Now sort out which wave format to use
    0182 if any(mode=='e')
    0183     wavtype=1;              % original WAVEFORMAT format
    0184 elseif any(mode=='x')
    0185     wavtype=2;              % WAVEFORMATEX format
    0186 elseif any(mode=='X') || (nargin>=6 && ~isempty(mask) && mask~=0)
    0187     wavtype=3;              % WAVEFORMATEXTENSIBLE format
    0188 else
    0189     wavtype=2-(info(8)==1);
    0190 end
    0191 wavfmt=info(8)*(wavtype<3)+(pow2(16)-2)*(wavtype==3);
    0192 fmtlen=[16 18 40]; % length of format chunk
    0193 factlen=12*(any(mode=='E') || info(8)~=1);
    0194 instlen=16*any(mode=='g');  % length of INST chunk (force to be even since some readers do not like odd lengths)
    0195 wavlen=[36 38 60]+factlen+instlen; % length of entire WAVE chunk except for the data (not including 8 byte RIFF header)
    0196
    0197
    0198 [n,nc]=size(d);
    0199 if n==1
    0200     n=nc;
    0201     nc=1;
    0202 else
    0203     d = d.';
    0204 end;
    0205 if nc>32
    0206     error('WRITEWAV: attempt to write a sound file with >32 channels');
    0207 end
    0208 nc=max(nc,1);
    0209 ncy=nc*info(6);                     % bytes per sample time
    0210 nyd=n*ncy;                          % bytes to write
    0211 if ischar(filename)
    0212     if any(mode=='d')
    0213         filename=fullfile(v_voicebox('dir_data'),filename);
    0214     end
    0215     ny=nyd;
    0216     if isempty(findstr(filename,'.'))
    0217         filename=[filename,'.wav'];
    0218     end
    0219     fid=fopen(filename,'wb+','l');
    0220     if fid == -1
    0221         error('Can''t open %s for output',filename);
    0222     end
    0223     info(1)=fid;
    0224     fwrite(fid,'RIFF','uchar');  % main RIFF header
    0225     fwrite(fid,wavlen(wavtype)+2*ceil(ny/2),'ulong');  %
    0226     fwrite(fid,'WAVEfmt ','uchar');   % write "WAVE" ID and "fmt" chunk
    0227     fwrite(fid,[fmtlen(wavtype) 0 wavfmt nc],'ushort'); % chunk size, format code, number of channels
    0228     fwrite(fid,[fs fs*ncy],'ulong');        % sample rate, bytes per sec
    0229     switch wavtype
    0230         case 1
    0231             fwrite(fid,[ncy info(7)],'ushort');     % block size, bits-per-sample
    0232         case 2
    0233             fwrite(fid,[ncy info(7)],'ushort');     % block size, bits-per-sample
    0234             fwrite(fid,0,'ushort');     % size of the extension=0
    0235         case 3
    0236             fwrite(fid,[ncy 8*info(6)],'ushort');     % block size, bits-per-sample (aways a multiple of 8)
    0237             fwrite(fid,[22 info(7)],'ushort');     % size of the extension=22, valid bits
    0238             fwrite(fid,[mask info(8)],'ulong');        % speaker position mask, encoding format
    0239             fwrite(fid,[0 16 128 43520 14336 29083],'ushort');                % GUID
    0240     end
    0241     if factlen
    0242         fwrite(fid,'fact','uchar');   % fact chunk header
    0243         fwrite(fid,[4 n],'ulong');       % length in bytes + number of samples
    0244     end
    0245     if instlen
    0246         fwrite(fid,'inst','uchar');   % fact chunk header
    0247         fwrite(fid,instlen-8,'ulong');       % length in bytes
    0248         fwrite(fid,zeros(1,instlen-8),'uchar');   % inst data (zero for now)
    0249     end
    0250     fwrite(fid,'data','uchar');   % data header
    0251     fwrite(fid,ny,'ulong');       % data length in bytes
    0252     nskip=0;                        % over-ride any nskip argument
    0253     info(3)=8+wavlen(wavtype);      % length of all header information
    0254     info(4)=n;                      % number of samples (per channel)
    0255     info(2)=n;                      % current file position (in samples)
    0256     info(10)=rand(1);                       % seed for dither generation
    0257 else
    0258     info=filename;
    0259     fid=info(1);
    0260     fseek(fid,0,1); % go to end of file
    0261     if nargin<5 || nskip<0
    0262         nskip=info(2);                      % use previous high water mark
    0263     end
    0264     info(2)=n+nskip;                          % file position following this write operation (in samples)
    0265     ny=nyd+nskip*ncy;                       % file position following this write operation (in bytes following header)
    0266     if n && (info(2)>info(4))               % update high water mark
    0267         if ~info(4)                           % if no data written previously
    0268             fseek(fid,22,-1); fwrite(fid,nc,'ushort');   % update number of channels
    0269             fseek(fid,28,-1); fwrite(fid,fs*ncy,'ulong'); % update bytes/second
    0270             fwrite(fid,ncy,'ushort'); % update bytes/sample
    0271         end
    0272         fseek(fid,4,-1); fwrite(fid,wavlen(wavtype)+2*ceil(ny/2),'ulong'); % update RIFF length
    0273         if factlen
    0274             fseek(fid,wavlen(wavtype)-4-instlen,-1); fwrite(fid,n,'ulong');  % update FACT number of samples
    0275         end
    0276         fseek(fid,4+wavlen(wavtype),-1); fwrite(fid,ny,'ulong');  % update DATA length
    0277         info(4)=info(2);
    0278     end
    0279 end
    0280 info(5)=nc;
    0281 if n                            % if there are any data values
    0282     if sc~='r'                  % 'r' = no scaling
    0283         if sc=='s'              % 's' = scale to peak signal
    0284             if nargin>=7 && ~isempty(mad) && mad>0
    0285                 pd=mad;
    0286             else
    0287                 pd=max(abs(d(:)));
    0288                 pd=pd+(pd==0);      % scale to 1 if data is all zero
    0289             end
    0290         elseif sc=='p'          % 'p' = scale to +-1 = full scale
    0291             pd=1;
    0292         else                    % 'q' = scale to 0dBm
    0293             if info(8)==7       % mu-law
    0294                 pd=2.03761563;
    0295             else                % A-law or anything else
    0296                 pd=2.03033976;
    0297             end
    0298         end
    0299         if instlen
    0300             info(11)=min(max(ceil(20*log10(pd)),-128),127);
    0301             d=pk*10^(-0.05*info(11))*d;
    0302             if fseek(fid,0,-1)  % MATLAB V6.5 fails if this is omitted
    0303                 error('Cannot rewind file');
    0304             end
    0305             if fseek(fid,info(3)-instlen+2,-1)
    0306                 error('Cannot seek to INST chunk gain byte');
    0307             end
    0308             fwrite(fid,info(11),'schar');   % write the INST gain in dB
    0309         else
    0310             d=pk/pd*d;
    0311         end
    0312     end
    0313     if fseek(fid,0,-1)  % MATLAB V6.5 fails if this is omitted
    0314         error('Cannot rewind file');
    0315     end
    0316     if fseek(fid,info(3)+nskip*nc*info(6),-1)
    0317         error('Cannot seek to byte %d in output file',info(3)+nskip*nc*info(6));
    0318     end
    0319     if info(8)==3 % floating point
    0320         if info(6)==4
    0321             fwrite(fid,d,'float32');
    0322         else
    0323             fwrite(fid,d,'float64');
    0324         end
    0325     else                        % integer data
    0326         if info(8)<6            % PCM
    0327             if di=='n'
    0328                 d=round(d);
    0329             else
    0330                 [d,info(10)]=v_ditherq(d,di,info(10));
    0331             end
    0332             d=min(max(d,lo),hi)*pow2(1,8*info(6)-info(7));       % clip data and shift to most significant bits
    0333         else                    % mu or A law
    0334             z=0;
    0335             if info(8) < 7
    0336                 d=v_lin2pcma(d,213,1);
    0337             else
    0338                 d=v_lin2pcmu(d,1);
    0339             end
    0340         end
    0341         if info(6)<3
    0342             if info(6)<2
    0343                 fwrite(fid,d+z,'uchar');
    0344             else
    0345                 fwrite(fid,d,'short');
    0346             end
    0347         else
    0348             if info(6)<4
    0349                 d=d(:)';
    0350                 d2=floor(d/65536);
    0351                 d=d-65536*d2;
    0352                 fwrite(fid,[rem(d,256); floor(d/256); d2+256*(d2<0)],'uchar');
    0353             else
    0354                 fwrite(fid,d,'long');
    0355             end
    0356         end
    0357         if rem(ny,2) % pad to an even number of bytes
    0358             fwrite(fid,0,'uchar');
    0359         end
    0360     end
    0361 end
    0362 if all(mode~='f')
    0363     fclose(fid);
    0364 end
    0365 if nargout
    0366     fidx=info;
    0367 end