I haven't got source for Allegro 4.2.0 so I can't check if the load_wav() function has been perfected and not just skipping a bunch of bytes.
Anyways, someone I know (who codes a lot of Assembler but does a lot of C nowadays) has given me the actual WAV header format.
00 DWORD "RIFF"
04 DWORD Filesize-8
08 DWORD "WAVE"
0C DWORD "fmt "
10 DWORD 0x10
14 WORD 1
16 WORD NumChannels
18 DWORD SampleRate
1C DWORD ByteRate=SampleRate*NumChannels*BitsPerSample/8
20 WORD BlockAlign=NumChannels*BitsPerSample/8
22 WORD BitsPerSample
24 DWORD "data"
28 DWORD DataSize=FileSize-0x2C
2C Data goes here
I reckon I could make something quite quickly. Here's a header...
typedef struct WaveFile { char riffhead[4]; // Set this to "RIFF" unsigned long fsm8; // Set this to sample>len + 36 (24h) char wavefmthead[8]; // Set this to "WAVEfmt " unsigned long fmtheaderlen; // Set this to 16 (10h) unsigned short codecused; // Set this to 1 (PCM unencoded format) unsigned short channels; // Set this to sample->stereo + 1 unsigned long samplerate; // Set this to sample->frequency unsigned long byterate; // Set this to samplerate*channels*bits/8 unsigned short blockalign; // Set this to channels*bits/8 unsigned short bits; // Set this to sample->bits char datahead[4]; // Set this to "data" unsigned long datasize; // Set this to sample->len void *data; // Set this to sample->data } WaveFile;
As you see, I'm really anal when it comes to signed and unsigned numbers.
Why would load_wav need to read data it doesn't use?
Though if you want to handle any riff format, give my libriff a shot. It might not handle aiff/iff files correctly, as for some reason the one file I tried stored the offsets and lengths in the wrong endian layout.
example:
1 | #include <stdio.h> |
2 | #include <errno.h> |
3 | #include <string.h> |
4 | #include "libriff.h" |
5 | |
6 | #include "debug.h" |
7 | |
8 | size_t stdio_read(void *f, size_t size, void *ptr) |
9 | { |
10 | return fread(ptr, 1, size, (FILE *)f); |
11 | } |
12 | |
13 | size_t stdio_write(void *f, size_t size, void *ptr) |
14 | { |
15 | return fwrite(ptr, 1, size, (FILE *)f); |
16 | } |
17 | |
18 | int32_t stdio_seek(void *f, off_t off, u_int32_t whence) |
19 | { |
20 | return fseek((FILE *)f, off, whence); |
21 | } |
22 | |
23 | off_t stdio_tell(void *f) |
24 | { |
25 | return ftell((FILE *)f); |
26 | } |
27 | |
28 | int32_t stdio_error(void *f) |
29 | { |
30 | return ferror((FILE *)f); |
31 | } |
32 | |
33 | int32_t stdio_eof(void *f) |
34 | { |
35 | return feof((FILE *)f); |
36 | } |
37 | |
38 | riff_callbacks_t stdio_vtable = { |
39 | &stdio_read, |
40 | &stdio_write, |
41 | &stdio_seek, |
42 | &stdio_tell, |
43 | &stdio_error, |
44 | &stdio_eof, |
45 | }; |
46 | |
47 | int copy_riff(riff_file_t *src, FILE *dest) |
48 | { |
49 | riff_file_t *riff = NULL; |
50 | riff_chunk_t *schunk = NULL, *dchunk = NULL; |
51 | unsigned char data[1024]; |
52 | |
53 | Assert(src); |
54 | |
55 | riff = riff_file_copy(src); |
56 | if(!riff) |
57 | return -1; |
58 | |
59 | schunk = riff_file_chunk(src); |
60 | dchunk = riff_file_chunk(riff); |
61 | |
62 | riff_file_data_set(riff, dest); |
63 | if(riff_file_save(riff) != 0) { |
64 | riff_file_destroy(riff); |
65 | warn("failed to save dest riff headers"); |
66 | return -1; |
67 | } |
68 | |
69 | while(schunk) { |
70 | u_int32_t i = 0; |
71 | if(riff_chunk_children(schunk)) { |
72 | schunk = riff_chunk_children(schunk); |
73 | dchunk = riff_chunk_children(dchunk); |
74 | continue; |
75 | } |
76 | |
77 | while(i < riff_chunk_size_get(schunk)) { |
78 | u_int32_t r = riff_chunk_read(src, schunk, 1024, data); |
79 | printf("read %i bytes\n", r); |
80 | riff_chunk_write(riff, dchunk, r, data); |
81 | |
82 | i += r; |
83 | } |
84 | |
85 | if(riff_chunk_next(schunk)) { |
86 | schunk = riff_chunk_next(schunk); |
87 | dchunk = riff_chunk_next(dchunk); |
88 | } else if(riff_chunk_parent(schunk) && riff_chunk_next(riff_chunk_parent(schunk))) { |
89 | schunk = riff_chunk_next(riff_chunk_parent(schunk)); |
90 | dchunk = riff_chunk_next(riff_chunk_parent(dchunk)); |
91 | } else |
92 | schunk = NULL; |
93 | } |
94 | |
95 | riff_file_destroy(riff); |
96 | |
97 | return 0; |
98 | } |
99 | |
100 | int main(int argc, char **argv) |
101 | { |
102 | riff_file_t *riff = NULL; |
103 | |
104 | FILE *src = NULL; |
105 | FILE *dest = NULL; |
106 | |
107 | if(argc < 3) |
108 | error("Usage: %s <src> <dest>", argv[0]); |
109 | |
110 | src = fopen(argv[1], "rb"); |
111 | if(!src) |
112 | error("Failed to open file '%s': %s", argv[1], strerror(errno)); |
113 | |
114 | dest = fopen(argv[2], "wb"); |
115 | if(!dest) |
116 | error("Failed to open file '%s': %s", argv[2], strerror(errno)); |
117 | |
118 | riff = riff_file_create(&stdio_vtable, src); |
119 | if(!riff) { // doesn't currently return if creation failed.. may change, so error checking is a good thing... |
120 | fclose(src); |
121 | fclose(dest); |
122 | error("Failed to create riff data"); |
123 | } |
124 | |
125 | if(!riff_file_load(riff)) { |
126 | riff_file_destroy(riff); |
127 | fclose(src); |
128 | fclose(dest); |
129 | error("Failed to load riff file"); |
130 | } |
131 | |
132 | if(copy_riff(riff, dest) != 0) { |
133 | warn("failed durring copy..."); |
134 | } |
135 | |
136 | fclose(dest); |
137 | |
138 | riff_file_destroy(riff); |
139 | fclose(src); |
140 | |
141 | return 0; |
142 | } |
This is a somewhat useless example, but hey, it shows how to read and write a riff file, so I thought it was better than just showing the others. it provides a "FILE" like interface to the individual chunks in a given riff file, reading, writing, seeking, and you can't seek, read or write past the end or before the start of a chunk, so its a little safe there.
Though I have to admit, the riff_file_copy function might need to be renamed, it only coppies the metadata, ie: the structs, and chunk structs, and not the actual data stored in the chunks.
Well most of the players load these values to perform checks.
A SAVE_WAV() function MUST save these values correctly anyways.
Let's see what I can whip up... Untested sorry. If it works, then I'm just the man.
1 | #include <stdio.h> |
2 | |
3 | #include <allegro.h> |
4 | typedef struct WaveFile { |
5 | char riffhead[4]; // Set this to "RIFF" |
6 | unsigned long fsm8; // Set this to sample>len + 36 (24h) |
7 | char wavefmthead[8]; // Set this to "WAVEfmt " |
8 | unsigned long fmtheaderlen; // Set this to 16 (10h) |
9 | unsigned short codecused; // Set this to 1 (PCM unencoded format) |
10 | unsigned short channels; // Set this to sample->stereo + 1 |
11 | unsigned long samplerate; // Set this to sample->frequency |
12 | unsigned long byterate; // Set this to samplerate*channels*bits/8 |
13 | unsigned short blockalign; // Set this to channels*bits/8 |
14 | unsigned short bits; // Set this to sample->bits |
15 | char datahead[4]; // Set this to "data" |
16 | unsigned long datasize; // Set this to sample->len |
17 | void *data; // Set this to sample->data |
18 | } WaveFile; |
19 | |
20 | int save_wav(const char *filename, SAMPLE *spl){ |
21 | PACKFILE *fp; |
22 | WaveFile wf; |
23 | |
24 | if(pack_fopen(filename,"w")){ |
25 | pack_fwrite("RIFF",4,fp); |
26 | pack_iputl(spl->len*(spl->stereo+1)*(spl->bits/8)+36,fp); |
27 | pack_fwrite("WAVEfmt ",8,fp); |
28 | pack_iputl(10,fp); |
29 | pack_iputw(1,fp); |
30 | pack_iputw(spl->stereo+1,fp); |
31 | pack_iputl(spl->freq,fp); |
32 | pack_iputl(spl->freq*(spl->stereo+1)*(spl->bits/8),fp); |
33 | pack_iputw((spl->stereo+1)*(spl->bits/8),fp); |
34 | pack_iputw((spl->stereo+1)*spl->bits,fp); |
35 | pack_fwrite("data",4,fp); |
36 | pack_iputl(spl->len*(spl->stereo+1)*(spl->bits/8),fp); |
37 | pack_fwrite(spl->data,spl->len*(spl->stereo+1)*(spl->bits/8),fp); |
38 | return 0; |
39 | } |
40 | return 1; |
41 | } |
Don't think allegro really needs to save waves but a simple save_wav might be cool for utilities or debugging. If you're looking for a lib, I've written several libraries that can read/write PCM format wave files and the most recent ('My_Audio' library) can handle all bit depths of PCM .wav (8 to 32 bit integer, float type3, double type3, and the non-standard CoolEdit 16.8) and all integer format .aif files. (can also read and write .mid files) It handles all unsupported chunks in a way that allows them to be saved back to the file and supports a lot of chunks common to the pro-audio scene including the 'smpl' chunk (sampler info), 'cue ' and 'plst' and even the 'bext' chunk (EBU extensions). It's not on the net yet but will be soon. You might want to check out libsndfile too. Libsndfile handles all kinds of audio file formats.
The best way would probably be an addon, which would use the save_sample and register_sample_file_type functions. I also should have a very simple function lying around somewhere which can be registered for saving to wav.
I also should have a very simple function lying around somewhere
So do not let it lying around, Let it be a part of allegro ;-)