Mercurial > hg > dmlib
view 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 |
line wrap: on
line source
/* * 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: optOutChannels = 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) { 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[]) { DMResource *inFile = NULL; FILE *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 = dmf_create_stdio(srcFilename)) == NULL) { fprintf(stderr, "Error opening input file '%s'. (%s)\n", srcFilename, strerror(errno)); return 1; } // Read module file fprintf(stderr, "Reading file: %s\n", srcFilename); #ifdef JSS_SUP_XM fprintf(stderr, "* Trying XM...\n"); result = jssLoadXM(inFile, &m); #endif #ifdef JSS_SUP_JSSMOD if (result != 0) { size_t bufgot, bufsize = dmfsize(inFile); Uint8 *buf = dmMalloc(bufsize); dmfseek(inFile, 0L, SEEK_SET); fprintf(stderr, "* Trying JSSMOD (%d bytes, %p)...\n", bufsize, buf); if ((bufgot = dmfread(buf, 1, bufsize, inFile)) != bufsize) { fprintf(stderr, "Error reading file (not enough data %d), #%d: %s\n", bufgot, dmferror(inFile), dmErrorStr(dmferror(inFile))); return 2; } result = jssLoadJSSMOD(buf, bufsize, &m); dmFree(buf); } #endif dmf_close(inFile); if (result != DMERR_OK) { fprintf(stderr, "Error loading module file, %d: %s\n", result, dmErrorStr(result)); return 3; } // Try to convert it if ((result = jssConvertModuleForPlaying(m)) != DMERR_OK) { fprintf(stderr, "Could not convert module for playing, %d: %s\n", result, dmErrorStr(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; }