view mod2wav.c @ 0:32250b436bca

Initial re-import.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 01:54:23 +0300
parents
children feec43a3497c
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:
        optOutFormat = 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)
{
    // Was not option argument
    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[])
{
    FILE *inFile = NULL, *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 = fopen(srcFilename, "rb")) == NULL) {
        dmError("Error opening input file '%s'. (%s)\n", srcFilename, strerror(errno));
        return 2;
    }

    // Read module file
#ifdef JSS_SUP_XM
    result = jssLoadXM(inFile, &m);
#endif
    if (result != 0) {
#ifdef JSS_SUP_JSSMOD
        Uint8 *buf;
        size_t bufsize;
        fseek(inFile, 0L, SEEK_END);
        bufsize = ftell(inFile);
        fseek(inFile, 0L, SEEK_SET);
        buf = dmMalloc(bufsize);
        if (fread(buf, 1, bufsize, inFile) != bufsize) {
            dmError("Error reading file!\n");
            return 2;
        }
        result = jssLoadJSSMOD(buf, bufsize, &m);
#endif
        if (result != 0) {
            dmError("Error loading module file: %d\n", 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;
}