Mercurial > hg > dmlib
view tools/mod2wav.c @ 2208:90ec1ec89c56
Revamp the palette handling in lib64gfx somewhat, add helper functions to
lib64util for handling external palette file options and add support for
specifying one of the "internal" palettes or external (.act) palette file to
gfxconv and 64vw.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 14 Jun 2019 05:01:12 +0300 |
parents | e3f0eaf23f4f |
children | c146033f1f6a |
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("Unimplemented option 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; }