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 }