Mercurial > hg > dmlib
comparison mod2wav.c @ 0:32250b436bca
Initial re-import.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 28 Sep 2012 01:54:23 +0300 |
parents | |
children | feec43a3497c |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:32250b436bca |
---|---|
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 | |
65 DMOptArg optList[] = { | |
66 { 0, '?', "help", "Show this help", OPT_NONE }, | |
67 { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, | |
68 { 3, '1', "16bit", "16-bit output", OPT_NONE }, | |
69 { 4, '8', "8bit", "8-bit output", OPT_NONE }, | |
70 { 5, 'm', "mono", "Mono output", OPT_NONE }, | |
71 { 6, 's', "stereo", "Stereo output", OPT_NONE }, | |
72 { 7, 'f', "freq", "Output frequency", OPT_ARGREQ }, | |
73 { 8, 'M', "mute", "Mute other channels than #", OPT_ARGREQ }, | |
74 { 9, 'o', "order", "Start from order #", OPT_ARGREQ }, | |
75 {10, 't', "time", "Play for # seconds", OPT_ARGREQ }, | |
76 // {10, 'l', "loop", "Loop for # times", OPT_ARGREQ }, | |
77 }; | |
78 | |
79 const int optListN = sizeof(optList) / sizeof(optList[0]); | |
80 | |
81 | |
82 BOOL argHandleOpt(const int optN, char *optArg, char *currArg) | |
83 { | |
84 (void) optArg; | |
85 | |
86 switch (optN) { | |
87 case 0: | |
88 dmPrintBanner(stdout, dmProgName, | |
89 "[options] [sourcefile] [destfile]"); | |
90 | |
91 dmArgsPrintHelp(stdout, optList, optListN); | |
92 exit(0); | |
93 break; | |
94 | |
95 case 2: | |
96 dmVerbosity++; | |
97 break; | |
98 | |
99 case 3: | |
100 optOutFormat = JSS_AUDIO_S16; | |
101 break; | |
102 | |
103 case 4: | |
104 optOutFormat = JSS_AUDIO_U8; | |
105 break; | |
106 | |
107 case 5: | |
108 optOutChannels = JSS_AUDIO_MONO; | |
109 break; | |
110 | |
111 case 6: | |
112 optOutFormat = JSS_AUDIO_STEREO; | |
113 break; | |
114 | |
115 case 7: | |
116 optOutFreq = atoi(optArg); | |
117 break; | |
118 | |
119 case 8: | |
120 optMuteOChannels = atoi(optArg); | |
121 break; | |
122 | |
123 case 9: | |
124 optStartOrder = atoi(optArg); | |
125 break; | |
126 | |
127 case 10: | |
128 optPlayTime = atoi(optArg); | |
129 optUsePlayTime = TRUE; | |
130 break; | |
131 | |
132 default: | |
133 dmError("Unknown argument '%s'.\n", currArg); | |
134 return FALSE; | |
135 } | |
136 | |
137 return TRUE; | |
138 } | |
139 | |
140 | |
141 BOOL argHandleFile(char *currArg) | |
142 { | |
143 // Was not option argument | |
144 if (!srcFilename) | |
145 srcFilename = currArg; | |
146 else if (!destFilename) | |
147 destFilename = currArg; | |
148 else { | |
149 dmError("Too many filename arguments (only source and dest needed) '%s'\n", currArg); | |
150 return FALSE; | |
151 } | |
152 | |
153 return TRUE; | |
154 } | |
155 | |
156 | |
157 BOOL jssWriteChunk(FILE * f, JSSWaveChunk *ch) | |
158 { | |
159 if (!dm_fwrite_str(f, ch->chunkID, 4)) return FALSE; | |
160 return dm_fwrite_le32(f, ch->chunkSize); | |
161 } | |
162 | |
163 | |
164 void jssMakeChunk(JSSWaveChunk *ch, const char *chunkID, const Uint32 chunkSize) | |
165 { | |
166 memcpy(&(ch->chunkID), (const void *) chunkID, 4); | |
167 ch->chunkSize = chunkSize; | |
168 } | |
169 | |
170 | |
171 void jssWriteWAVHeader(FILE *outFile, int sampBits, int sampFreq, int sampChn, size_t sampLen) | |
172 { | |
173 JSSWaveFile wav; | |
174 | |
175 // PCM WAVE chunk | |
176 jssMakeChunk(&wav.chFormat, JSS_WAVE_FMT_ID, (2 + 2 + 4 + 4 + 2 + 2)); | |
177 | |
178 wav.wFormatTag = JSS_WAVE_FORMAT_PCM; | |
179 wav.nChannels = sampChn; | |
180 wav.nSamplesPerSec = sampFreq; | |
181 wav.nAvgBytesPerSec = (sampBits * sampChn * sampFreq) / 8; | |
182 wav.nBlockAlign = (sampBits * sampChn) / 8; | |
183 wav.wBitsPerSample = sampBits; | |
184 | |
185 // Data chunk | |
186 jssMakeChunk(&wav.chData, JSS_WAVE_DATA_ID, (sampLen * wav.nBlockAlign)); | |
187 | |
188 // RIFF header | |
189 memcpy(&wav.riffID, (const void *) JSS_WAVE_RIFF_ID, 4); | |
190 memcpy(&wav.riffType, (const void *) JSS_WAVE_WAVE_ID, 4); | |
191 wav.fileSize = ((4 + 4 + 4) + wav.chFormat.chunkSize + wav.chData.chunkSize); | |
192 | |
193 // Write header | |
194 dm_fwrite_str(outFile, wav.riffID, sizeof(wav.riffID)); | |
195 dm_fwrite_le32(outFile, wav.fileSize); | |
196 | |
197 dm_fwrite_str(outFile, wav.riffType, sizeof(wav.riffType)); | |
198 jssWriteChunk(outFile, &wav.chFormat); | |
199 | |
200 dm_fwrite_le16(outFile, wav.wFormatTag); | |
201 dm_fwrite_le16(outFile, wav.nChannels); | |
202 dm_fwrite_le32(outFile, wav.nSamplesPerSec); | |
203 dm_fwrite_le32(outFile, wav.nAvgBytesPerSec); | |
204 dm_fwrite_le16(outFile, wav.nBlockAlign); | |
205 dm_fwrite_le16(outFile, wav.wBitsPerSample); | |
206 | |
207 jssWriteChunk(outFile, &wav.chData); | |
208 } | |
209 | |
210 | |
211 int main(int argc, char *argv[]) | |
212 { | |
213 FILE *inFile = NULL, *outFile = NULL; | |
214 JSSModule *m = NULL; | |
215 JSSMixer *d = NULL; | |
216 JSSPlayer *p = NULL; | |
217 int result = -1; | |
218 size_t bufLen = 1024*4, dataTotal, dataWritten, sampSize; | |
219 Uint8 *mb = NULL; | |
220 | |
221 dmInitProg("mod2wav", "XM/JSSMOD to WAV renderer", "0.2", NULL, NULL); | |
222 dmVerbosity = 1; | |
223 | |
224 // Parse arguments | |
225 if (!dmArgsProcess(argc, argv, optList, optListN, | |
226 argHandleOpt, argHandleFile, TRUE)) | |
227 exit(1); | |
228 | |
229 // Check arguments | |
230 if (!srcFilename || !destFilename) { | |
231 dmError("Input or output file not specified!\n"); | |
232 return 1; | |
233 } | |
234 | |
235 // Initialize miniJSS | |
236 jssInit(); | |
237 | |
238 // Open the source file | |
239 if ((inFile = fopen(srcFilename, "rb")) == NULL) { | |
240 dmError("Error opening input file '%s'. (%s)\n", srcFilename, strerror(errno)); | |
241 return 2; | |
242 } | |
243 | |
244 // Read module file | |
245 #ifdef JSS_SUP_XM | |
246 result = jssLoadXM(inFile, &m); | |
247 #endif | |
248 if (result != 0) { | |
249 #ifdef JSS_SUP_JSSMOD | |
250 Uint8 *buf; | |
251 size_t bufsize; | |
252 fseek(inFile, 0L, SEEK_END); | |
253 bufsize = ftell(inFile); | |
254 fseek(inFile, 0L, SEEK_SET); | |
255 buf = dmMalloc(bufsize); | |
256 if (fread(buf, 1, bufsize, inFile) != bufsize) { | |
257 dmError("Error reading file!\n"); | |
258 return 2; | |
259 } | |
260 result = jssLoadJSSMOD(buf, bufsize, &m); | |
261 #endif | |
262 if (result != 0) { | |
263 dmError("Error loading module file: %d\n", result); | |
264 return 3; | |
265 } | |
266 } | |
267 | |
268 // Open mixer | |
269 d = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); | |
270 if (!d) { | |
271 fprintf(stderr, "jvmInit() returned NULL\n"); | |
272 return 4; | |
273 } | |
274 | |
275 sampSize = jvmGetSampleSize(d); | |
276 mb = dmMalloc(bufLen * sampSize); | |
277 if (!mb) { | |
278 fprintf(stderr, "Could not allocate mixing buffer\n"); | |
279 return 5; | |
280 } | |
281 | |
282 dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n", | |
283 optOutFormat, jvmGetSampleRes(d), optOutChannels, optOutFreq, | |
284 sampSize); | |
285 | |
286 // Initialize player | |
287 p = jmpInit(d); | |
288 if (!p) { | |
289 fprintf(stderr, "jmpInit() returned NULL\n"); | |
290 return 6; | |
291 } | |
292 | |
293 // Set callback | |
294 jvmSetCallback(d, jmpExec, p); | |
295 | |
296 // Initialize playing | |
297 jmpSetModule(p, m); | |
298 if (optStartOrder >= 0) { | |
299 dmMsg(1, "Starting from song order #%d\n", optStartOrder); | |
300 } else | |
301 optStartOrder = 0; | |
302 jmpPlayOrder(p, optStartOrder); | |
303 jvmSetGlobalVol(d, 50); | |
304 | |
305 if (optMuteOChannels > 0 && optMuteOChannels <= m->nchannels) { | |
306 int i; | |
307 for (i = 0; i < m->nchannels; i++) | |
308 jvmMute(d, i, TRUE); | |
309 jvmMute(d, optMuteOChannels - 1, FALSE); | |
310 } | |
311 | |
312 // Open output file | |
313 if ((outFile = fopen(destFilename, "wb")) == NULL) { | |
314 dmError("Error opening output file '%s'. (%s)\n", srcFilename, strerror(errno)); | |
315 return 7; | |
316 } | |
317 | |
318 // Write initial header | |
319 jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, 1024); | |
320 | |
321 // Render audio data and output to file | |
322 if (optUsePlayTime) | |
323 dmMsg(1, "Rendering module (%d seconds) ...\n", optPlayTime); | |
324 else | |
325 dmMsg(1, "Rendering module ...\n"); | |
326 | |
327 optPlayTime *= optOutFreq; | |
328 dataTotal = 0; | |
329 dataWritten = 1; | |
330 while (p->isPlaying && dataWritten > 0) | |
331 { | |
332 size_t writeLen = bufLen; | |
333 if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime) | |
334 writeLen = optPlayTime - dataTotal; | |
335 | |
336 if (writeLen > 0) | |
337 { | |
338 jvmRenderAudio(d, mb, writeLen); | |
339 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) | |
340 jssEncodeSample16((Uint16 *)mb, writeLen * optOutChannels, jsampSwapEndianess); | |
341 #endif | |
342 dataWritten = fwrite(mb, sampSize, writeLen, outFile); | |
343 if (dataWritten < writeLen) | |
344 { | |
345 dmError("Error writing data!\n"); | |
346 fclose(outFile); | |
347 return 8; | |
348 } | |
349 dataTotal += dataWritten; | |
350 } | |
351 | |
352 if (optUsePlayTime && dataTotal >= optPlayTime) | |
353 break; | |
354 } | |
355 | |
356 // Write the correct header | |
357 if (fseek(outFile, 0L, SEEK_SET) != 0) | |
358 { | |
359 dmError("Error rewinding to header position!\n"); | |
360 return 9; | |
361 } | |
362 | |
363 jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, dataTotal); | |
364 | |
365 // Done! | |
366 fclose(outFile); | |
367 jssFreeModule(m); | |
368 dmMsg(1, "OK.\n"); | |
369 return 0; | |
370 } |