1 /* ScummVM Tools
2 *
3 * ScummVM Tools is the legal property of its developers, whose
4 * names are too numerous to list here. Please refer to the
5 * COPYRIGHT file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 /* .smp compressor */
23
24 #include <stdlib.h>
25
26 #include "compress.h"
27 #include "common/endian.h"
28
29 #include "compress_tinsel.h"
30
31 // data-format of index-file:
32 // [pointer to data file DWORD] [pointer to data file DWORD] [pointer to data file DWORD]
33 // we use index[0] to signal the engine what data format it's supposed to expect. It may be 'MP3 ', 'OGG ' or 'FLAC'
34
35 // data-format of sample-file:
36 // [sample-length DWORD] [sample-data]
37 // or
38 // [subsamplecount DWORD] [sample-length DWORD] [sample-data]
39 // where subsamplecount has upmost bit 31 set (that's how one differentiates sample-length and subsamplecount)
40 // It seems that data-format 1 is used by DiscWorld 1 and data-format 2 is used by DiscWorld 2. Also DiscWorld 1 uses
41 // raw-data as samples and DiscWorld 2 uses ADPCM 6-bit encoded data as samples. We suppose that we will need to do ADPCM
42 // decoding on all multi-sample data.
43
44 #define TEMP_IDX "compressed.idx"
45 #define TEMP_SMP "compressed.smp"
46 #define TEMP_RAW "tempfile.raw"
47 #define TEMP_ENC "tempfile.enc"
48
CompressTinsel(const std::string & name)49 CompressTinsel::CompressTinsel(const std::string &name) : CompressionTool(name, TOOLTYPE_COMPRESSION) {
50 _supportsProgressBar = true;
51
52 ToolInput input1;
53 input1.format = "*.smp";
54 _inputPaths.push_back(input1);
55
56 ToolInput input2;
57 input2.format = "*.idx";
58 _inputPaths.push_back(input2);
59
60 _shorthelp = "Used to compress Tinsel .smp files.";
61 _helptext = "\nUsage: " + getName() + " [mode-params] [-o outputname] <infile.smp> <infile.idx>\n";
62 }
63
64 /* Converts raw-data sample in input_smp of size SampleSize to requested dataformat and writes to output_smp */
convertTinselRawSample(uint32 sampleSize)65 void CompressTinsel::convertTinselRawSample (uint32 sampleSize) {
66 uint32 copyLeft = 0;
67 uint32 doneRead = 0;
68 char buffer[2048];
69 Common::File curFileHandle;
70
71 print("Assuming DW1 sample being 8-bit raw...");
72
73 Common::removeFile(TEMP_RAW); Common::removeFile(TEMP_ENC);
74 curFileHandle.open(TEMP_RAW, "wb");
75 copyLeft = sampleSize;
76 while (copyLeft > 0) {
77 doneRead = _input_smp.read_noThrow(buffer, copyLeft > sizeof(buffer) ? sizeof(buffer) : copyLeft);
78 if (doneRead <= 0)
79 break;
80 copyLeft -= (int)doneRead;
81 curFileHandle.write(buffer, doneRead);
82 }
83 curFileHandle.close();
84
85 // Encode this raw data...
86 setRawAudioType(true, false, 8); // LE, mono, 8-bit (??)
87 encodeAudio(TEMP_RAW, true, 22050, TEMP_ENC, _format);
88
89 // Append compressed data to output_smp
90 curFileHandle.open(TEMP_ENC, "rb");
91 curFileHandle.seek(0, SEEK_END);
92 copyLeft = curFileHandle.pos();
93 curFileHandle.seek(0, SEEK_SET);
94 // Write size of compressed data
95 _output_smp.writeUint32LE(copyLeft);
96 // Write actual data
97 while (copyLeft > 0) {
98 doneRead = curFileHandle.read_noThrow(buffer, copyLeft > sizeof(buffer) ? sizeof(buffer) : copyLeft);
99 if (doneRead <= 0)
100 break;
101 copyLeft -= (int)doneRead;
102 _output_smp.write(buffer, doneRead);
103 }
104 }
105
106 static const double TinselFilterTable[4][2] = {
107 {0, 0 },
108 {0.9375, 0},
109 {1.796875, -0.8125},
110 {1.53125, -0.859375}
111 };
112
CLIP(T v,T amin,T amax)113 template<typename T> inline T CLIP (T v, T amin, T amax) {
114 if (v < amin)
115 return amin;
116 else if (v > amax)
117 return amax;
118 else
119 return v;
120 }
121
122 /* Converts ADPCM-data sample in input_smp of size SampleSize to requested dataformat and writes to output_smp */
123 /* Quick hack together from adpcm.cpp */
convertTinselADPCMSample(uint32 sampleSize)124 void CompressTinsel::convertTinselADPCMSample (uint32 sampleSize) {
125 byte *inBuffer, *inPos;
126 int16 *outBuffer, *outPos;
127 double predictor = 0;
128 double k0 = 0, k1 = 0;
129 double d0 = 0, d1 = 0;
130 uint32 blockAlign, blockPos;
131 uint16 chunkData = 0;
132 int16 chunkWord = 0;
133 uint8 headerByte, filterVal, chunkPos = 0;
134 const double eVal = 1.032226562;
135 uint32 decodeLeft = 0, decodedCount = 0;
136 uint32 uncompressedSize;
137 double sample;
138
139 uint32 copyLeft = 0;
140 uint32 doneRead = 0;
141 char buffer[2048];
142 Common::File curFileHandle;
143
144 print("Assuming DW2 sample using ADPCM 6-bit, decoding to 16-bit raw...");
145
146 // Allocate buffer for the ADPCM-compressed sample
147 inBuffer = (byte *)malloc(sampleSize);
148 if (!inBuffer) {
149 print("malloc failed!");
150 return;
151 }
152
153 // Allocate buffer for uncompressed sample data (3 bytes will be uncompressed to 8 bytes)
154 uncompressedSize = (sampleSize/3)*4*2+16;
155 outBuffer = (int16 *)malloc(uncompressedSize);
156 if (!outBuffer) {
157 print("malloc failed!");
158 free(inBuffer);
159 return;
160 }
161
162 _input_smp.read_throwsOnError(inBuffer, sampleSize);
163
164 // 1 channel, 22050 rate, block align 24,
165 blockAlign = 24; // Fixed for Tinsel 6-bit
166 blockPos = blockAlign; // To make sure first header is read
167
168 inPos = inBuffer; outPos = outBuffer;
169 decodeLeft = sampleSize;
170 while (decodeLeft > 0) {
171 if (blockPos == blockAlign) {
172 // read Tinsel header
173 headerByte = *inPos; inPos++; decodeLeft--;
174 filterVal = (headerByte & 0xC0) >> 6;
175
176 if ((headerByte & 0x20) != 0) {
177 //Lower 6 bit are negative
178 // Negate
179 headerByte = ~(headerByte | 0xC0) + 1;
180 predictor = 1 << headerByte;
181 } else {
182 // Lower 6 bit are positive
183 // Truncate
184 headerByte &= 0x1F;
185 predictor = ((double) 1.0) / (1 << headerByte);
186 }
187 k0 = TinselFilterTable[filterVal][0];
188 k1 = TinselFilterTable[filterVal][1];
189 blockPos = 0;
190 chunkPos = 0;
191 }
192
193 switch (chunkPos) {
194 case 0:
195 chunkData = *inPos; inPos++; decodeLeft--;
196 chunkWord = (chunkData << 8) & 0xFC00;
197 break;
198 case 1:
199 chunkData = (chunkData << 8) | *inPos; inPos++; decodeLeft--;
200 blockPos++;
201 chunkWord = (chunkData << 6) & 0xFC00;
202 break;
203 case 2:
204 chunkData = (chunkData << 8) | *inPos; inPos++; decodeLeft--;
205 blockPos++;
206 chunkWord = (chunkData << 4) & 0xFC00;
207 break;
208 case 3:
209 chunkData = chunkData << 8;
210 blockPos++;
211 chunkWord = (chunkData << 2) & 0xFC00;
212 break;
213 }
214 sample = chunkWord;
215 sample *= eVal * predictor;
216 sample += (d0 * k0) + (d1 * k1);
217 d1 = d0;
218 d0 = sample;
219 *outPos = (int16) CLIP<double>(sample, -32768.0, 32767.0); outPos++;
220 decodedCount++;
221 chunkPos = (chunkPos + 1) % 4;
222 }
223
224 Common::removeFile(TEMP_RAW);
225 Common::removeFile(TEMP_ENC);
226
227 curFileHandle.open(TEMP_RAW, "wb");
228 curFileHandle.write(outBuffer, decodedCount*2);
229
230 free(inBuffer);
231 free(outBuffer);
232
233 // Encode this raw data...
234 setRawAudioType(true, false, 16); // LE, mono, 16-bit
235 encodeAudio(TEMP_RAW, true, 22050, TEMP_ENC, _format);
236
237 // Append compressed data to output_smp
238 curFileHandle.open(TEMP_ENC, "rb");
239 curFileHandle.seek(0, SEEK_END);
240 copyLeft = curFileHandle.pos();
241 curFileHandle.seek(0, SEEK_SET);
242 // Write size of compressed data
243 _output_smp.writeUint32LE(copyLeft);
244 // Write actual data
245 while (copyLeft > 0) {
246 doneRead = curFileHandle.read_noThrow(buffer, copyLeft > sizeof(buffer) ? sizeof(buffer) : copyLeft);
247 if (doneRead <= 0)
248 break;
249 copyLeft -= (int)doneRead;
250 _output_smp.write(buffer, doneRead);
251 }
252 }
253
execute()254 void CompressTinsel::execute() {
255 uint32 indexNo = 0;
256 uint32 indexCount = 0;
257 uint32 indexOffset = 0;
258 uint32 loopCount = 0;
259 uint32 sampleSize = 0;
260 uint32 sampleCount = 0;
261
262 Common::Filename inpath_smp = _inputPaths[0].path;
263 Common::Filename inpath_idx = _inputPaths[1].path;
264
265 Common::Filename &outpath = _outputPath;
266 if (outpath.empty())
267 outpath = inpath_smp.getPath();
268
269 _input_idx.open(inpath_idx, "rb");
270 _input_smp.open(inpath_smp, "rb");
271
272 Common::Filename outpath_idx = outpath;
273 outpath_idx.setFullName(TEMP_IDX);
274 Common::removeFile(outpath_idx.getFullPath().c_str());
275 _output_idx.open(outpath_idx, "wb");
276
277 Common::Filename outpath_smp = outpath;
278 outpath_smp.setFullName(TEMP_SMP);
279 Common::removeFile(outpath_smp.getFullPath().c_str());
280 _output_smp.open(outpath_smp, "wb");
281
282 switch (_format) {
283 case AUDIO_MP3:
284 _output_smp.writeUint32BE(MKID_BE('MP3 '));
285 break;
286 case AUDIO_VORBIS:
287 _output_smp.writeUint32BE(MKID_BE('OGG '));
288 break;
289 case AUDIO_FLAC:
290 _output_smp.writeUint32BE(MKID_BE('FLAC'));
291 break;
292 default:
293 throw ToolException("Unknown audio format!");
294 }
295
296 _input_idx.seek(0, SEEK_END);
297 indexCount = _input_idx.pos() / sizeof(uint32);
298 _input_idx.seek(0, SEEK_SET);
299
300 loopCount = indexCount;
301 while (loopCount>0) {
302 // Update progress
303 updateProgress(indexCount - loopCount, indexCount);
304
305 indexOffset = _input_idx.readUint32LE();
306 if (indexOffset) {
307 if (indexNo==0) {
308 error("The sourcefiles are already compressed, aborting...");
309 }
310 // Got sample(s), so convert...
311 print("Converting sample %d of %d", indexNo, indexCount);
312
313 // Seek to Sample in input-file and read SampleSize
314 _input_smp.seek(indexOffset, SEEK_SET);
315 sampleSize = _input_smp.readUint32LE();
316
317 // Write offset of new data to new index file
318 _output_idx.writeUint32LE(_output_smp.pos());
319
320 if (sampleSize & 0x80000000) {
321 // multiple samples in ADPCM format
322 sampleCount = sampleSize & ~0x80000000;
323 // Write sample count to new sample file
324 _output_smp.writeUint32LE(sampleSize);
325 while (sampleCount>0) {
326 sampleSize = _input_smp.readUint32LE();
327 convertTinselADPCMSample(sampleSize);
328 sampleCount--;
329 }
330 } else {
331 // just one sample in raw format
332 convertTinselRawSample(sampleSize);
333 }
334 } else {
335 if (indexNo==0) {
336 // Write signature as index 0
337 switch (_format) {
338 case AUDIO_MP3: _output_idx.writeUint32BE(MKID_BE('MP3 ')); break;
339 case AUDIO_VORBIS: _output_idx.writeUint32BE(MKID_BE('OGG ')); break;
340 case AUDIO_FLAC: _output_idx.writeUint32BE(MKID_BE('FLAC')); break;
341 default: throw ToolException("Unknown audio format!");
342 }
343 } else {
344 _output_idx.writeUint32LE(0);
345 }
346 }
347 loopCount--;
348 indexNo++;
349 }
350
351 /* Close file handles */
352 _output_smp.close();
353 _output_idx.close();
354 _input_smp.close();
355 _input_idx.close();
356 /* And some clean-up :-) */
357 Common::removeFile(TEMP_RAW);
358 Common::removeFile(TEMP_ENC);
359 }
360
361
362 #ifdef STANDALONE_MAIN
main(int argc,char * argv[])363 int main(int argc, char *argv[]) {
364 CompressTinsel tinsel(argv[0]);
365 return tinsel.run(argc, argv);
366 }
367 #endif
368