* Bug 15978 fixed [sound]: wavwrite (en) page. savewave: + mclose() < errors
[scilab.git] / scilab / modules / sound / macros / savewave.sci
1 // Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
2 // Copyright (C) 2008 - DIGITEO
3 // Copyright (C) 2012 - 2016 - Scilab Enterprises
4 // Copyright (C) 2018 - St├ęphane MOTTELET
5 // Copyright (C) 2019 - Samuel GOUGEON
6 //
7 // This file is hereby licensed under the terms of the GNU GPL v2.0,
8 // pursuant to article 5.3.4 of the CeCILL v.2.1.
9 // This file was originally licensed under the terms of the CeCILL v2.1,
10 // and continues to be available under such terms.
11 // For more information, see the COPYING file which you should have received
12 // along with this program.
13
14 // =============================================================================
15 // WAVE Audio File Format
16 // https://www.loc.gov/preservation/digital/formats/fdd/fdd000001.shtml
17 // =============================================================================
18 function savewave(filename,x,rate,nbits)
19
20     // create_wavheader, write_ckinfo, write_wavedat functions only defined in savewave.sci
21
22     function [x,fmt] = create_wavheader(x,rate,nbits)
23
24         [channels, samples] = size(x);
25         if (samples == 1) then
26             x = x';
27             [channels, samples] = size(x);
28         end
29
30         // Clip data to normalized range [-1,+1]:
31         i = matrix(find(abs(x)>1),1,-1);
32         if ~(i == []) then
33             // Data clipped during write to file.
34             x(i) = sign(x(i));
35         end
36
37         // # bytes per sample to write
38         bytes_per_sample = ceil(nbits/8);
39         total_samples = samples * channels;
40         total_bytes = total_samples * bytes_per_sample;
41
42         riff_cksize = 36 + total_bytes;
43         // Don't include 'RIFF' or its size field
44         fmt_cksize = 16;
45         // Don't include 'fmt' or its size field
46         data_cksize = total_bytes;
47         // Don't include 'data' or its size field
48
49         // Determine pad bytes:
50         data_pad = data_cksize - fix(data_cksize./2).*2;
51         riff_cksize = riff_cksize + data_pad;
52         // + fmt_pad, always 0
53
54         ck= tlist(["ck","fid","Size","ID"]) ;
55         // Write RIFF chunk:
56         ck("fid") = fid;
57         ck("Size") = riff_cksize;
58         ck("ID") = "RIFF";
59         write_ckinfo(ck);
60
61         // Write WAVE:
62         ck("ID") = "WAVE";
63         write_ckinfo(ck,1);
64
65         // Write <fmt-ck>:
66         ck("ID") = "fmt ";
67         ck("Size") = fmt_cksize;
68         write_ckinfo(ck);
69         // Write <wave-format>:
70         fmt = tlist(["fmt","wFormatTag","nChannels","nSamplesPerSec","nAvgBytesPerSec","nBlockAlign","nBitsPerSample"]);
71         fmt("wFormatTag") = 1;
72         // Data encoding format = PCM
73         fmt("nChannels") = channels;
74         // Number of channels
75         fmt("nSamplesPerSec") = rate;
76         // Samples per second
77         fmt("nAvgBytesPerSec") = channels * bytes_per_sample * rate;
78         // Avg transfer rate
79         fmt("nBlockAlign") = channels * bytes_per_sample;
80         // Block alignment
81         fmt("nBitsPerSample") = nbits;
82
83         // standard <PCM-format-specific> info
84         status = write_wavefmt(fid,fmt);
85
86         // Write <data-ck>:
87         ck("ID") = "data";
88         ck("Size") = data_cksize;
89         write_ckinfo(ck);
90
91     endfunction
92     // =============================================================================
93     function write_ckinfo(ck,sflg)
94         [nargout,nargin] = argn(0)
95         // WRITE_CKINFO: Writes next RIFF chunk, but not the chunk data.
96         //   If optional sflg is set to nonzero, write SUBchunk info instead.
97         //   Expects an open FID pointing to first byte of chunk header,
98         //   and a chunk structure.
99         //   ck.fid, ck.ID, ck.Size, ck.Data
100         if length(ck("ID"))<>4 then
101             mclose(ck("fid"));
102             error(msprintf(gettext("%s: Wrong size for input argument #%d.\n"),"write_ckinfo",1));
103         end
104
105         mput(ascii(ck("ID")),"c",ck("fid"));
106
107         // Error condition
108         if (nargin == 1) then
109             // Write chunk size (skip if subchunk):
110             mput(ck("Size"),"ui",ck("fid"));
111         end
112     endfunction
113     // =============================================================================
114     function [status] = write_wavedat(fid,fmt,data)
115         status = [];
116         // WRITE_WAVEDAT: Write WAVE data chunk
117         //   Assumes fid points to the wave-data chunk
118         //   Requires <wave-format> structure to be passed.
119
120         status = 0;
121
122         if fmt("wFormatTag") == 1 then
123             // PCM Format:
124             // Determine # bytes/sample - format requires rounding
125             //  to next integer number of bytes:
126             BytesPerSample = ceil(fmt("nBitsPerSample")/8);
127
128             select BytesPerSample
129             case 1 then
130                 dtype = "uc"; // unsigned 8-bit
131                 // Scale data according to bits/samples: [-1,+1] -> [0,255]
132                 data = round((data+1)*255/2);
133             case 2 then
134                 dtype = "s";
135                 // signed 16-bit
136                 // Scale data according to bits/samples: [-1,+1] -> [-32768,+32767]
137                 data = round((data+1)*65535/2)-32768;
138             case 3 then
139                 dtype="c"
140                 // signed 24-bit
141                 // Scale data according to bits/samples: [-1,+1] -> [-8 388 608,+8 388 607]
142                 data = round((data+1)*(2^24-1)/2)-(2^23);
143             case 4 then
144                 dtype="i"
145                 // signed 32-bit
146                 // Scale data according to bits/samples: [-1,+1] -> [-2 147 483 648,+2 147 483 647]
147                 data = round((data+1)*(2^32-1)/2)-(2^31);
148
149             else
150                 mclose(fid)
151                 error(msprintf(gettext("%s: An error occurred: %s\n"),"savewave",gettext("only 8/16/24/32 bits for the encoding.")));
152             end
153
154             // Write data, one row at a time (one sample from each channel):
155             [channels,samples] = size(data);
156             total_samples = samples * channels;
157
158             //24-bits needs special treatment
159             if (BytesPerSample == 3) then
160                 oct3 = (floor((data)/(2^16)));//msb
161                 oct2 = (floor((data-(oct3*2^16))/(2^8)));
162                 oct1 = (floor(data-(oct3*2^16)-(oct2*2^8)));//lsb
163                 data_line = zeros(3*total_samples,1);
164                 bs=3*channels;
165                 for ch=1:channels
166                     data_line(3*ch-2:bs:$) = oct1(ch,:)';
167                     data_line(3*ch-1:bs:$) = oct2(ch,:)';
168                     data_line(3*ch:bs:$)   = oct3(ch,:)';
169                 end
170                 data_line = data_line';
171             else
172                 data_line = data;
173             end
174
175             try
176                 mput(data_line,dtype,fid);
177             catch
178                 status = -1;
179                 return
180             end
181             // Error condition
182             // Determine if a pad-byte is appended to data chunk:
183             %v2_1$1 = total_samples * BytesPerSample;
184             if ( %v2_1$1 - fix(%v2_1$1./2).*2 ) then
185                 mput(0,"uc",fid);
186             end
187         else
188             // Unknown wave-format for data.
189             mclose(fid)
190             error(msprintf(gettext("%s: An error occurred: %s\n"),"write_wavedat",gettext("Unknown data format.")));
191         end
192     endfunction
193     // =============================================================================
194     function [status]=write_wavefmt(fid,fmt)
195         status = 0;
196         // WRITE_WAVEFMT: Write WAVE format chunk.
197         //   Assumes fid points to the wave-format subchunk.
198         //   Requires chunk structure to be passed, indicating
199         //   the length of the chunk.
200
201         // Create <wave-format> data:
202
203         mput(fmt("wFormatTag"),"us",fid);
204         mput(fmt("nChannels"),"us",fid);
205         mput(fmt("nSamplesPerSec"),"ui",fid);
206         mput(fmt("nAvgBytesPerSec"),"ui",fid);
207         mput(fmt("nBlockAlign"),"us",fid);
208
209         // Write format-specific info:
210         if ( fmt("wFormatTag") == 1 ) then
211             // Write standard <PCM-format-specific> info:
212             mput(fmt("nBitsPerSample"),"us",fid)
213         else
214             mclose(fid)
215             error("Unknown data format.");
216         end
217     endfunction
218     // =============================================================================
219
220     // savewave main
221     lhs = argn(1);
222     rhs = argn(2);
223
224     if (rhs < 4) then
225         nbits = 16;
226     end;
227
228     if (rhs < 3) then
229         rate = 22050;
230     end;
231
232     if ~(type(filename) == 10) then
233         error(msprintf(gettext("%s: Wrong type for input argument #%d: string expected.\n" ),"savewave",1));
234     end
235
236     if strindex(filename,".")==[] then
237         filename = filename+".wav";
238     end
239
240     [fid,%v] = mopen(filename,"wb",1);
241
242     if fid==-1 | %v < 0 then
243         error(msprintf(gettext("%s: Cannot open file %s.\n"),"savewave",filename));
244     end
245
246     [x,fmt] = create_wavheader(x,rate,nbits);
247     status = write_wavedat(fid,fmt,x);
248     mclose(fid);
249
250 endfunction
251 // =============================================================================