1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "audio/wav_sound_file.hpp"
18
19#include <config.h>
20
21#include <string.h>
22#include <stdint.h>
23#include <assert.h>
24#include <algorithm>
25
26#include "audio/sound_error.hpp"
27#include "util/log.hpp"
28
29static inline uint32_t read32LE(PHYSFS_file* file)
30{
31 uint32_t result;
32 if (PHYSFS_readULE32(file, &result) == 0)
33 throw SoundError("file too short");
34
35 return result;
36}
37
38static inline uint16_t read16LE(PHYSFS_file* file)
39{
40 uint16_t result;
41 if (PHYSFS_readULE16(file, &result) == 0)
42 throw SoundError("file too short");
43
44 return result;
45}
46
47WavSoundFile::WavSoundFile(PHYSFS_file* file_) :
48 m_file(file_),
49 m_datastart()
50{
51 assert(m_file);
52 char magic[4];
53 if (PHYSFS_readBytes(m_file, magic, sizeof(magic)) < static_cast<std::make_signed<size_t>::type>(sizeof(magic)))
54 throw SoundError("Couldn't read file magic (not a wave file)");
55 if (strncmp(magic, "RIFF", 4) != 0) {
56 log_debug << "MAGIC: " << magic << std::endl;
57 throw SoundError("file is not a RIFF wav file");
58 }
59
60 uint32_t wavelen = read32LE(m_file);
61 (void) wavelen;
62
63 if (PHYSFS_readBytes(m_file, magic, sizeof(magic)) < static_cast<std::make_signed<size_t>::type>(sizeof(magic)))
64 throw SoundError("Couldn't read chunk header (not a wav file?)");
65 if (strncmp(magic, "WAVE", 4) != 0)
66 throw SoundError("file is not a valid RIFF/WAVE file");
67
68 char chunkmagic[4];
69 uint32_t chunklen;
70
71 // search audio data format chunk
72 do {
73 if (PHYSFS_readBytes(m_file, chunkmagic, sizeof(chunkmagic)) < static_cast<std::make_signed<size_t>::type>(sizeof(chunkmagic)))
74 throw SoundError("EOF while searching format chunk");
75 chunklen = read32LE(m_file);
76
77 if (strncmp(chunkmagic, "fmt ", 4) == 0)
78 break;
79
80 if (strncmp(chunkmagic, "fact", 4) == 0
81 || strncmp(chunkmagic, "LIST", 4) == 0) {
82 // skip chunk
83 if (PHYSFS_seek(m_file, PHYSFS_tell(m_file) + chunklen) == 0)
84 throw SoundError("EOF while searching fmt chunk");
85 } else {
86 throw SoundError("complex WAVE files not supported");
87 }
88 } while(true);
89
90 if (chunklen < 16)
91 throw SoundError("Format chunk too short");
92
93 // parse format
94 uint16_t encoding = read16LE(m_file);
95 if (encoding != 1)
96 throw SoundError("only PCM encoding supported");
97 m_channels = read16LE(m_file);
98 m_rate = read32LE(m_file);
99 uint32_t byterate = read32LE(m_file);
100 (void) byterate;
101 uint16_t blockalign = read16LE(m_file);
102 (void) blockalign;
103 m_bits_per_sample = read16LE(m_file);
104
105 if (chunklen > 16) {
106 if (PHYSFS_seek(m_file, PHYSFS_tell(m_file) + (chunklen-16)) == 0)
107 throw SoundError("EOF while reading rest of format chunk");
108 }
109
110 // set file offset to DATA chunk data
111 do {
112 if (PHYSFS_readBytes(m_file, chunkmagic, sizeof(chunkmagic)) < static_cast<std::make_signed<size_t>::type>(sizeof(chunkmagic)))
113 throw SoundError("EOF while searching data chunk");
114 chunklen = read32LE(m_file);
115
116 if (strncmp(chunkmagic, "data", 4) == 0)
117 break;
118
119 // skip chunk
120 if (PHYSFS_seek(m_file, PHYSFS_tell(m_file) + chunklen) == 0)
121 throw SoundError("EOF while searching fmt chunk");
122 } while(true);
123
124 m_datastart = PHYSFS_tell(m_file);
125 m_size = static_cast<size_t>(chunklen);
126}
127
128WavSoundFile::~WavSoundFile()
129{
130 PHYSFS_close(m_file);
131}
132
133void
134WavSoundFile::reset()
135{
136 if (PHYSFS_seek(m_file, m_datastart) == 0)
137 throw SoundError("Couldn't seek to data start");
138}
139
140size_t
141WavSoundFile::read(void* buffer, size_t buffer_size)
142{
143 PHYSFS_sint64 end = m_datastart + m_size;
144 PHYSFS_sint64 cur = PHYSFS_tell(m_file);
145 if (cur >= end)
146 return 0;
147
148 size_t readsize = std::min(static_cast<size_t> (end - cur), buffer_size);
149 if (PHYSFS_readBytes(m_file, buffer, readsize) != static_cast<std::make_signed<size_t>::type>(readsize))
150 throw SoundError("read error while reading samples");
151
152#ifdef WORDS_BIGENDIAN
153 if (bits_per_sample != 16)
154 return readsize;
155 char *tmp = (char*)buffer;
156
157 for (size_t i = 0; i < readsize / 2; i++)
158 {
159 char c = tmp[2*i];
160 tmp[2*i] = tmp[2*i+1];
161 tmp[2*i+1] = c;
162 }
163
164 *(char *)buffer = *tmp;
165#endif
166
167 return readsize;
168}
169
170/* EOF */
171