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