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