view ppl.c @ 99:7a59611f9d4f

Various minor improvements in PPL.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 02 Oct 2012 20:26:08 +0300
parents 5cefa59baa9d
children 1d7dc7c8745c
line wrap: on
line source

#include "jss.h"
#include "jssmod.h"
#include "jssmix.h"
#include "jssplr.h"

#include "dmlib.h"
#include "dmargs.h"
#include "dmimage.h"
#include "dmtext.h"

#include <errno.h>

struct
{
    BOOL exitFlag;
    SDL_Surface *screen;
    SDL_Event event;
    int optScrWidth, optScrHeight, optVFlags, optScrDepth;
} engine;

struct
{
    Uint32 boxBg, inboxBg, box1, box2, viewDiv, activeRow;
} col;


DMBitmapFont *font = NULL;

char    *optFilename = NULL,
        *optFontFilename = "c64font.png";
int     optOutFormat = JSS_AUDIO_S16,
        optOutChannels = 2,
        optOutFreq = 48000,
        optMuteOChannels = -1,
        optStartOrder = -1;
BOOL    optUsePlayTime = FALSE;
size_t  optPlayTime;


DMOptArg optList[] =
{
    {  0, '?', "help",     "Show this help", OPT_NONE },
    {  1, 'v', "verbose",  "Be more verbose", OPT_NONE },
    {  2,   0, "fs",       "Fullscreen", OPT_NONE },
    {  3, 'w', "window",   "Initial window size/resolution -w 640x480", OPT_ARGREQ },

    {  4, '1', "16bit",    "16-bit output", OPT_NONE },
    {  5, '8', "8bit",     "8-bit output", OPT_NONE },
    {  6, 'm', "mono",     "Mono output", OPT_NONE },
    {  7, 's', "stereo",   "Stereo output", OPT_NONE },
    {  8, 'f', "freq",     "Output frequency", OPT_ARGREQ },

    {  9, 'M', "mute",     "Mute other channels than #", OPT_ARGREQ },
    { 10, 'o', "order",    "Start from order #", OPT_ARGREQ },
    { 11, 't', "time",     "Play for # seconds", OPT_ARGREQ },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    dmPrintBanner(stdout, dmProgName, "[options] <module>");
    dmArgsPrintHelp(stdout, optList, optListN);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN) {
        case 0:
            argShowHelp();
            exit(0);
            break;

        case 1:
            dmVerbosity++;
            break;
        
        case 2:
            engine.optVFlags |= SDL_FULLSCREEN;
            break;

        case 3:
            {
                int w, h;
                if (sscanf(optArg, "%dx%d", &w, &h) == 2)
                {
                    if (w < 320 || h < 200 || w > 3200 || h > 3200)
                    {
                        dmError("Invalid width or height: %d x %d\n", w, h);
                        return FALSE;
                    }
                    engine.optScrWidth = w;
                    engine.optScrHeight = h;
                }
                else 
                {
                    dmError("Invalid size argument '%s'.\n", optArg);
                    return FALSE;
                }
            }
            break;

        case 4:
            optOutFormat = JSS_AUDIO_S16;
            break;

        case 5:
            optOutFormat = JSS_AUDIO_U8;
            break;

        case 6:
            optOutChannels = JSS_AUDIO_MONO;
            break;

        case 7:
            optOutChannels = JSS_AUDIO_STEREO;
            break;

        case 8:
            optOutFreq = atoi(optArg);
            break;

        case 9:
            optMuteOChannels = atoi(optArg);
            break;

        case 10:
            optStartOrder = atoi(optArg);
            break;

        case 11:
            optPlayTime = atoi(optArg);
            optUsePlayTime = TRUE;
            break;

        default:
            dmError("Unknown option '%s'.\n", currArg);
            return FALSE;
    }
    
    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (!optFilename)
        optFilename = currArg;
    else
    {
        dmError("Too many filename arguments '%s'\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


Uint32 dmCol(float r, float g, float b)
{
    return dmMapRGB(engine.screen, 255.0f * r, 255.0f * g, 255.0f * b);
}

BOOL dmInitializeVideo()
{
    engine.screen = SDL_SetVideoMode(
        engine.optScrWidth, engine.optScrHeight, engine.optScrDepth,
        engine.optVFlags | SDL_RESIZABLE | SDL_SWSURFACE | SDL_HWPALETTE);

    if (engine.screen == NULL)
    {
        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
        return FALSE;
    }

    col.inboxBg    = dmCol(0.6, 0.5, 0.2);
    col.boxBg      = dmCol(0.7, 0.6, 0.3);
    col.box1       = dmCol(1.0, 0.9, 0.6);
    col.box2       = dmCol(0.3, 0.3, 0.15);
    col.viewDiv    = dmCol(0,0,0);
    col.activeRow  = dmCol(0.5,0.4,0.1);

    return TRUE;
}


static const char patNoteTable[12][3] =
{
    "C-", "C#", "D-",
    "D#", "E-", "F-",
    "F#", "G-", "G#",
    "A-", "A#", "B-"
};


#define jmpNMODEffectTable (36)
static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void dmPrintNote(SDL_Surface *screen, int xc, int yc, JSSNote *n)
{
    char text[32];
    char *ptr = text;
    
    switch (n->note)
    {
        case jsetNotSet:
            sprintf(ptr, "..._");
            break;
        case jsetNoteOff:
            sprintf(ptr, "===_");
            break;
        default:
            sprintf(ptr, "%s%i_",
                patNoteTable[n->note % 12],
                n->note / 12);
            break;
    }

    ptr += 4;

    if (n->instrument != jsetNotSet)
        sprintf(ptr, "%.2x_", n->instrument + 1);
    else
        sprintf(ptr, ".._");

    ptr += 3;

    if (n->volume == jsetNotSet)
        sprintf(ptr, ".._");
    else
    if (n->volume >= 0x00 && n->volume <= 0x40)
        sprintf(ptr, "%.2x_", n->volume);
    else
    {
        char c;
        switch (n->volume & 0xf0)
        {
            case 0x50: c = '-'; break;
            case 0x60: c = '+'; break;
            case 0x70: c = '/'; break;
            case 0x80: c = '\\'; break;
            case 0x90: c = 'S'; break;
            case 0xa0: c = 'V'; break;
            case 0xb0: c = 'P'; break;
            case 0xc0: c = '<'; break;
            case 0xd0: c = '>'; break;
            case 0xe0: c = 'M'; break;
            default:   c = '?'; break;
        }
        sprintf(ptr, "%c%x_", c, (n->volume & 0x0f));
    }
    
    ptr += 3;
    
    if (n->effect >= 0 && n->effect < jmpNMODEffectTable)
        *ptr = jmpMODEffectTable[n->effect];
    else
    if (n->effect == jsetNotSet)
        *ptr = '.';
    else
        *ptr = '?';

    ptr += 1;

    if (n->param != jsetNotSet)
        sprintf(ptr, "%.2x", n->param);
    else
        sprintf(ptr, "..");

    dmDrawBMTextConst(screen, font, DMD_TRANSPARENT, xc, yc, text);
}


void dmDisplayPattern(SDL_Surface *screen, int x0, int y0, int x1, int y1, JSSPattern *pat, int row, int choffs)
{
    int cwidth = (font->width * 10 + 3 * 4 + 5),
        lwidth = 6 + font->width * 3,
        qwidth  = ((x1 - x0 - lwidth) / cwidth),
        qheight = ((y1 - y0 - 4) / (font->height + 1)),
        nrow, nchannel, yc,
        midrow = qheight / 2;
    
    dmDrawBox3D(screen, x0 + lwidth, y0, x1, y1,
        col.inboxBg, col.box2, col.box1);

    yc = y0 + 2 + (font->height + 1) * midrow;
    dmFillRect(screen, x0 + lwidth + 1, yc - 1, x1 - 1, yc + font->height, col.activeRow);

    for (nchannel = 1; nchannel < qwidth; nchannel++)
    {
        dmDrawVLine(screen, y0 + 1, y1 - 1, x0 + lwidth + 1 + nchannel * cwidth, col.viewDiv);
    }
    
    for (nrow = 0; nrow < qheight; nrow++)
    {
        int crow = nrow - midrow + row;
        yc = y0 + 2 + (font->height + 1) * nrow;
        
        if (crow >= 0 && crow < pat->nrows)
        {
            dmDrawBMText(screen, font, DMD_TRANSPARENT, x0, yc, "%03d", crow);

            for (nchannel = 0; nchannel < qwidth; nchannel++)
            {
                if (choffs + nchannel >= pat->nchannels)
                    break;

                dmPrintNote(screen, x0 + lwidth + 4 + nchannel * cwidth, yc,
                    pat->data + (pat->nchannels * crow) + choffs + nchannel);
            }
        }
    }
}


void audioCallback(void *userdata, Uint8 *stream, int len)
{
    JSSMixer *d = (JSSMixer *) userdata;

    if (d != NULL)
    {
        jvmRenderAudio(d, stream, len / jvmGetSampleSize(d));
    }
}


int main(int argc, char *argv[])
{
    BOOL initSDL = FALSE;
    DMResource *file = NULL;
    JSSModule *mod = NULL;
    JSSMixer *dev = NULL;
    JSSPlayer *plr = NULL;
    SDL_AudioSpec afmt;
    int result = -1;

    memset(&afmt, 0, sizeof(afmt));
    memset(&engine, 0, sizeof(engine));

    engine.optScrWidth = 640;
    engine.optScrHeight = 480;
    engine.optScrDepth = 32;

    dmInitProg("ppl", "Penis Player", "0.1", NULL, NULL);

    // Parse arguments
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, TRUE))
        exit(1);

    // Open the files
    if (optFilename == NULL)
    {
        dmError("No filename specified.\n");
        return 1;
    }
    
    if ((file = dmf_create_stdio(optFilename, "rb")) == NULL)
    {
        dmError("Error opening file '%s', %d: (%s)\n",
            optFilename, errno, strerror(errno));
        return 1;
    }

    // Initialize miniJSS
    jssInit();

    // Read module file
    dmMsg(1, "Reading file: %s\n", optFilename);
#ifdef JSS_SUP_XM
    dmMsg(2, "* Trying XM...\n");
    result = jssLoadXM(file, &mod);
#endif
#ifdef JSS_SUP_JSSMOD
    if (result != 0)
    {
        size_t bufgot, bufsize = dmfsize(file);
        Uint8 *buf = dmMalloc(bufsize);
        dmfseek(file, 0L, SEEK_SET);
        dmMsg(2, "* Trying JSSMOD (%d bytes, %p)...\n", bufsize, buf);
        if ((bufgot = dmfread(buf, 1, bufsize, file)) != bufsize)
        {
            dmf_close(file);
            dmError("Error reading file (not enough data %d), #%d: %s\n",
                bufgot, dmferror(file), dmErrorStr(dmferror(file)));
            goto error_exit;
        }
        result = jssLoadJSSMOD(buf, bufsize, &mod);
        dmFree(buf);
    }
#endif
    dmf_close(file);

    if (result != DMERR_OK)
    {
        dmError("Error loading module file, %d: %s\n",
            result, dmErrorStr(result));
        goto error_exit;
    }

    // Try to convert it
    if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK)
    {
        dmError("Could not convert module for playing, %d: %s\n",
            result, dmErrorStr(result));
        goto error_exit;
    }

