1 // license:BSD-3-Clause
2 // copyright-holders:Nathan Woods
3 /*********************************************************************
4
5 wavfile.c
6
7 Format code for wave (*.wav) files
8
9 *********************************************************************/
10
11 #include "wavfile.h"
12
13 #include <cstdio>
14 #include <cassert>
15
16 static const char magic1[4] = { 'R', 'I', 'F', 'F' };
17 static const char magic2[4] = { 'W', 'A', 'V', 'E' };
18 static const char format_tag_id[4] = { 'f', 'm', 't', ' ' };
19 static const char data_tag_id[4] = { 'd', 'a', 't', 'a' };
20
21 #define WAV_FORMAT_PCM 1
22
23
24
get_leuint32(const void * ptr)25 static uint32_t get_leuint32(const void *ptr)
26 {
27 uint32_t value;
28 memcpy(&value, ptr, sizeof(value));
29 return little_endianize_int32(value);
30 }
31
32
33
get_leuint16(const void * ptr)34 static uint16_t get_leuint16(const void *ptr)
35 {
36 uint16_t value;
37 memcpy(&value, ptr, sizeof(value));
38 return little_endianize_int16(value);
39 }
40
41
42
put_leuint32(void * ptr,uint32_t value)43 static void put_leuint32(void *ptr, uint32_t value)
44 {
45 value = little_endianize_int32(value);
46 memcpy(ptr, &value, sizeof(value));
47 }
48
49
50
put_leuint16(void * ptr,uint16_t value)51 static void put_leuint16(void *ptr, uint16_t value)
52 {
53 value = little_endianize_int16(value);
54 memcpy(ptr, &value, sizeof(value));
55 }
56
57
58
wavfile_process(cassette_image * cassette,cassette_image::Options * opts,bool read_waveform)59 static cassette_image::error wavfile_process(cassette_image *cassette, cassette_image::Options *opts,
60 bool read_waveform)
61 {
62 uint8_t file_header[12];
63 uint8_t tag_header[8];
64 uint8_t format_tag[16];
65 uint32_t stated_size;
66 uint64_t file_size;
67 uint32_t tag_size;
68 uint32_t tag_samples;
69 uint64_t offset;
70 bool format_specified = false;
71
72 uint16_t format_type = 0;
73 uint32_t bytes_per_second = 0;
74 // uint16_t block_align = 0;
75 int waveform_flags = 0;
76
77 /* read header */
78 cassette->image_read(file_header, 0, sizeof(file_header));
79 offset = sizeof(file_header);
80
81 /* check magic numbers */
82 if (memcmp(&file_header[0], magic1, 4))
83 return cassette_image::error::INVALID_IMAGE;
84 if (memcmp(&file_header[8], magic2, 4))
85 return cassette_image::error::INVALID_IMAGE;
86
87 /* read and sanity check size */
88 stated_size = get_leuint32(&file_header[4]) + 8;
89 file_size = cassette->image_size();
90 if (stated_size > file_size)
91 stated_size = (uint32_t) file_size;
92
93 while(offset < stated_size)
94 {
95 cassette->image_read(tag_header, offset, sizeof(tag_header));
96 tag_size = get_leuint32(&tag_header[4]);
97 offset += sizeof(tag_header);
98
99 if (!memcmp(tag_header, format_tag_id, 4))
100 {
101 /* format tag */
102 if (format_specified || (tag_size < sizeof(format_tag)))
103 return cassette_image::error::INVALID_IMAGE;
104 format_specified = true;
105
106 cassette->image_read(format_tag, offset, sizeof(format_tag));
107
108 format_type = get_leuint16(&format_tag[0]);
109 opts->channels = get_leuint16(&format_tag[2]);
110 opts->sample_frequency = get_leuint32(&format_tag[4]);
111 bytes_per_second = get_leuint32(&format_tag[8]);
112 // block_align = get_leuint16(&format_tag[12]);
113 opts->bits_per_sample = get_leuint16(&format_tag[14]);
114
115 if (format_type != WAV_FORMAT_PCM)
116 return cassette_image::error::INVALID_IMAGE;
117 if (opts->sample_frequency * opts->bits_per_sample * opts->channels / 8 != bytes_per_second)
118 return cassette_image::error::INVALID_IMAGE;
119
120 switch(opts->bits_per_sample)
121 {
122 case 8:
123 waveform_flags = cassette_image::WAVEFORM_8BIT | cassette_image::WAVEFORM_UNSIGNED; // 8-bits wav are stored unsigned
124 break;
125 case 16:
126 waveform_flags = cassette_image::WAVEFORM_16BITLE;
127 break;
128 case 32:
129 waveform_flags = cassette_image::WAVEFORM_32BITLE;
130 break;
131 default:
132 return cassette_image::error::INVALID_IMAGE;
133 }
134 }
135 else if (!memcmp(tag_header, data_tag_id, 4))
136 {
137 /* data tag */
138 if (!format_specified)
139 return cassette_image::error::INVALID_IMAGE;
140
141 if (read_waveform)
142 {
143 tag_samples = tag_size / (opts->bits_per_sample / 8) / opts->channels;
144 cassette->read_samples(opts->channels, 0.0, tag_samples / ((double) opts->sample_frequency),
145 tag_samples, offset, waveform_flags);
146 }
147 }
148 else
149 {
150 /* ignore other tags */
151 }
152 offset += tag_size;
153 }
154
155 return cassette_image::error::SUCCESS;
156 }
157
158
159
wavfile_identify(cassette_image * cassette,cassette_image::Options * opts)160 static cassette_image::error wavfile_identify(cassette_image *cassette, cassette_image::Options *opts)
161 {
162 return wavfile_process(cassette, opts, false);
163 }
164
165
166
wavfile_load(cassette_image * cassette)167 static cassette_image::error wavfile_load(cassette_image *cassette)
168 {
169 cassette_image::Options opts;
170 memset(&opts, 0, sizeof(opts));
171 return wavfile_process(cassette, &opts, true);
172 }
173
174
175
wavfile_save(cassette_image * cassette,const cassette_image::Info * info)176 static cassette_image::error wavfile_save(cassette_image *cassette, const cassette_image::Info *info)
177 {
178 cassette_image::error err;
179 uint8_t consolidated_header[12 + 8 + 16 + 8];
180 uint8_t *header = &consolidated_header[0];
181 uint8_t *format_tag_header = &consolidated_header[12];
182 uint8_t *format_tag_data = &consolidated_header[12 + 8];
183 uint8_t *data_tag_header = &consolidated_header[12 + 8 + 16];
184 uint32_t file_size;
185 uint32_t bytes_per_second;
186 uint16_t bits_per_sample;
187 uint32_t data_size;
188 size_t bytes_per_sample = 2;
189 int waveform_flags = cassette_image::WAVEFORM_16BITLE;
190 uint16_t block_align;
191
192 bits_per_sample = (uint16_t) (bytes_per_sample * 8);
193 bytes_per_second = info->sample_frequency * bytes_per_sample * info->channels;
194 data_size = (uint32_t) (info->sample_count * bytes_per_sample * info->channels);
195 file_size = data_size + sizeof(consolidated_header) - 8;
196 block_align = (uint16_t) (bytes_per_sample * info->channels);
197
198 /* set up header */
199 memcpy(&header[0], magic1, 4);
200 memcpy(&header[8], magic2, 4);
201 put_leuint32(&header[4], file_size);
202
203 /* set up format tag */
204 memcpy(&format_tag_header[0], format_tag_id, 4);
205 put_leuint32(&format_tag_header[4], 16);
206 put_leuint16(&format_tag_data[0], WAV_FORMAT_PCM);
207 put_leuint16(&format_tag_data[2], info->channels);
208 put_leuint32(&format_tag_data[4], info->sample_frequency);
209 put_leuint32(&format_tag_data[8], bytes_per_second);
210 put_leuint16(&format_tag_data[12], block_align);
211 put_leuint16(&format_tag_data[14], bits_per_sample);
212
213 /* set up data tag */
214 memcpy(&data_tag_header[0], data_tag_id, 4);
215 put_leuint32(&data_tag_header[4], data_size);
216
217 /* write consolidated header */
218 cassette->image_write(consolidated_header, 0, sizeof(consolidated_header));
219
220 /* write out the actual data */
221 err = cassette->write_samples(info->channels, 0.0, info->sample_count
222 / (double) info->sample_frequency, info->sample_count, sizeof(consolidated_header),
223 waveform_flags);
224 if (err != cassette_image::error::SUCCESS)
225 return err;
226
227 return cassette_image::error::SUCCESS;
228 }
229
230
231
232 const cassette_image::Format cassette_image::wavfile_format =
233 {
234 "wav",
235 wavfile_identify,
236 wavfile_load,
237 wavfile_save
238 };
239
240
241
242 /*********************************************************************
243 wavfile_testload()
244
245 This is a hokey function used to test the cassette wave loading
246 system, specifically to test that when one loads a WAV file image
247 that the resulting info queried will be the same data in the WAV.
248
249 This code has already identified some rounding errors
250 *********************************************************************/
251
252 #ifdef UNUSED_FUNCTION
wavfile_testload(const char * fname)253 void wavfile_testload(const char *fname)
254 {
255 cassette_image *cassette;
256 FILE *f;
257 long offset;
258 int freq, samples, i;
259 int32_t cassamp;
260 int16_t wavsamp;
261
262 f = fopen(fname, "rb");
263 if (!f)
264 return;
265
266 if (cassette_open(f, &stdio_ioprocs, &wavfile_format, cassette_image::FLAG_READONLY, &cassette))
267 {
268 fclose(f);
269 return;
270 }
271
272 offset = 44;
273 freq = 44100;
274 samples = 5667062;
275
276 for (i = 0; i < samples; i++)
277 {
278 cassette_get_sample(cassette, 0, i / (double) freq, 0.0, &cassamp);
279
280 fseek(f, offset + i * 2, SEEK_SET);
281 fread(&wavsamp, 1, 2, f);
282 assert(cassamp == (((uint32_t) wavsamp) << 16));
283 }
284
285 cassette_close(cassette);
286
287 fclose(f);
288 }
289 #endif
290