Mercurial > hg > dmlib
annotate mod2wav.c @ 100:f16d102dbbac
Add a function for setting bitmapped font palette.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 02 Oct 2012 20:33:01 +0300 |
parents | 6b42aed2745b |
children | 1f8f4d7cb33b |
rev | line source |
---|---|
0 | 1 /* |
2 * mod2wav - Render XM/JSSMOD module to WAV waveform file | |
3 * Programmed and designed by Matti 'ccr' Hamalainen | |
4 * (C) Copyright 2007 Tecnic Software productions (TNSP) | |
5 * | |
6 * Please read file 'COPYING' for information on license and distribution. | |
7 */ | |
8 #include <stdio.h> | |
9 #include <string.h> | |
10 #include <stdlib.h> | |
11 #include <errno.h> | |
12 #include "jss.h" | |
13 #include "jssmod.h" | |
14 #include "jssmix.h" | |
15 #include "jssplr.h" | |
16 #include "dmlib.h" | |
17 #include "dmargs.h" | |
18 #include "dmfile.h" | |
19 | |
20 | |
21 #define JSS_WAVE_FORMAT_PCM (1) | |
22 #define JSS_WAVE_RIFF_ID "RIFF" | |
23 #define JSS_WAVE_WAVE_ID "WAVE" | |
24 #define JSS_WAVE_FMT_ID "fmt " | |
25 #define JSS_WAVE_DATA_ID "data" | |
26 | |
27 | |
28 typedef struct | |
29 { | |
30 Uint8 chunkID[4]; | |
31 Uint32 chunkSize; | |
32 } JSSWaveChunk; | |
33 | |
34 | |
35 typedef struct | |
36 { | |
37 Uint8 riffID[4]; | |
38 Uint32 fileSize; | |
39 Uint8 riffType[4]; | |
40 | |
41 JSSWaveChunk chFormat; | |
42 | |
43 Uint16 wFormatTag; | |
44 Uint16 nChannels; | |
45 Uint32 nSamplesPerSec; | |
46 Uint32 nAvgBytesPerSec; | |
47 Uint16 nBlockAlign; | |
48 Uint16 wBitsPerSample; | |
49 | |
50 JSSWaveChunk chData; | |
51 // Data follows here | |
52 } JSSWaveFile; | |
53 | |
54 | |
55 char *srcFilename = NULL, *destFilename = NULL; | |
56 int optOutFormat = JSS_AUDIO_S16, | |
57 optOutChannels = 2, | |
58 optOutFreq = 44100, | |
59 optMuteOChannels = -1, | |
60 optStartOrder = -1; | |
61 BOOL optUsePlayTime = FALSE; | |
62 size_t optPlayTime; | |
63 | |
64 | |
37 | 65 DMOptArg optList[] = |
66 { | |
67 { 0, '?', "help", "Show this help", OPT_NONE }, | |
68 { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, | |
69 { 3, '1', "16bit", "16-bit output", OPT_NONE }, | |
70 { 4, '8', "8bit", "8-bit output", OPT_NONE }, | |
71 { 5, 'm', "mono", "Mono output", OPT_NONE }, | |
72 { 6, 's', "stereo", "Stereo output", OPT_NONE }, | |
73 { 7, 'f', "freq", "Output frequency", OPT_ARGREQ }, | |
74 { 8, 'M', "mute", "Mute other channels than #", OPT_ARGREQ }, | |
75 { 9, 'o', "order", "Start from order #", OPT_ARGREQ }, | |
76 { 10, 't', "time", "Play for # seconds", OPT_ARGREQ }, | |
0 | 77 // {10, 'l', "loop", "Loop for # times", OPT_ARGREQ }, |
78 }; | |
79 | |
80 const int optListN = sizeof(optList) / sizeof(optList[0]); | |
81 | |
82 | |
83 BOOL argHandleOpt(const int optN, char *optArg, char *currArg) | |
84 { | |
85 (void) optArg; | |
86 | |
37 | 87 switch (optN) |
88 { | |
89 case 0: | |
90 dmPrintBanner(stdout, dmProgName, | |
91 "[options] [sourcefile] [destfile]"); | |
92 | |
93 dmArgsPrintHelp(stdout, optList, optListN); | |
94 exit(0); | |
95 break; | |
0 | 96 |
37 | 97 case 2: |
98 dmVerbosity++; | |
99 break; | |
100 | |
101 case 3: | |
102 optOutFormat = JSS_AUDIO_S16; | |
103 break; | |
0 | 104 |
37 | 105 case 4: |
106 optOutFormat = JSS_AUDIO_U8; | |
107 break; | |
0 | 108 |
37 | 109 case 5: |
110 optOutChannels = JSS_AUDIO_MONO; | |
111 break; | |
0 | 112 |
37 | 113 case 6: |
38
8b04b0b51edc
Oops, fix a stupid bug in -s option .. it was setting the wrong variable. :|
Matti Hamalainen <ccr@tnsp.org>
parents:
37
diff
changeset
|
114 optOutChannels = JSS_AUDIO_STEREO; |
37 | 115 break; |
0 | 116 |
37 | 117 case 7: |
118 optOutFreq = atoi(optArg); | |
119 break; | |
0 | 120 |
37 | 121 case 8: |
122 optMuteOChannels = atoi(optArg); | |
123 break; | |
124 | |
125 case 9: | |
126 optStartOrder = atoi(optArg); | |
127 break; | |
0 | 128 |
37 | 129 case 10: |
130 optPlayTime = atoi(optArg); | |
131 optUsePlayTime = TRUE; | |
132 break; | |
0 | 133 |
37 | 134 default: |
135 dmError("Unknown argument '%s'.\n", currArg); | |
136 return FALSE; | |
0 | 137 } |
138 | |
139 return TRUE; | |
140 } | |
141 | |
142 | |
143 BOOL argHandleFile(char *currArg) | |
144 { | |
145 if (!srcFilename) | |
146 srcFilename = currArg; | |
37 | 147 else |
148 if (!destFilename) | |
0 | 149 destFilename = currArg; |
37 | 150 else |
151 { | |
0 | 152 dmError("Too many filename arguments (only source and dest needed) '%s'\n", currArg); |
153 return FALSE; | |
154 } | |
155 | |
156 return TRUE; | |
157 } | |
158 | |
159 | |
160 BOOL jssWriteChunk(FILE * f, JSSWaveChunk *ch) | |
161 { | |
162 if (!dm_fwrite_str(f, ch->chunkID, 4)) return FALSE; | |
163 return dm_fwrite_le32(f, ch->chunkSize); | |
164 } | |
165 | |
166 | |
167 void jssMakeChunk(JSSWaveChunk *ch, const char *chunkID, const Uint32 chunkSize) | |
168 { | |
169 memcpy(&(ch->chunkID), (const void *) chunkID, 4); | |
170 ch->chunkSize = chunkSize; | |
171 } | |
172 | |
173 | |
174 void jssWriteWAVHeader(FILE *outFile, int sampBits, int sampFreq, int sampChn, size_t sampLen) | |
175 { | |
176 JSSWaveFile wav; | |
177 | |
178 // PCM WAVE chunk | |
179 jssMakeChunk(&wav.chFormat, JSS_WAVE_FMT_ID, (2 + 2 + 4 + 4 + 2 + 2)); | |
180 | |
181 wav.wFormatTag = JSS_WAVE_FORMAT_PCM; | |
182 wav.nChannels = sampChn; | |
183 wav.nSamplesPerSec = sampFreq; | |
184 wav.nAvgBytesPerSec = (sampBits * sampChn * sampFreq) / 8; | |
185 wav.nBlockAlign = (sampBits * sampChn) / 8; | |
186 wav.wBitsPerSample = sampBits; | |
187 | |
188 // Data chunk | |
189 jssMakeChunk(&wav.chData, JSS_WAVE_DATA_ID, (sampLen * wav.nBlockAlign)); | |
190 | |
191 // RIFF header | |
192 memcpy(&wav.riffID, (const void *) JSS_WAVE_RIFF_ID, 4); | |
193 memcpy(&wav.riffType, (const void *) JSS_WAVE_WAVE_ID, 4); | |
194 wav.fileSize = ((4 + 4 + 4) + wav.chFormat.chunkSize + wav.chData.chunkSize); | |
195 | |
196 // Write header | |
197 dm_fwrite_str(outFile, wav.riffID, sizeof(wav.riffID)); | |
198 dm_fwrite_le32(outFile, wav.fileSize); | |
199 | |
200 dm_fwrite_str(outFile, wav.riffType, sizeof(wav.riffType)); | |
201 jssWriteChunk(outFile, &wav.chFormat); | |
202 | |
203 dm_fwrite_le16(outFile, wav.wFormatTag); | |
204 dm_fwrite_le16(outFile, wav.nChannels); | |
205 dm_fwrite_le32(outFile, wav.nSamplesPerSec); | |
206 dm_fwrite_le32(outFile, wav.nAvgBytesPerSec); | |
207 dm_fwrite_le16(outFile, wav.nBlockAlign); | |
208 dm_fwrite_le16(outFile, wav.wBitsPerSample); | |
209 | |
210 jssWriteChunk(outFile, &wav.chData); | |
211 } | |
212 | |
213 | |
214 int main(int argc, char *argv[]) | |
215 { | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
216 DMResource *inFile = NULL; |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
217 FILE *outFile = NULL; |
68 | 218 JSSModule *mod = NULL; |
219 JSSMixer *dev = NULL; | |
220 JSSPlayer *plr = NULL; | |
0 | 221 int result = -1; |
222 size_t bufLen = 1024*4, dataTotal, dataWritten, sampSize; | |
223 Uint8 *mb = NULL; | |
224 | |
225 dmInitProg("mod2wav", "XM/JSSMOD to WAV renderer", "0.2", NULL, NULL); | |
226 dmVerbosity = 1; | |
227 | |
228 // Parse arguments | |
229 if (!dmArgsProcess(argc, argv, optList, optListN, | |
230 argHandleOpt, argHandleFile, TRUE)) | |
231 exit(1); | |
232 | |
233 // Check arguments | |
37 | 234 if (!srcFilename || !destFilename) |
235 { | |
0 | 236 dmError("Input or output file not specified!\n"); |
237 return 1; | |
238 } | |
239 | |
240 // Initialize miniJSS | |
241 jssInit(); | |
242 | |
243 // Open the source file | |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
244 if ((inFile = dmf_create_stdio(srcFilename, "rb")) == NULL) |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
245 { |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
246 dmError("Error opening input file '%s', %d: %s\n", |
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
247 srcFilename, errno, strerror(errno)); |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
248 return 1; |
0 | 249 } |
250 | |
251 // Read module file | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
252 fprintf(stderr, "Reading file: %s\n", srcFilename); |
0 | 253 #ifdef JSS_SUP_XM |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
254 fprintf(stderr, "* Trying XM...\n"); |
68 | 255 result = jssLoadXM(inFile, &mod); |
0 | 256 #endif |
257 #ifdef JSS_SUP_JSSMOD | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
258 if (result != 0) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
259 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
260 size_t bufgot, bufsize = dmfsize(inFile); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
261 Uint8 *buf = dmMalloc(bufsize); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
262 dmfseek(inFile, 0L, SEEK_SET); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
263 fprintf(stderr, "* Trying JSSMOD (%d bytes, %p)...\n", bufsize, buf); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
264 if ((bufgot = dmfread(buf, 1, bufsize, inFile)) != bufsize) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
265 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
266 fprintf(stderr, "Error reading file (not enough data %d), #%d: %s\n", |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
267 bufgot, dmferror(inFile), dmErrorStr(dmferror(inFile))); |
0 | 268 return 2; |
269 } | |
68 | 270 result = jssLoadJSSMOD(buf, bufsize, &mod); |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
271 dmFree(buf); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
272 } |
0 | 273 #endif |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
274 dmf_close(inFile); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
275 if (result != DMERR_OK) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
276 { |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
277 dmError("Error loading module file, %d: %s\n", |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
278 result, dmErrorStr(result)); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
279 return 3; |
0 | 280 } |
281 | |
49
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
282 // Try to convert it |
68 | 283 if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK) |
49
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
284 { |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
285 dmError("Could not convert module for playing, %d: %s\n", |
49
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
286 result, dmErrorStr(result)); |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
287 return 3; |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
288 } |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
289 |
0 | 290 // Open mixer |
68 | 291 dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); |
292 if (dev == NULL) | |
39 | 293 { |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
294 dmError("jvmInit() returned NULL\n"); |
0 | 295 return 4; |
296 } | |
297 | |
68 | 298 sampSize = jvmGetSampleSize(dev); |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
299 if ((mb = dmMalloc(bufLen * sampSize)) == NULL) |
39 | 300 { |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
301 dmError("Could not allocate mixing buffer\n"); |
0 | 302 return 5; |
303 } | |
304 | |
305 dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n", | |
68 | 306 optOutFormat, jvmGetSampleRes(dev), optOutChannels, optOutFreq, |
0 | 307 sampSize); |
308 | |
309 // Initialize player | |
68 | 310 if ((plr = jmpInit(dev)) == NULL) |
311 { | |
83
6b42aed2745b
Cleanups and correct the dmf_create_stdio() issue here as well.
Matti Hamalainen <ccr@tnsp.org>
parents:
68
diff
changeset
|
312 dmError("jmpInit() returned NULL.\n"); |
0 | 313 return 6; |
314 } | |
315 | |
316 // Set callback | |
68 | 317 jvmSetCallback(dev, jmpExec, plr); |
0 | 318 |
319 // Initialize playing | |
68 | 320 jmpSetModule(plr, mod); |
37 | 321 if (optStartOrder >= 0) |
322 { | |
0 | 323 dmMsg(1, "Starting from song order #%d\n", optStartOrder); |
324 } else | |
325 optStartOrder = 0; | |
37 | 326 |
68 | 327 jmpPlayOrder(plr, optStartOrder); |
328 jvmSetGlobalVol(dev, 50); | |
0 | 329 |
68 | 330 if (optMuteOChannels > 0 && optMuteOChannels <= mod->nchannels) |
37 | 331 { |
0 | 332 int i; |
68 | 333 for (i = 0; i < mod->nchannels; i++) |
334 jvmMute(dev, i, TRUE); | |
335 jvmMute(dev, optMuteOChannels - 1, FALSE); | |
0 | 336 } |
337 | |
338 // Open output file | |
37 | 339 if ((outFile = fopen(destFilename, "wb")) == NULL) |
340 { | |
0 | 341 dmError("Error opening output file '%s'. (%s)\n", srcFilename, strerror(errno)); |
342 return 7; | |
343 } | |
344 | |
345 // Write initial header | |
68 | 346 jssWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, 1024); |
0 | 347 |
348 // Render audio data and output to file | |
349 if (optUsePlayTime) | |
350 dmMsg(1, "Rendering module (%d seconds) ...\n", optPlayTime); | |
351 else | |
352 dmMsg(1, "Rendering module ...\n"); | |
353 | |
354 optPlayTime *= optOutFreq; | |
355 dataTotal = 0; | |
356 dataWritten = 1; | |
68 | 357 while (plr->isPlaying && dataWritten > 0) |
0 | 358 { |
359 size_t writeLen = bufLen; | |
360 if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime) | |
361 writeLen = optPlayTime - dataTotal; | |
362 | |
363 if (writeLen > 0) | |
364 { | |
68 | 365 jvmRenderAudio(dev, mb, writeLen); |
0 | 366 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) |
367 jssEncodeSample16((Uint16 *)mb, writeLen * optOutChannels, jsampSwapEndianess); | |
368 #endif | |
369 dataWritten = fwrite(mb, sampSize, writeLen, outFile); | |
370 if (dataWritten < writeLen) | |
371 { | |
372 dmError("Error writing data!\n"); | |
373 fclose(outFile); | |
374 return 8; | |
375 } | |
376 dataTotal += dataWritten; | |
377 } | |
378 | |
379 if (optUsePlayTime && dataTotal >= optPlayTime) | |
380 break; | |
381 } | |
382 | |
383 // Write the correct header | |
384 if (fseek(outFile, 0L, SEEK_SET) != 0) | |
385 { | |
386 dmError("Error rewinding to header position!\n"); | |
387 return 9; | |
388 } | |
389 | |
68 | 390 jssWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, dataTotal); |
0 | 391 |
392 // Done! | |
393 fclose(outFile); | |
68 | 394 |
395 jmpClose(plr); | |
396 jvmClose(dev); | |
397 jssFreeModule(mod); | |
398 jssClose(); | |
399 | |
0 | 400 dmMsg(1, "OK.\n"); |
401 return 0; | |
402 } |