#if 0
    if ((file = dmf_create_stdio(optFontFilename, "rb")) == NULL)
    {
        dmError("Error opening font file '%s', %d: %s\n",
            optFontFilename, errno, strerror(errno));
        goto error_exit;
    }
    SDL_Surface *fontbmap = dmLoadImage(file);
    dmf_close(file);
    if (fontbmap == NULL)
    {
        dmError("Could not load image file '%s'.\n", optFontFilename);
        goto error_exit;
    }

    if ((result = dmCreateBitmapFontFromImage(fontbmap, 8, 8, &font)) != DMERR_OK)
    {
        dmError("Could not create a font from image, %d: %s\n",
            result, dmErrorStr(result));
        goto error_exit;
    }
    SDL_FreeSurface(fontbmap);
#else
    if ((file = dmf_create_stdio("fnsmall.fnt", "rb")) == NULL)
    {
        dmError("Error opening font file '%s', %d: %s\n",
            optFontFilename, errno, strerror(errno));
        goto error_exit;
    }
    result = dmLoadBitmapFont(file, &font);
    dmf_close(file);
    if (result != DMERR_OK)
    {
        dmError("Could not load font from file, %d: %s\n",
            result, dmErrorStr(result));
        goto error_exit;
    }
#endif

    // Initialize SDL components
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = TRUE;


    // Initialize mixing device
    dmMsg(2, "Initializing miniJSS mixer with: %d, %d, %d\n",
        optOutFormat, optOutChannels, optOutFreq);

    dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO);
    if (dev == NULL)
    {
        dmError("jvmInit() returned NULL\n");
        goto error_exit;
    }
    
    switch (optOutFormat)
    {
        case JSS_AUDIO_S16: afmt.format = AUDIO_S16SYS; break;
        case JSS_AUDIO_U16: afmt.format = AUDIO_U16SYS; break;
        case JSS_AUDIO_S8:  afmt.format = AUDIO_S8; break;
        case JSS_AUDIO_U8:  afmt.format = AUDIO_U8; break;
        default:
            dmError("Unsupported audio format %d (could not set matching SDL format)\n",
                optOutFormat);
            goto error_exit;
    }

    afmt.freq     = optOutFreq;
    afmt.channels = optOutChannels;
    afmt.samples  = optOutFreq * optOutChannels * 4;
    afmt.callback = audioCallback;
    afmt.userdata = (void *) dev;

    // Open the audio device
    if (SDL_OpenAudio(&afmt, NULL) < 0)
    {
        dmError("Couldn't open SDL audio: %s\n",
            SDL_GetError());
        goto error_exit;
    }
    
    // Initialize player
    if ((plr = jmpInit(dev)) == NULL)
    {
        dmError("jmpInit() returned NULL\n");
        goto error_exit;
    }
    
    jvmSetCallback(dev, jmpExec, plr);
    jmpSetModule(plr, mod);
    jmpPlayOrder(plr, 0);
    jvmSetGlobalVol(dev, 60);

    // Initialize video
    if (!dmInitializeVideo())
        goto error_exit;

    SDL_WM_SetCaption(dmProgDesc, dmProgName);


    // okay, main loop here ... "play" module and print out info
    SDL_PauseAudio(0);

    while (!engine.exitFlag && plr->isPlaying)
    {
        while (SDL_PollEvent(&engine.event))
        switch (engine.event.type)
        {
            case SDL_KEYDOWN:
                switch (engine.event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                        engine.exitFlag = TRUE;
                        break;
                    
                    default:
                        break;
                }

                break;

            case SDL_VIDEORESIZE:
                engine.optScrWidth = engine.event.resize.w;
                engine.optScrHeight = engine.event.resize.h;

                if (!dmInitializeVideo())
                    goto error_exit;

                break;

            case SDL_VIDEOEXPOSE:
                break;

            case SDL_QUIT:
                engine.exitFlag = TRUE;
                break;
        }

        // Draw frame
        if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
        {
            dmError("Can't lock surface.\n");
            goto error_exit;
        }
        
        dmDrawBox3D(engine.screen, 0, 0, engine.screen->w - 1, engine.screen->h - 1,
            col.boxBg, col.box1, col.box2);
        
        dmDrawBMText(engine.screen, font, DMD_TRANSPARENT, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012 TNSP", dmProgDesc, dmProgVersion);


        JSS_LOCK(dev);
        JSS_LOCK(plr);

        dmDrawBMText(engine.screen, font, DMD_TRANSPARENT, 5, 5 + 12,
            "Tempo: %3d | Speed: %3d | Row: %3d | Order: %d",
            plr->tempo, plr->speed, plr->row, plr->order
            );

        dmDisplayPattern(engine.screen, 5, 30, engine.screen->w - 6, engine.screen->h - 10,
            plr->pattern, plr->row, 0);
        JSS_UNLOCK(plr);
        JSS_UNLOCK(dev);

        // Flip screen
        if (SDL_MUSTLOCK(engine.screen) != 0)
            SDL_UnlockSurface(engine.screen);

        SDL_Flip(engine.screen);
        SDL_Delay(10);
    }

error_exit:
    SDL_PauseAudio(1);
    
    if (engine.screen)
        SDL_FreeSurface(engine.screen);

    SDL_LockAudio();
    jmpClose(plr);
    jvmClose(dev);
    jssFreeModule(mod);
    SDL_UnlockAudio();

    dmFreeBitmapFont(font);

    if (initSDL)
        SDL_Quit();

    jssClose();

    return 0;
}