view mod2wav.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents 4972ca91d062
children
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 <stdlib.h>
#include <errno.h>
#include "jss.h"
#include "jssmod.h"
#include "jssmix.h"
#include "jssplr.h"
#include "dmlib.h"
#include "dmargs.h"
#include "dmwav.h"
#include "dmmutex.h"


char    *optInFilename = NULL, *optOutFilename = 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 (!optInFilename)
        optInFilename = currArg;
    else
    if (!optOutFilename)
        optOutFilename = currArg;
    else
    {
        dmError("Too many filename arguments (only source and dest needed) '%s'\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


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, TRUE))
        exit(1);

    // Check arguments
    if (optInFilename == NULL || optOutFilename == NULL)
    {
        dmError("Input or output file not specified. Try --help.\n");
        return 1;
    }
    
    // Initialize miniJSS
    jssInit();
    
    // Open the source file
    if ((inFile = dmf_create_stdio(optInFilename, "rb")) == NULL)
    {
        dmError("Error opening input file '%s', %d: %s\n",
            optInFilename, errno, strerror(errno));
        return 1;
    }

    // Read module file
    fprintf(stderr, "Reading file: %s\n", optInFilename);
#ifdef JSS_SUP_XM
    fprintf(stderr, "* Trying XM...\n");
    result = jssLoadXM(inFile, &mod);
#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, &mod);
        dmFree(buf);
    }
#endif
    dmf_close(inFile);
    if (result != DMERR_OK)
    {
        dmError("Error loading module file, %d: %s\n",
            result, dmErrorStr(result));
        return 3;
    }

    // Try to convert it
    if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK)
    {
        dmError("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)
    {
        dmError("jvmInit() returned NULL\n");
        return 4;
    }

    sampSize = jvmGetSampleSize(dev);
    if ((mb = dmMalloc(bufLen * sampSize)) == NULL)
    {
        dmError("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)
    {
        dmError("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)
    {
        dmError("Error opening output file '%s'. (%s)\n", optInFilename, strerror(errno));
        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)
            {
                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;
    }
    
    dmWriteWAVHeader(outFile, jvmGetSampleRes(dev), optOutFreq, optOutChannels, dataTotal);
    
    // Done!
    fclose(outFile);

    jmpClose(plr);
    jvmClose(dev);
    jssFreeModule(mod);
    jssClose();

    dmMsg(1, "OK.\n");
    return 0;
}