Mercurial > hg > dmlib
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod2wav.c Fri Sep 28 01:54:23 2012 +0300 @@ -0,0 +1,370 @@ +/* + * mod2wav - Render XM/JSSMOD module to WAV waveform file + * Programmed and designed by Matti 'ccr' Hamalainen + * (C) Copyright 2007 Tecnic Software productions (TNSP) + * + * Please read file 'COPYING' for information on license and distribution. + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include "jss.h" +#include "jssmod.h" +#include "jssmix.h" +#include "jssplr.h" +#include "dmlib.h" +#include "dmargs.h" +#include "dmfile.h" + + +#define JSS_WAVE_FORMAT_PCM (1) +#define JSS_WAVE_RIFF_ID "RIFF" +#define JSS_WAVE_WAVE_ID "WAVE" +#define JSS_WAVE_FMT_ID "fmt " +#define JSS_WAVE_DATA_ID "data" + + +typedef struct +{ + Uint8 chunkID[4]; + Uint32 chunkSize; +} JSSWaveChunk; + + +typedef struct +{ + Uint8 riffID[4]; + Uint32 fileSize; + Uint8 riffType[4]; + + JSSWaveChunk chFormat; + + Uint16 wFormatTag; + Uint16 nChannels; + Uint32 nSamplesPerSec; + Uint32 nAvgBytesPerSec; + Uint16 nBlockAlign; + Uint16 wBitsPerSample; + + JSSWaveChunk chData; + // Data follows here +} JSSWaveFile; + + +char *srcFilename = NULL, *destFilename = NULL; +int optOutFormat = JSS_AUDIO_S16, + optOutChannels = 2, + optOutFreq = 44100, + optMuteOChannels = -1, + optStartOrder = -1; +BOOL optUsePlayTime = FALSE; +size_t optPlayTime; + + +DMOptArg optList[] = { + { 0, '?', "help", "Show this help", OPT_NONE }, + { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 3, '1', "16bit", "16-bit output", OPT_NONE }, + { 4, '8', "8bit", "8-bit output", OPT_NONE }, + { 5, 'm', "mono", "Mono output", OPT_NONE }, + { 6, 's', "stereo", "Stereo output", OPT_NONE }, + { 7, 'f', "freq", "Output frequency", OPT_ARGREQ }, + { 8, 'M', "mute", "Mute other channels than #", OPT_ARGREQ }, + { 9, 'o', "order", "Start from order #", OPT_ARGREQ }, + {10, 't', "time", "Play for # seconds", OPT_ARGREQ }, +// {10, 'l', "loop", "Loop for # times", OPT_ARGREQ }, +}; + +const int optListN = sizeof(optList) / sizeof(optList[0]); + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + (void) optArg; + + switch (optN) { + case 0: + dmPrintBanner(stdout, dmProgName, + "[options] [sourcefile] [destfile]"); + + dmArgsPrintHelp(stdout, optList, optListN); + exit(0); + break; + + case 2: + dmVerbosity++; + break; + + case 3: + optOutFormat = JSS_AUDIO_S16; + break; + + case 4: + optOutFormat = JSS_AUDIO_U8; + break; + + case 5: + optOutChannels = JSS_AUDIO_MONO; + break; + + case 6: + optOutFormat = JSS_AUDIO_STEREO; + break; + + case 7: + optOutFreq = atoi(optArg); + break; + + case 8: + optMuteOChannels = atoi(optArg); + break; + + case 9: + optStartOrder = atoi(optArg); + break; + + case 10: + optPlayTime = atoi(optArg); + optUsePlayTime = TRUE; + break; + + default: + dmError("Unknown argument '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + // Was not option argument + if (!srcFilename) + srcFilename = currArg; + else if (!destFilename) + destFilename = currArg; + else { + dmError("Too many filename arguments (only source and dest needed) '%s'\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL jssWriteChunk(FILE * f, JSSWaveChunk *ch) +{ + if (!dm_fwrite_str(f, ch->chunkID, 4)) return FALSE; + return dm_fwrite_le32(f, ch->chunkSize); +} + + +void jssMakeChunk(JSSWaveChunk *ch, const char *chunkID, const Uint32 chunkSize) +{ + memcpy(&(ch->chunkID), (const void *) chunkID, 4); + ch->chunkSize = chunkSize; +} + + +void jssWriteWAVHeader(FILE *outFile, int sampBits, int sampFreq, int sampChn, size_t sampLen) +{ + JSSWaveFile wav; + + // PCM WAVE chunk + jssMakeChunk(&wav.chFormat, JSS_WAVE_FMT_ID, (2 + 2 + 4 + 4 + 2 + 2)); + + wav.wFormatTag = JSS_WAVE_FORMAT_PCM; + wav.nChannels = sampChn; + wav.nSamplesPerSec = sampFreq; + wav.nAvgBytesPerSec = (sampBits * sampChn * sampFreq) / 8; + wav.nBlockAlign = (sampBits * sampChn) / 8; + wav.wBitsPerSample = sampBits; + + // Data chunk + jssMakeChunk(&wav.chData, JSS_WAVE_DATA_ID, (sampLen * wav.nBlockAlign)); + + // RIFF header + memcpy(&wav.riffID, (const void *) JSS_WAVE_RIFF_ID, 4); + memcpy(&wav.riffType, (const void *) JSS_WAVE_WAVE_ID, 4); + wav.fileSize = ((4 + 4 + 4) + wav.chFormat.chunkSize + wav.chData.chunkSize); + + // Write header + dm_fwrite_str(outFile, wav.riffID, sizeof(wav.riffID)); + dm_fwrite_le32(outFile, wav.fileSize); + + dm_fwrite_str(outFile, wav.riffType, sizeof(wav.riffType)); + jssWriteChunk(outFile, &wav.chFormat); + + dm_fwrite_le16(outFile, wav.wFormatTag); + dm_fwrite_le16(outFile, wav.nChannels); + dm_fwrite_le32(outFile, wav.nSamplesPerSec); + dm_fwrite_le32(outFile, wav.nAvgBytesPerSec); + dm_fwrite_le16(outFile, wav.nBlockAlign); + dm_fwrite_le16(outFile, wav.wBitsPerSample); + + jssWriteChunk(outFile, &wav.chData); +} + + +int main(int argc, char *argv[]) +{ + FILE *inFile = NULL, *outFile = NULL; + JSSModule *m = NULL; + JSSMixer *d = NULL; + JSSPlayer *p = NULL; + int result = -1; + size_t bufLen = 1024*4, dataTotal, dataWritten, sampSize; + Uint8 *mb = NULL; + + dmInitProg("mod2wav", "XM/JSSMOD to WAV renderer", "0.2", NULL, NULL); + dmVerbosity = 1; + + // Parse arguments + if (!dmArgsProcess(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, TRUE)) + exit(1); + + // Check arguments + if (!srcFilename || !destFilename) { + dmError("Input or output file not specified!\n"); + return 1; + } + + // Initialize miniJSS + jssInit(); + + // Open the source file + if ((inFile = fopen(srcFilename, "rb")) == NULL) { + dmError("Error opening input file '%s'. (%s)\n", srcFilename, strerror(errno)); + return 2; + } + + // Read module file +#ifdef JSS_SUP_XM + result = jssLoadXM(inFile, &m); +#endif + if (result != 0) { +#ifdef JSS_SUP_JSSMOD + Uint8 *buf; + size_t bufsize; + fseek(inFile, 0L, SEEK_END); + bufsize = ftell(inFile); + fseek(inFile, 0L, SEEK_SET); + buf = dmMalloc(bufsize); + if (fread(buf, 1, bufsize, inFile) != bufsize) { + dmError("Error reading file!\n"); + return 2; + } + result = jssLoadJSSMOD(buf, bufsize, &m); +#endif + if (result != 0) { + dmError("Error loading module file: %d\n", result); + return 3; + } + } + + // Open mixer + d = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); + if (!d) { + fprintf(stderr, "jvmInit() returned NULL\n"); + return 4; + } + + sampSize = jvmGetSampleSize(d); + mb = dmMalloc(bufLen * sampSize); + if (!mb) { + fprintf(stderr, "Could not allocate mixing buffer\n"); + return 5; + } + + dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n", + optOutFormat, jvmGetSampleRes(d), optOutChannels, optOutFreq, + sampSize); + + // Initialize player + p = jmpInit(d); + if (!p) { + fprintf(stderr, "jmpInit() returned NULL\n"); + return 6; + } + + // Set callback + jvmSetCallback(d, jmpExec, p); + + // Initialize playing + jmpSetModule(p, m); + if (optStartOrder >= 0) { + dmMsg(1, "Starting from song order #%d\n", optStartOrder); + } else + optStartOrder = 0; + jmpPlayOrder(p, optStartOrder); + jvmSetGlobalVol(d, 50); + + if (optMuteOChannels > 0 && optMuteOChannels <= m->nchannels) { + int i; + for (i = 0; i < m->nchannels; i++) + jvmMute(d, i, TRUE); + jvmMute(d, optMuteOChannels - 1, FALSE); + } + + // Open output file + if ((outFile = fopen(destFilename, "wb")) == NULL) { + dmError("Error opening output file '%s'. (%s)\n", srcFilename, strerror(errno)); + return 7; + } + + // Write initial header + jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, 1024); + + // Render audio data and output to file + if (optUsePlayTime) + dmMsg(1, "Rendering module (%d seconds) ...\n", optPlayTime); + else + dmMsg(1, "Rendering module ...\n"); + + optPlayTime *= optOutFreq; + dataTotal = 0; + dataWritten = 1; + while (p->isPlaying && dataWritten > 0) + { + size_t writeLen = bufLen; + if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime) + writeLen = optPlayTime - dataTotal; + + if (writeLen > 0) + { + jvmRenderAudio(d, mb, writeLen); +#if (SDL_BYTEORDER == SDL_BIG_ENDIAN) + jssEncodeSample16((Uint16 *)mb, writeLen * optOutChannels, jsampSwapEndianess); +#endif + dataWritten = fwrite(mb, sampSize, writeLen, outFile); + if (dataWritten < writeLen) + { + dmError("Error writing data!\n"); + fclose(outFile); + return 8; + } + dataTotal += dataWritten; + } + + if (optUsePlayTime && dataTotal >= optPlayTime) + break; + } + + // Write the correct header + if (fseek(outFile, 0L, SEEK_SET) != 0) + { + dmError("Error rewinding to header position!\n"); + return 9; + } + + jssWriteWAVHeader(outFile, jvmGetSampleRes(d), optOutFreq, optOutChannels, dataTotal); + + // Done! + fclose(outFile); + jssFreeModule(m); + dmMsg(1, "OK.\n"); + return 0; +}