Mercurial > hg > dmlib
view tools/mod2wav.c @ 2043:cbb3463fea2a
Initial dabbling for SDL2 migration of the SW rendering / dmsimple.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 30 Nov 2018 06:56:38 +0200 |
parents | c3e88d9343ca |
children | e3f0eaf23f4f |
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 "dmtool.h" #include <stdio.h> #include <stdlib.h> #include "jss.h" #include "jssmod.h" #include "jssmix.h" #include "jssplr.h" #include "dmlib.h" #include "dmargs.h" #include "dmfile.h" #include "dmmutex.h" #define DM_WAVE_FORMAT_PCM (1) #define DM_WAVE_RIFF_ID "RIFF" #define DM_WAVE_WAVE_ID "WAVE" #define DM_WAVE_FMT_ID "fmt " #define DM_WAVE_DATA_ID "data" typedef struct { Uint8 chunkID[4]; Uint32 chunkSize; } DMWaveChunk; typedef struct { Uint8 riffID[4]; Uint32 fileSize; Uint8 riffType[4]; DMWaveChunk chFormat; Uint16 wFormatTag; Uint16 nChannels; Uint32 nSamplesPerSec; Uint32 nAvgBytesPerSec; Uint16 nBlockAlign; Uint16 wBitsPerSample; DMWaveChunk chData; // Data follows here } DMWaveFile; char *optInFilename = NULL, *optOutFilename = NULL; int optOutFormat = JSS_AUDIO_S16, optOutChannels = 2, optOutFreq = 44100, optMuteOChannels = -1, optStartOrder = -1; BOOL optUsePlayTime = FALSE; size_t optPlayTime; static const 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, 0); 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: dmErrorMsg("Unknown argument '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optInFilename) optInFilename = currArg; else if (!optOutFilename) optOutFilename = currArg; else { dmErrorMsg("Too many filename arguments (only source and dest needed) '%s'\n", currArg); return FALSE; } return TRUE; } BOOL dmWriteWAVChunk(FILE * f, DMWaveChunk *ch) { return dm_fwrite_str(f, ch->chunkID, 4) && dm_fwrite_le32(f, ch->chunkSize); } void dmMakeWAVChunk(DMWaveChunk *ch, const char *chunkID, const Uint32 chunkSize) { memcpy(&(ch->chunkID), (const void *) chunkID, 4); ch->chunkSize = chunkSize; } void dmWriteWAVHeader(FILE *outFile, int sampBits, int sampFreq, int sampChn, size_t sampLen) { DMWaveFile wav; // PCM WAVE chunk dmMakeWAVChunk(&wav.chFormat, DM_WAVE_FMT_ID, (2 + 2 + 4 + 4 + 2 + 2)); wav.wFormatTag = DM_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 dmMakeWAVChunk(&wav.chData, DM_WAVE_DATA_ID, (sampLen * wav.nBlockAlign)); // RIFF header memcpy(&wav.riffID, (const void *) DM_WAVE_RIFF_ID, 4); memcpy(&wav.riffType, (const void *) DM_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)); dmWriteWAVChunk(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); dmWriteWAVChunk(outFile, &wav.chData); } int main(int argc, char *argv[]) { DMResource *inFile = NULL; FILE *outFile = NULL; JSSModule *mod = NULL; JSSMixer *dev = NULL; JSSPlayer *plr = 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, OPTH_BAILOUT)) exit(1); // Check arguments if (optInFilename == NULL || optOutFilename == NULL) { dmErrorMsg("Input or output file not specified. Try --help.\n"); return 1; } // Initialize miniJSS jssInit(); // Open the source file if ((result = dmf_open_stdio(optInFilename, "rb", &inFile)) != DMERR_OK) { dmErrorMsg("Error opening input file '%s', %d: %s\n", optInFilename, result, dmErrorStr(result)); return 1; } // Read module file dmMsg(1, "Reading file: %s\n", optInFilename); #ifdef JSS_SUP_XM result = jssLoadXM(inFile, &mod, TRUE); #endif #ifdef JSS_SUP_JSSMOD dmfreset(inFile); if (result != DMERR_OK) { dmMsg(1, "* Trying JSSMOD ...\n"); result = jssLoadJSSMOD(inFile, &mod, TRUE); dmfreset(inFile); if (result == DMERR_OK) result = jssLoadJSSMOD(inFile, &mod, FALSE); } else { dmMsg(2, "* Trying XM...\n"); result = jssLoadXM(inFile, &mod, FALSE); } #endif dmf_close(inFile); // Check for errors, we still might have some data tho if (result != DMERR_OK) { dmErrorMsg("Error loading module file, %d: %s\n", result, dmErrorStr(result)); } // Check if we have anything if (mod == NULL) return 3; // Try to convert it if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK) { dmErrorMsg("Could not convert module for playing, %d: %s\n", result, dmErrorStr(result)); return 3; } // Open mixer dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); if (dev == NULL) { dmErrorMsg("jvmInit() returned NULL\n"); return 4; } sampSize = jvmGetSampleSize(dev); if ((mb = dmMalloc(bufLen * sampSize)) == NULL) { dmErrorMsg("Could not allocate mixing buffer\n"); return 5; } dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%d / sample]\n", optOutFormat, jvmGetSampleRes(dev), optOutChannels, optOutFreq, sampSize); // Initialize player if ((plr = jmpInit(dev)) == NULL) { dmErrorMsg("jmpInit() returned NULL.\n"); return 6; } // Set callback jvmSetCallback(dev, jmpExec, plr); // Initialize playing jmpSetModule(plr, mod); if (optStartOrder >= 0) { dmMsg(1, "Starting from song order #%d\n", optStartOrder); } else optStartOrder = 0; jmpPlayOrder(plr, optStartOrder); jvmSetGlobalVol(dev, 150); if (optMuteOChannels > 0 && optMuteOChannels <= mod->nchannels) { int i; for (i = 0; i < mod->nchannels; i++) jvmMute(dev, i, TRUE); jvmMute(dev, optMuteOChannels - 1, FALSE); } // Open output file if ((outFile = fopen(optOutFilename, "wb")) == NULL) { int err = dmGetErrno(); dmErrorMsg("Error opening output file '%s' #%d: %s.\n", optInFilename, err, dmErrorStr(err)); return 7; } // Write initial header dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), 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 (plr->isPlaying && dataWritten > 0) { size_t writeLen = bufLen; if (optUsePlayTime && (writeLen + dataTotal) > optPlayTime) writeLen = optPlayTime - dataTotal; if (writeLen > 0) { jvmRenderAudio(dev, mb, writeLen); #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) jssEncodeSample16((Uint16 *)mb, writeLen * optOutChannels, jsampSwapEndianess); #endif dataWritten = fwrite(mb, sampSize, writeLen, outFile); if (dataWritten < writeLen) { dmErrorMsg("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) { dmErrorMsg("Error rewinding to header position!\n"); return 9; } dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, dataTotal); // Done! fclose(outFile); jmpClose(plr); jvmClose(dev); jssFreeModule(mod); jssClose(); dmMsg(1, "OK.\n"); return 0; }