Mercurial > hg > dmlib
annotate mod2wav.c @ 49:033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
8bit, but samples are internally upconverted to 16bit after module loading.)
Also prepare for floating point mixing support.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 01 Oct 2012 02:51:41 +0300 |
parents | 281b080e8c44 |
children | 182e5fac93f5 |
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; |
0 | 218 JSSModule *m = NULL; |
219 JSSMixer *d = NULL; | |
220 JSSPlayer *p = NULL; | |
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 | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
244 if ((inFile = dmf_create_stdio(srcFilename)) == NULL) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
245 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
246 fprintf(stderr, "Error opening input file '%s'. (%s)\n", srcFilename, strerror(errno)); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
247 return 1; |
0 | 248 } |
249 | |
250 // Read module file | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
251 fprintf(stderr, "Reading file: %s\n", srcFilename); |
0 | 252 #ifdef JSS_SUP_XM |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
253 fprintf(stderr, "* Trying XM...\n"); |
0 | 254 result = jssLoadXM(inFile, &m); |
255 #endif | |
256 #ifdef JSS_SUP_JSSMOD | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
257 if (result != 0) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
258 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
259 size_t bufgot, bufsize = dmfsize(inFile); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
260 Uint8 *buf = dmMalloc(bufsize); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
261 dmfseek(inFile, 0L, SEEK_SET); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
262 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
|
263 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
|
264 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
265 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
|
266 bufgot, dmferror(inFile), dmErrorStr(dmferror(inFile))); |
0 | 267 return 2; |
268 } | |
269 result = jssLoadJSSMOD(buf, bufsize, &m); | |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
270 dmFree(buf); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
271 } |
0 | 272 #endif |
15
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
273 dmf_close(inFile); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
274 if (result != DMERR_OK) |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
275 { |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
276 fprintf(stderr, "Error loading module file, %d: %s\n", |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
277 result, dmErrorStr(result)); |
feec43a3497c
Fix input file reading of mod2wav utility.
Matti Hamalainen <ccr@tnsp.org>
parents:
0
diff
changeset
|
278 return 3; |
0 | 279 } |
280 | |
49
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
281 // Try to convert it |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
282 if ((result = jssConvertModuleForPlaying(m)) != DMERR_OK) |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
283 { |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
284 fprintf(stderr, "Could not convert module for playing, %d: %s\n", |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
285 result, dmErrorStr(result)); |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
286 return 3; |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
287 } |
033c660c25f5
Restructure module playing, removing 8bit sample mixing (output can still be
Matti Hamalainen <ccr@tnsp.org>
parents:
39
diff
changeset
|
288 |
0 | 289 // Open mixer |
290 d = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); | |
39 | 291 if (!d) |
292 { | |
0 | 293 fprintf(stderr, "jvmInit() returned NULL\n"); |
294 return 4; | |
295 } | |
296 | |
297 sampSize = jvmGetSampleSize(d); | |
298 mb = dmMalloc(bufLen * sampSize); | |
39 | 299 if (!mb) |
300 { | |
0 | 301 fprintf(stderr, "Could not allocate mixing buffer\n"); |
302 return 5; | |
303 } | |
304 | |
305 dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n", | |
306 optOutFormat, jvmGetSampleRes(d), optOutChannels, optOutFreq, | |
307 sampSize); | |
308 | |
309 // Initialize player | |
310 p = jmpInit(d); | |
311 if (!p) { | |
312 fprintf(stderr, "jmpInit() returned NULL\n"); | |
313 return 6; | |
314 } | |
315 | |
316 // Set callback | |
317 jvmSetCallback(d, jmpExec, p); | |
318 | |
319 // Initialize playing | |
320 jmpSetModule(p, m); | |
37 | 321 if (optStartOrder >= 0) |
322 { | |
0 | 323 dmMsg(1, "Starting from song order #%d\n", optStartOrder); |
324 } else | |
325 optStartOrder = 0; | |
37 | 326 |
0 | 327 jmpPlayOrder(p, optStartOrder); |
328 jvmSetGlobalVol(d, 50); | |
329 | |
37 | 330 if (optMuteOChannels > 0 && optMuteOChannels <= m->nchannels) |
331 { | |
0 | 332 int i; |
333 for (i = 0; i < m->nchannels; i++) | |
334 jvmMute(d, i, TRUE); | |
335 jvmMute(d, optMuteOChannels - 1, FALSE); | |
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 | |
346 jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, 1024); | |
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; | |
357 while (p->isPlaying && dataWritten > 0) | |
358 { | |
359 size_t writeLen = bufLen; | |
360 if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime) | |
361 writeLen = optPlayTime - dataTotal; | |
362 | |
363 if (writeLen > 0) | |
364 { | |
365 jvmRenderAudio(d, mb, writeLen); | |
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 | |
390 jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, dataTotal); | |
391 | |
392 // Done! | |
393 fclose(outFile); | |
394 jssFreeModule(m); | |
395 dmMsg(1, "OK.\n"); | |
396 return 0; | |
397 } |