# HG changeset patch # User Matti Hamalainen # Date 1349088189 -10800 # Node ID b908fda1036e8b06dcc4ffff51f14669306a7837 # Parent a791146e309484a29ef33d9e15af6a1fabea12d6 Add a simplistic skeleton of a module player with SDL-based view. diff -r a791146e3094 -r b908fda1036e Makefile.gen --- a/Makefile.gen Mon Oct 01 13:42:47 2012 +0300 +++ b/Makefile.gen Mon Oct 01 13:43:09 2012 +0300 @@ -151,7 +151,7 @@ ifeq ($(DM_BUILD_TOOLS),yes) ifeq ($(DMRES_STDIO),yes) ifeq ($(SUP_MODLOAD),yes) -BINARIES+= viewmod mod2wav testpl +BINARIES+= viewmod mod2wav testpl ppl endif ifeq ($(JSS_SUP_JSSMOD),yes) @@ -289,6 +289,10 @@ @echo " LINK $+" @$(CC) -o $@ $+ $(DM_LDFLAGS) +$(BINPATH)ppl$(EXEEXT): $(OBJPATH)ppl.o $(DMLIB_A) + @echo " LINK $+" + @$(CC) -o $@ $+ $(DM_LDFLAGS) + ### ### Special targets diff -r a791146e3094 -r b908fda1036e ppl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ppl.c Mon Oct 01 13:43:09 2012 +0300 @@ -0,0 +1,592 @@ +#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 + +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] "); + 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; +} + + +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 = dmMapRGB(engine.screen, 0, 0, 0); + col.boxBg = dmMapRGB(engine.screen, 200, 100, 30); + col.box1 = dmMapRGB(engine.screen, 250, 250, 200); + col.box2 = dmMapRGB(engine.screen, 100, 50, 0); + col.viewDiv = dmMapRGB(engine.screen, 0,0,0); + col.activeRow = dmMapRGB(engine.screen, 150,80,0); + + 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_SATURATE, 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 * font->width + 5), + lwidth = 6 + font->width * 3, + qwidth = ((x1 - x0 - lwidth) / cwidth), + qheight = ((y1 - y0 - 4) / (font->height + 1)), + nrow, nchannel, + midrow = qheight / 2; + + dmDrawBox3D(screen, x0 + lwidth, y0, x1, y1, + col.boxBg, col.box2, col.box1); + + 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 yc = y0 + 2 + (font->height + 1) * nrow; + + dmDrawBMText(screen, font, DMD_SATURATE, x0, yc, "%03d", nrow); + + if (nrow == row) + { + dmFillRect(screen, x0 + lwidth + 1, yc - 1, x1 - 1, yc + font->height, col.activeRow); + } + + for (nchannel = 0; nchannel < qwidth; nchannel++) + { + if (choffs + nchannel >= pat->nchannels) + break; + + dmPrintNote(screen, x0 + lwidth + 4 + nchannel * cwidth, yc, + pat->data + (pat->nchannels * nrow) + 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)) == NULL) + { + dmError("Error opening input file '%s'. (%s)\n", + optFilename, strerror(errno)); + return 1; + } + + if ((file = dmf_create_stdio(optFilename)) == NULL) + { + dmError("Error opening input file '%s'. (%s)\n", + optFilename, 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 ((file = dmf_create_stdio(optFontFilename)) == NULL) + { + dmError("Error opening input file '%s'. (%s)\n", + optFontFilename, 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); + + + // 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_SATURATE, 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_SATURATE, 5, 5 + 9, + "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; +}