Mercurial > hg > dmlib
view tools/mod2wav.c @ 2568:8ab923fe23ef
Add Make parameter/variable CHARGEN= for easier defining of
the chargen ROM file path.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 02 Mar 2022 00:04:30 +0200 |
parents | b205c60aa657 |
children | 9807ae37ad69 |
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 }, { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE }, { 2, 'v', "verbose" , "Be more verbose", OPT_NONE }, { 10, '1', "16bit" , "16-bit output", OPT_NONE }, { 12, '8', "8bit" , "8-bit output", OPT_NONE }, { 14, 'm', "mono" , "Mono output", OPT_NONE }, { 16, 's', "stereo" , "Stereo output", OPT_NONE }, { 18, 'f', "freq" , "Output frequency", OPT_ARGREQ }, { 20, 'M', "mute" , "Mute other channels than #", OPT_ARGREQ }, { 22, 'o', "order" , "Start from order #", OPT_ARGREQ }, { 24, 't', "time" , "Play for # seconds", OPT_ARGREQ }, // { 26, 'l', "loop" , "Loop for # times", OPT_ARGREQ }, }; const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] [sourcefile] [destfile.wav]"); dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { (void) optArg; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmPrintLicense(stdout); exit(0); break; case 2: dmVerbosity++; break; case 10: optOutFormat = JSS_AUDIO_S16; break; case 12: optOutFormat = JSS_AUDIO_U8; break; case 14: optOutChannels = JSS_AUDIO_MONO; break; case 16: optOutChannels = JSS_AUDIO_STEREO; break; case 18: optOutFreq = atoi(optArg); break; case 20: optMuteOChannels = atoi(optArg); break; case 22: optStartOrder = atoi(optArg); break; case 24: optPlayTime = atoi(optArg); optUsePlayTime = TRUE; break; default: dmErrorMsg("Unimplemented option argument '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (optInFilename == NULL) optInFilename = currArg; else if (optOutFilename == NULL) 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; size_t bufLen = 1024*4, dataTotal, dataWritten, sampSize; Uint8 *dataBuf = NULL; int res = DMERR_OK; 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)) goto out; // Check arguments if (optInFilename == NULL || optOutFilename == NULL) { argShowHelp(); res = dmError(DMERR_INVALID_ARGS, "Input or output file not specified.\n"); goto out; } // Initialize miniJSS jssInit(); // Open the source file if ((res = dmf_open_stdio(optInFilename, "rb", &inFile)) != DMERR_OK) { dmErrorMsg("Error opening input file '%s': %s\n", optInFilename, dmErrorStr(res)); goto out; } // Read module file dmMsg(1, "Reading file: %s\n", optInFilename); #ifdef JSS_SUP_XM if (mod == NULL) { dmMsg(2, "* Trying XM...\n"); dmfreset(inFile); if ((res = jssLoadXM(inFile, &mod, TRUE)) == DMERR_OK) { dmfreset(inFile); res = jssLoadXM(inFile, &mod, FALSE); } } #endif #ifdef JSS_SUP_JSSMOD if (mod == NULL) { dmMsg(1, "* Trying JSSMOD ...\n"); dmfreset(inFile); if ((res = jssLoadJSSMOD(inFile, &mod, TRUE)) == DMERR_OK) { dmfreset(inFile); res = jssLoadJSSMOD(inFile, &mod, FALSE); } } #endif dmf_close(inFile); // Check for errors, we still might have some data tho if (res != DMERR_OK) { dmErrorMsg("Error loading module file: %s\n", dmErrorStr(res)); goto out; } // Check if we have anything if (mod == NULL) { res = dmError(DMERR_INIT_FAIL, "Could not load module file.\n"); goto out; } // Try to convert it if ((res = jssConvertModuleForPlaying(mod)) != DMERR_OK) { dmErrorMsg("Could not convert module for playing: %s\n", dmErrorStr(res)); goto out; } // Open mixer dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); if (dev == NULL) { res = dmError(DMERR_INIT_FAIL, "jvmInit() returned NULL\n"); goto out; } sampSize = jvmGetSampleSize(dev); if ((dataBuf = dmMalloc(bufLen * sampSize)) == NULL) { res = dmError(DMERR_MALLOC, "Could not allocate mixing buffer.\n"); goto out; } dmMsg(1, "Using fmt=%d, bits=%d, channels=%d, freq=%d [%" DM_PRIu_SIZE_T " / sample]\n", optOutFormat, jvmGetSampleRes(dev), optOutChannels, optOutFreq, sampSize); // Initialize player if ((plr = jmpInit(dev)) == NULL) { dmErrorMsg("jmpInit() returned NULL.\n"); goto out; } // 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) { for (int i = 0; i < mod->nchannels; i++) jvmMute(dev, i, TRUE); jvmMute(dev, optMuteOChannels - 1, FALSE); } // Open output file if ((outFile = fopen(optOutFilename, "wb")) == NULL) { res = dmGetErrno(); dmErrorMsg("Error opening output file '%s': %s.\n", optInFilename, dmErrorStr(res)); goto out; } // Write initial header dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, 1024); // Render audio data and output to file if (optUsePlayTime) dmMsg(1, "Rendering module (%" DM_PRIu_SIZE_T " 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, dataBuf, writeLen); #if (SDL_BYTEORDER == SDL_BIG_ENDIAN) jssEncodeSample16((Uint16 *) dataBuf, writeLen * optOutChannels, jsampSwapEndianess); #endif dataWritten = fwrite(dataBuf, sampSize, writeLen, outFile); if (dataWritten < writeLen) { res = dmError(DMERR_FWRITE, "Error writing audio data!\n"); goto out; } dataTotal += dataWritten; } if (optUsePlayTime && dataTotal >= optPlayTime) break; } // Write the correct header if (fseek(outFile, 0L, SEEK_SET) != 0) { res = dmError(DMERR_FSEEK, "Error rewinding to header position!\n"); goto out; } dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, dataTotal); // Done! dmMsg(1, "OK.\n"); out: if (outFile != NULL) fclose(outFile); dmFree(dataBuf); jmpClose(plr); jvmClose(dev); jssFreeModule(mod); jssClose(); return res; }