Mercurial > hg > dmlib
diff tools/ppl.c @ 652:d9888292f971
Rename again.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 16 Apr 2013 06:04:22 +0300 |
parents | utils/ppl.c@e2ac08228a0f |
children | 3d813c81f33c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/ppl.c Tue Apr 16 06:04:22 2013 +0300 @@ -0,0 +1,932 @@ +/* + * Cyrbe Pasci Player - A simple SDL-based UI for XM module playing + * Programmed and designed by Matti 'ccr' Hamalainen + * (C) Copyright 2012 Tecnic Software productions (TNSP) + * + * Please read file 'COPYING' for information on license and distribution. + */ +#include <SDL.h> +#include "dmlib.h" + +#include "jss.h" +#include "jssmod.h" +#include "jssmix.h" +#include "jssplr.h" + +#include "dmargs.h" +#include "dmimage.h" +#include "dmtext.h" + +#include "setupfont.h" + + +struct +{ + BOOL exitFlag; + SDL_Surface *screen; + SDL_Event event; + int optScrWidth, optScrHeight, optVFlags, optScrDepth; + + int actChannel; + BOOL pauseFlag; + + JSSModule *mod; + JSSMixer *dev; + JSSPlayer *plr; + SDL_AudioSpec afmt; +} engine; + +struct +{ + Uint32 boxBg, inboxBg, box1, box2, viewDiv, activeRow, activeChannel; +} col; + + +DMBitmapFont *font = NULL; + +char *optFilename = NULL; +int optOutFormat = JSS_AUDIO_S16, + optOutChannels = 2, + optOutFreq = 48000, + optMuteOChannels = -1, + optStartOrder = 0; +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; +} + + +void dmDrawBMTextConstQ(SDL_Surface *screen, DMBitmapFont *font, int mode, int xc, int yc, const char *fmt) +{ + const char *ptr = fmt; + DMUnscaledBlitFunc blit = NULL; + + while (*ptr) + { + int ch = *ptr++; + SDL_Surface *glyph; + + if (ch == '_') + { + xc += 4; + continue; + } + + if (ch >= 0 && ch < font->nglyphs && (glyph = font->glyphs[ch]) != NULL) + { + if (blit == NULL) + blit = dmGetUnscaledBlitFunc(glyph->format, screen->format, mode); + + blit(glyph, xc, yc, screen); + xc += font->width; + } + else + xc += font->width; + } +} + + +void dmDrawBMTextVAQ(SDL_Surface *screen, DMBitmapFont *font, int mode, int xc, int yc, const char *fmt, va_list ap) +{ + char tmp[512]; + vsnprintf(tmp, sizeof(tmp), fmt, ap); + dmDrawBMTextConstQ(screen, font, mode, xc, yc, tmp); +} + + +void dmDrawBMTextQ(SDL_Surface *screen, DMBitmapFont *font, int mode, int xc, int yc, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + dmDrawBMTextVAQ(screen, font, mode, xc, yc, fmt, ap); + va_end(ap); +} + + +Uint32 dmCol(float r, float g, float b) +{ + return dmMapRGB(engine.screen, 255.0f * r, 255.0f * g, 255.0f * b); +} + + +BOOL dmInitializeVideo() +{ + SDL_FreeSurface(engine.screen); + + 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); + col.activeChannel = dmCol(0.6, 0.8, 0.2); + + return TRUE; +} + + +void dmDisplayChn(SDL_Surface *screen, int x0, int y0, int x1, int y1, int nchannel, JSSChannel *chn) +{ + int yh = y1 - y0 - 2; + if (yh < 10 || chn == NULL) + return; + + int xc, ym = y0 + (y1 - y0) / 2, vol = FP_GETH(chn->chVolume); + int pitch = screen->pitch / sizeof(Uint32); + int len = FP_GETH(chn->chSize); + DMFixedPoint offs = chn->chPos; + Uint32 coln = dmCol(0.0, 0.8, 0.0), colx = dmCol(1.0, 0, 0); + Uint32 *pix = screen->pixels; + Sint16 *data = chn->chData; + + + dmFillBox3D(screen, x0, y0, x1, y1, + (chn->chMute ? dmCol(0.3,0.1,0.1) : dmCol(0,0,0)), + nchannel == engine.actChannel ? colx : col.box2, + nchannel == engine.actChannel ? colx : col.box1); + + if (chn->chData == NULL || !chn->chPlaying) + return; + + if (chn->chDirection) + { + for (xc = x0 + 1; xc < x1 - 1; xc++) + { + if (FP_GETH(offs) >= len) + break; + Sint16 val = ym + (data[FP_GETH(offs)] * yh * vol) / (65535 * 255); + pix[xc + val * pitch] = coln; + FP_ADD(offs, chn->chDeltaO); + } + } + else + { + for (xc = x0 + 1; xc < x1 - 1; xc++) + { + if (FP_GETH(offs) < 0) + break; + Sint16 val = ym + (data[FP_GETH(offs)] * yh * vol) / (65535 * 255); + pix[xc + val * pitch] = coln; + FP_SUB(offs, chn->chDeltaO); + } + } +} + + +void dmDisplayChannels(SDL_Surface *screen, int x0, int y0, int x1, int y1, JSSMixer *dev) +{ + int nchannel, qx, qy, + qwidth = x1 - x0, + qheight = y1 - y0, + nwidth = jsetNChannels, + nheight = 1; + + if (qheight < 40) + return; + + while (qwidth / nwidth <= 60 && qheight / nheight >= 40) + { + nheight++; + nwidth /= nheight; + } + +// fprintf(stderr, "%d x %d\n", nwidth, nheight); + + if (qheight / nheight <= 40) + { + nwidth = qwidth / 60; + nheight = qheight / 40; + } + + qwidth /= nwidth; + qheight /= nheight; + + for (nchannel = qy = 0; qy < nheight && nchannel < jsetNChannels; qy++) + { + for (qx = 0; qx < nwidth && nchannel < jsetNChannels; qx++) + { + int xc = x0 + qx * qwidth, + yc = y0 + qy * qheight; + + dmDisplayChn(screen, xc + 1, yc + 1, + xc + qwidth - 1, yc + qheight - 1, + nchannel, &dev->channels[nchannel]); + + nchannel++; + } + } +} + + +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"; + +static const char jmpHexTab[16] = "0123456789ABCDEF"; + +static inline char dmHexVal(int v) +{ + return jmpHexTab[v & 15]; +} + +void dmPrintNote(SDL_Surface *screen, int xc, int yc, JSSNote *n) +{ + char text[32]; + char *ptr = text; + + switch (n->note) + { + case jsetNotSet: + strcpy(ptr, "..._"); + break; + case jsetNoteOff: + strcpy(ptr, "===_"); + break; + default: + sprintf(ptr, "%s%i_", + patNoteTable[n->note % 12], + n->note / 12); + break; + } + + ptr += 4; + + if (n->instrument != jsetNotSet) + { + int v = n->instrument + 1; + *ptr++ = dmHexVal(v >> 4); + *ptr++ = dmHexVal(v); + } + else + { + *ptr++ = '.'; + *ptr++ = '.'; + } + *ptr++ = '_'; + + if (n->volume == jsetNotSet) + { + *ptr++ = '.'; + *ptr++ = '.'; + } + else + if (n->volume >= 0x00 && n->volume <= 0x40) + { + *ptr++ = dmHexVal(n->volume >> 4); + *ptr++ = dmHexVal(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; + } + *ptr++ = c; + *ptr++ = dmHexVal(n->volume); + } + *ptr++ = '_'; + + if (n->effect >= 0 && n->effect < jmpNMODEffectTable) + *ptr++ = jmpMODEffectTable[n->effect]; + else + *ptr++ = (n->effect == jsetNotSet ? '.' : '?'); + + if (n->param != jsetNotSet) + { + *ptr++ = dmHexVal(n->param >> 4); + *ptr++ = dmHexVal(n->param); + } + else + { + *ptr++ = '.'; + *ptr++ = '.'; + } + + *ptr = 0; + + dmDrawBMTextConstQ(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 cwidth = (font->width * 10 + 3 * 4 + 5), + lwidth = 6 + font->width * 3, + qy0 = y0 + font->height + 2, + qy1 = y1 - font->height - 2, + qwidth = ((x1 - x0 - lwidth) / cwidth), + qheight = ((qy1 - qy0 - 4) / (font->height + 1)), + nrow, nchannel, yc, choffs, + midrow = qheight / 2; + + if (engine.actChannel < qwidth / 2) + choffs = 0; + else + if (engine.actChannel >= pat->nchannels - qwidth/2) + choffs = pat->nchannels - qwidth; + else + choffs = engine.actChannel - qwidth/2; + + dmDrawBox3D(screen, x0 + lwidth, qy0, x1, qy1, col.box2, col.box1); + + for (nchannel = 0; nchannel < qwidth; nchannel++) + { + int bx0 = x0 + lwidth + 1 + nchannel * cwidth, + bx1 = bx0 + cwidth; + + if (engine.actChannel == nchannel + choffs) + { + dmFillRect(screen, bx0+1, qy0 + 1, bx1-1, qy1 - 1, col.activeChannel); + } + else + { + dmFillRect(screen, bx0+1, qy0 + 1, bx1-1, qy1 - 1, col.inboxBg); + } + } + + yc = qy0 + 2 + (font->height + 1) * midrow; + dmFillRect(screen, x0 + lwidth + 1, yc - 1, x1 - 1, yc + font->height, col.activeRow); + + for (nchannel = 0; nchannel < qwidth; nchannel++) + { + int bx0 = x0 + lwidth + 1 + nchannel * cwidth, + bx1 = bx0 + cwidth; + + dmDrawVLine(screen, qy0 + 1, qy1 - 1, bx1, col.viewDiv); + + if (jvmGetMute(engine.dev, nchannel + choffs)) + { + dmDrawBMTextConstQ(screen, font, DMD_TRANSPARENT, + bx0 + (cwidth - font->width * 5) / 2, qy1 + 3, "MUTED"); + } + + dmDrawBMTextQ(screen, font, DMD_TRANSPARENT, + bx0 + (cwidth - font->width * 3) / 2, y0 + 1, "%3d", + nchannel + choffs); + } + + for (nrow = 0; nrow < qheight; nrow++) + { + int crow = nrow - midrow + row; + yc = qy0 + 2 + (font->height + 1) * nrow; + + if (crow >= 0 && crow < pat->nrows) + { + dmDrawBMTextQ(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)); + } +} + + +void dmMuteChannels(BOOL mute) +{ + int i; + for (i = 0; i < engine.mod->nchannels; i++) + jvmMute(engine.dev, i, mute); +} + +int main(int argc, char *argv[]) +{ + BOOL initSDL = FALSE, audioInit = FALSE; + DMResource *file = NULL; + int result = -1; + BOOL muteState = FALSE; + + memset(&engine, 0, sizeof(engine)); + + engine.optScrWidth = 640; + engine.optScrHeight = 480; + engine.optScrDepth = 32; + + dmInitProg("CBP", "Cyrbe Basci 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) + { + int err = dmGetErrno(); + dmError("Error opening file '%s', %d: (%s)\n", + optFilename, err, dmErrorStr(err)); + 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, &engine.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, &engine.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(engine.mod)) != DMERR_OK) + { + dmError("Could not convert module for playing, %d: %s\n", + result, dmErrorStr(result)); + goto error_exit; + } + + // Get font +// file = dmf_create_stdio("fnsmall.fnt", "rb"); + file = dmf_create_memio(NULL, "pplfont.fnt", engineSetupFont, sizeof(engineSetupFont)); + if (file == NULL) + { + dmError("Error opening font file 'pplfont.fnt'.\n"); + 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; + } + + // 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); + + engine.dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); + if (engine.dev == NULL) + { + dmError("jvmInit() returned NULL\n"); + goto error_exit; + } + + switch (optOutFormat) + { + case JSS_AUDIO_S16: engine.afmt.format = AUDIO_S16SYS; break; + case JSS_AUDIO_U16: engine.afmt.format = AUDIO_U16SYS; break; + case JSS_AUDIO_S8: engine.afmt.format = AUDIO_S8; break; + case JSS_AUDIO_U8: engine.afmt.format = AUDIO_U8; break; + default: + dmError("Unsupported audio format %d (could not set matching SDL format)\n", + optOutFormat); + goto error_exit; + } + + engine.afmt.freq = optOutFreq; + engine.afmt.channels = optOutChannels; + engine.afmt.samples = optOutFreq / 16; + engine.afmt.callback = audioCallback; + engine.afmt.userdata = (void *) engine.dev; + + // Open the audio device + if (SDL_OpenAudio(&engine.afmt, NULL) < 0) + { + dmError("Couldn't open SDL audio: %s\n", + SDL_GetError()); + goto error_exit; + } + audioInit = TRUE; + + // Initialize player + if ((engine.plr = jmpInit(engine.dev)) == NULL) + { + dmError("jmpInit() returned NULL\n"); + goto error_exit; + } + + jvmSetCallback(engine.dev, jmpExec, engine.plr); + jmpSetModule(engine.plr, engine.mod); + jmpPlayOrder(engine.plr, optStartOrder); + jvmSetGlobalVol(engine.dev, 64); + + if (optMuteOChannels >= 0 && optMuteOChannels < engine.mod->nchannels) + { + dmMuteChannels(TRUE); + jvmMute(engine.dev, optMuteOChannels, FALSE); + engine.actChannel = optMuteOChannels; + muteState = TRUE; + } + + // Initialize video + if (!dmInitializeVideo()) + goto error_exit; + + SDL_WM_SetCaption(dmProgDesc, dmProgName); + + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + + // okay, main loop here ... "play" module and print out info + SDL_LockAudio(); + SDL_PauseAudio(0); + SDL_UnlockAudio(); + + int currTick, prevTick = 0, prevRow = -1; + + while (!engine.exitFlag) + { + currTick = SDL_GetTicks(); + BOOL force = (currTick - prevTick > 500), updated = FALSE; + + 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; + + case SDLK_SPACE: + engine.pauseFlag = !engine.pauseFlag; + SDL_PauseAudio(engine.pauseFlag); + break; + + case SDLK_LEFT: + if (engine.actChannel > 0) + { + engine.actChannel--; + force = TRUE; + } + break; + + case SDLK_RIGHT: + if (engine.actChannel < engine.mod->nchannels) + { + engine.actChannel++; + force = TRUE; + } + break; + + case SDLK_m: + if (engine.event.key.keysym.mod & KMOD_SHIFT) + { + muteState = !muteState; + dmMuteChannels(muteState); + } + else + if (engine.event.key.keysym.mod & KMOD_CTRL) + { + dmMuteChannels(FALSE); + } + else + { + jvmMute(engine.dev, engine.actChannel, !jvmGetMute(engine.dev, engine.actChannel)); + } + force = TRUE; + break; + + case SDLK_PAGEUP: + JSS_LOCK(engine.dev); + JSS_LOCK(engine.plr); + jmpChangeOrder(engine.plr, dmClamp(engine.plr->order - 1, 0, engine.mod->norders)); + JSS_UNLOCK(engine.plr); + JSS_UNLOCK(engine.dev); + force = TRUE; + break; + + case SDLK_PAGEDOWN: + JSS_LOCK(engine.dev); + JSS_LOCK(engine.plr); + jmpChangeOrder(engine.plr, dmClamp(engine.plr->order + 1, 0, engine.mod->norders)); + JSS_UNLOCK(engine.plr); + JSS_UNLOCK(engine.dev); + force = TRUE; + break; + + case SDLK_f: + engine.optVFlags ^= SDL_FULLSCREEN; + if (!dmInitializeVideo()) + goto error_exit; + force = 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; + } + + +#if 1 + JSS_LOCK(engine.plr); + JSSPattern *currPattern = engine.plr->pattern; + int currRow = engine.plr->row; + if (!engine.plr->isPlaying) + engine.exitFlag = TRUE; + JSS_UNLOCK(engine.plr); + + if (currRow != prevRow || force) + { + prevRow = currRow; + force = TRUE; + } + + // Draw frame + if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0) + { + dmError("Can't lock surface.\n"); + goto error_exit; + } + + if (force) + { + dmClearSurface(engine.screen, col.boxBg); + + dmDrawBMTextQ(engine.screen, font, DMD_TRANSPARENT, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012 TNSP", dmProgDesc, dmProgVersion); + + dmDrawBMTextQ(engine.screen, font, DMD_TRANSPARENT, 5, 5 + 12 + 11, + "Song: '%s'", + engine.mod->moduleName); + + dmDisplayPattern(engine.screen, 5, 40, + engine.screen->w - 6, engine.screen->h * 0.8, + currPattern, currRow); + + JSS_LOCK(engine.plr); + dmDrawBMTextQ(engine.screen, font, DMD_TRANSPARENT, 5, 5 + 12, + "Tempo: %3d | Speed: %3d | Row: %3d/%-3d | Order: %3d/%-3d | Pattern: %3d/%-3d", + engine.plr->tempo, engine.plr->speed, + engine.plr->row, engine.plr->pattern->nrows, + engine.plr->order, engine.mod->norders, + engine.plr->npattern, engine.mod->npatterns); + JSS_UNLOCK(engine.plr); + updated = TRUE; + } + + if (force || currTick - prevTick >= (engine.pauseFlag ? 100 : 20)) + { + JSS_LOCK(engine.dev); + dmDisplayChannels(engine.screen, 5, engine.screen->h * 0.8 + 5, + engine.screen->w - 5, engine.screen->h - 5, engine.dev); + JSS_UNLOCK(engine.dev); + updated = TRUE; + } + + if (force) + prevTick = currTick; + +#endif + // Flip screen + if (SDL_MUSTLOCK(engine.screen) != 0) + SDL_UnlockSurface(engine.screen); + + if (updated) + SDL_Flip(engine.screen); + + SDL_Delay(engine.pauseFlag ? 100 : 30); + } + +error_exit: + if (engine.screen) + SDL_FreeSurface(engine.screen); + + dmMsg(0, "Audio shutdown.\n"); + if (audioInit) + { + SDL_LockAudio(); + SDL_PauseAudio(1); + SDL_UnlockAudio(); + SDL_CloseAudio(); + } + + jmpClose(engine.plr); + jvmClose(engine.dev); + jssFreeModule(engine.mod); + + dmFreeBitmapFont(font); + + if (initSDL) + SDL_Quit(); + + jssClose(); + + return 0; +}