Mercurial > hg > dmlib
view tools/ppl.c @ 1915:788cfc7096f3
Add support for Gigapaint hires (unpacked) format.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 27 Jun 2018 15:43:20 +0300 |
parents | 8af6067b6bd7 |
children | 6147dd3fa5d2 |
line wrap: on
line source
/* * Cyrbe Pasci Player - A simple SDL-based UI for XM module playing * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2018 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "dmlib.h" #include <SDL.h> #include "libgutil.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_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; 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, scope, muted, black, red; } 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; static const 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, 0); } 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_WINDOW_FULLSCREEN_DESKTOP; break; case 3: { int w, h; if (sscanf(optArg, "%dx%d", &w, &h) == 2) { if (w < 320 || h < 200 || w > 3200 || h > 3200) { dmErrorMsg("Invalid width or height: %d x %d\n", w, h); return FALSE; } engine.optScrWidth = w; engine.optScrHeight = h; } else { dmErrorMsg("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: dmErrorMsg("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optFilename) optFilename = currArg; else { dmErrorMsg("Too many filename arguments '%s'\n", currArg); return FALSE; } return TRUE; } static inline Uint32 dmCol(const float r, const float g, const float b) { return dmMapRGB(engine.screen, 255.0f * r, 255.0f * g, 255.0f * b); } BOOL dmInitializeVideo() { SDL_DestroyTexture(engine.texture); SDL_FreeSurface(engine.screen); if ((engine.texture = SDL_CreateTexture(engine.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, engine.optScrWidth, engine.optScrHeight)) == NULL) { dmErrorMsg("Could not create SDL texture.\n"); return FALSE; } if ((engine.screen = SDL_CreateRGBSurfaceWithFormat(0, engine.optScrWidth, engine.optScrHeight, 32, SDL_PIXELFORMAT_RGBA32)) == NULL) { dmErrorMsg("Could not create SDL surface.\n"); return FALSE; } col.black = dmCol(0 , 0 , 0); 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); col.muted = dmCol(0.3, 0.1, 0.1); col.scope = dmCol(0 , 0.8, 0); col.red = dmCol(1 , 0 , 0); return TRUE; } // // XXX TODO: To display actual continuous sample data for channel, // we would need to have separate "FIFO" buffers for each, updating // them with new data from incoming channel data. // 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_GETH32(chn->chVolume); DMFixedPoint offs = chn->chPos; int len = FP_GETH32(chn->chSize); Uint32 pitch = screen->pitch / sizeof(Uint32); Uint32 *pix = screen->pixels; Sint16 *data = chn->chData; dmFillBox3D(screen, x0, y0, x1, y1, (chn->chMute ? col.muted : col.black), nchannel == engine.actChannel ? col.red : col.box2, nchannel == engine.actChannel ? col.red : col.box1); if (chn->chData == NULL || !chn->chPlaying) return; if (chn->chDirection) { for (xc = x0 + 1; xc < x1 - 1; xc++) { if (FP_GETH32(offs) >= len) break; Sint16 val = ym + (data[FP_GETH32(offs)] * yh * vol) / (65535 * 255); pix[xc + val * pitch] = col.scope; FP_ADD(offs, chn->chDeltaO); } } else { for (xc = x0 + 1; xc < x1 - 1; xc++) { if (FP_GETH32(offs) < 0) break; Sint16 val = ym + (data[FP_GETH32(offs)] * yh * vol) / (65535 * 255); pix[xc + val * pitch] = col.scope; 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 (pat == NULL) return; 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; dmFillRect(screen, bx0+1, qy0+1, bx1-1, qy1-1, (engine.actChannel == nchannel + choffs) ? col.activeChannel : 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; dmMemset(&engine, 0, sizeof(engine)); engine.optScrWidth = 640; engine.optScrHeight = 480; engine.optScrDepth = 32; dmInitProg("CBP", "Cyrbe Basci Player", "0.2", NULL, NULL); // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); // Open the files if (optFilename == NULL) { dmErrorMsg("No filename specified.\n"); return 1; } if ((result = dmf_open_stdio(optFilename, "rb", &file)) != DMERR_OK) { dmErrorMsg("Error opening file '%s', %d: (%s)\n", optFilename, result, dmErrorStr(result)); return 1; } // Initialize miniJSS jssInit(); // Read module file dmMsg(1, "Reading file: %s\n", optFilename); #ifdef JSS_SUP_XM result = jssLoadXM(file, &engine.mod, TRUE); #endif #ifdef JSS_SUP_JSSMOD dmfreset(file); if (result != DMERR_OK) { dmMsg(1, "* Trying JSSMOD ...\n"); result = jssLoadJSSMOD(file, &engine.mod, TRUE); dmfreset(file); if (result == DMERR_OK) result = jssLoadJSSMOD(file, &engine.mod, FALSE); } else { dmMsg(2, "* Trying XM...\n"); result = jssLoadXM(file, &engine.mod, FALSE); } #endif dmf_close(file); if (result != DMERR_OK) { dmErrorMsg("Error loading module file, %d: %s\n", result, dmErrorStr(result)); goto error_exit; } // Try to convert it if ((result = jssConvertModuleForPlaying(engine.mod)) != DMERR_OK) { dmErrorMsg("Could not convert module for playing, %d: %s\n", result, dmErrorStr(result)); goto error_exit; } // Get font result = dmf_open_memio(NULL, "pplfont.fnt", engineSetupFont, sizeof(engineSetupFont), &file); if (result != DMERR_OK) { dmErrorMsg("Error opening font file 'pplfont.fnt', #%d: %s\n", result, dmErrorStr(result)); goto error_exit; } result = dmLoadBitmapFont(file, &font); dmf_close(file); if (result != DMERR_OK) { dmErrorMsg("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) { dmErrorMsg("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) { dmErrorMsg("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: dmErrorMsg("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) { dmErrorMsg("Couldn't open SDL audio: %s\n", SDL_GetError()); goto error_exit; } audioInit = TRUE; // Initialize player if ((engine.plr = jmpInit(engine.dev)) == NULL) { dmErrorMsg("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, 255); if (optMuteOChannels >= 0 && optMuteOChannels < engine.mod->nchannels) { dmMuteChannels(TRUE); jvmMute(engine.dev, optMuteOChannels, FALSE); engine.actChannel = optMuteOChannels; muteState = TRUE; } // Open window if ((engine.window = SDL_CreateWindow(dmProgName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, engine.optScrWidth, engine.optScrHeight, engine.optVFlags | SDL_WINDOW_RESIZABLE //| SDL_WINDOW_HIDDEN )) == NULL) { dmErrorMsg("Can't create an SDL window: %s\n", SDL_GetError()); goto error_exit; } SDL_SetWindowTitle(engine.window, dmProgDesc); if ((engine.renderer = SDL_CreateRenderer(engine.window, -1, SDL_RENDERER_PRESENTVSYNC)) == NULL) { dmErrorMsg("Can't create an SDL renderer: %s\n", SDL_GetError()); goto error_exit; } if (!dmInitializeVideo()) goto error_exit; // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); // 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 needUpdate = (currTick - prevTick > 500), needRender = FALSE; while (SDL_PollEvent(&engine.event)) switch (engine.event.type) { case SDL_KEYDOWN: switch (engine.event.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_q: 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--; needUpdate = TRUE; } break; case SDLK_RIGHT: if (engine.actChannel < engine.mod->nchannels - 1) { engine.actChannel++; needUpdate = 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)); } needUpdate = 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); needUpdate = 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); needUpdate = TRUE; break; case SDLK_f: engine.optVFlags ^= SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(engine.window, engine.optVFlags) != 0) goto error_exit; needUpdate = TRUE; break; default: break; } break; case SDL_WINDOWEVENT: switch (engine.event.window.event) { case SDL_WINDOWEVENT_EXPOSED: needUpdate = TRUE; break; case SDL_WINDOWEVENT_RESIZED: engine.optScrWidth = engine.event.window.data1; engine.optScrHeight = engine.event.window.data2; if (!dmInitializeVideo()) goto error_exit; needUpdate = TRUE; break; } 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 || needUpdate) { prevRow = currRow; needUpdate = TRUE; } // Draw frame if (needUpdate) { dmClearSurface(engine.screen, col.boxBg); dmDrawBMTextQ(engine.screen, font, DMD_TRANSPARENT, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012-2018 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 != NULL) ? engine.plr->pattern->nrows : 0, engine.plr->order + 1, engine.mod->norders, engine.plr->npattern, engine.mod->npatterns); JSS_UNLOCK(engine.plr); needRender = TRUE; } if (needUpdate || 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); needRender = TRUE; } if (needUpdate) prevTick = currTick; #endif // Flip screen if (needRender) { SDL_Surface dst; SDL_LockTexture(engine.texture, NULL, &dst.pixels, &dst.pitch); if (dst.pitch != engine.screen->pitch) engine.exitFlag = TRUE; else memcpy(dst.pixels, engine.screen->pixels, engine.screen->h * dst.pitch); SDL_UnlockTexture(engine.texture); //SDL_RenderClear(engine.renderer); SDL_RenderCopy(engine.renderer, engine.texture, NULL, NULL); SDL_RenderPresent(engine.renderer); } SDL_Delay(engine.pauseFlag ? 100 : 30); } error_exit: if (engine.texture != NULL) SDL_DestroyTexture(engine.texture); if (engine.renderer != NULL) SDL_DestroyRenderer(engine.renderer); if (engine.window != NULL) SDL_DestroyWindow(engine.window); if (engine.screen != NULL) 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; }