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