Mercurial > hg > dmlib
view tools/ppl.c @ 2576:812b16ee49db
I had been living under apparent false impression that "realfft.c"
on which the FFT implementation in DMLIB was basically copied from
was released in public domain at some point, but it could very well
be that it never was. Correct license is (or seems to be) GNU GPL.
Thus I removing the code from DMLIB, and profusely apologize to the
author, Philip Van Baren.
It was never my intention to distribute code based on his original
work under a more liberal license than originally intended.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 11 Mar 2022 16:32:50 +0200 |
parents | d56a0e86067a |
children | 9807ae37ad69 |
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-2019 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; } eng; 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, optUseGUI = TRUE; size_t optPlayTime; static const DMOptArg optList[] = { { 0, '?', "help" , "Show this help", OPT_NONE }, { 1, 0, "license" , "Print out this program's license agreement", OPT_NONE }, { 2, 'v', "verbose" , "Be more verbose", OPT_NONE }, { 10, 0, "fs" , "Fullscreen", OPT_NONE }, { 12, 'C', "cli" , "Do not open GUI window", OPT_NONE }, { 14, 'w', "window" , "Initial window size/resolution -w 640x480", OPT_ARGREQ }, { 16, '1', "16bit" , "16-bit output", OPT_NONE }, { 18, '8', "8bit" , "8-bit output", OPT_NONE }, { 20, 'm', "mono" , "Mono output", OPT_NONE }, { 22, 's', "stereo" , "Stereo output", OPT_NONE }, { 24, 'f', "freq" , "Output frequency", OPT_ARGREQ }, { 28, 'M', "mute" , "Mute other channels than #", OPT_ARGREQ }, { 30, 'o', "order" , "Start from order #", OPT_ARGREQ }, { 32, '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, 80 - 2); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmPrintLicense(stdout); exit(0); break; case 2: dmVerbosity++; break; case 10: eng.optVFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; break; case 12: optUseGUI = FALSE; break; case 14: { 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; } eng.optScrWidth = w; eng.optScrHeight = h; } else { dmErrorMsg("Invalid size argument '%s'.\n", optArg); return FALSE; } } break; case 16: optOutFormat = JSS_AUDIO_S16; break; case 18: optOutFormat = JSS_AUDIO_U8; break; case 20: optOutChannels = JSS_AUDIO_MONO; break; case 22: optOutChannels = JSS_AUDIO_STEREO; break; case 24: optOutFreq = atoi(optArg); break; case 28: optMuteOChannels = atoi(optArg); break; case 30: optStartOrder = atoi(optArg); break; case 32: optPlayTime = atoi(optArg); optUsePlayTime = TRUE; break; default: dmErrorMsg("Unimplemented option argument '%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(eng.screen, 255.0f * r, 255.0f * g, 255.0f * b); } BOOL dmInitializeVideo() { SDL_DestroyTexture(eng.texture); SDL_FreeSurface(eng.screen); if ((eng.texture = SDL_CreateTexture(eng.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, eng.optScrWidth, eng.optScrHeight)) == NULL) { dmErrorMsg("Could not create SDL texture.\n"); return FALSE; } if ((eng.screen = SDL_CreateRGBSurfaceWithFormat(0, eng.optScrWidth, eng.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 == eng.actChannel ? col.red : col.box2, nchannel == eng.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 (qwidth > pat->nchannels) qwidth = pat->nchannels; if (eng.actChannel < qwidth / 2) choffs = 0; else if (eng.actChannel >= pat->nchannels - qwidth/2) choffs = pat->nchannels - qwidth; else choffs = eng.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, (eng.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(eng.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) { for (int i = 0; i < eng.mod->nchannels; i++) jvmMute(eng.dev, i, mute); } int main(int argc, char *argv[]) { BOOL initSDL = FALSE, audioInit = FALSE; DMResource *file = NULL; int res = DMERR_OK; BOOL muteState = FALSE; memset(&eng, 0, sizeof(eng)); eng.optScrWidth = 640; eng.optScrHeight = 480; eng.optScrDepth = 32; dmInitProg("CBP", "Cyrbe Basci Player", "0.2", NULL, NULL); // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) goto out; // Open the files if (optFilename == NULL || argc < 2) { argShowHelp(); res = dmError(DMERR_INVALID_ARGS, "No filename specified.\n"); goto out; } if ((res = dmf_open_stdio(optFilename, "rb", &file)) != DMERR_OK) { dmErrorMsg("Error opening file '%s': %s\n", optFilename, dmErrorStr(res)); goto out; } // Initialize miniJSS jssInit(); // Read module file dmMsg(1, "Reading file: %s\n", optFilename); #ifdef JSS_SUP_XM if (eng.mod == NULL) { dmMsg(2, "* Trying XM...\n"); dmfreset(file); if ((res = jssLoadXM(file, &eng.mod, TRUE)) == DMERR_OK) { dmfreset(file); res = jssLoadXM(file, &eng.mod, FALSE); } } #endif #ifdef JSS_SUP_JSSMOD if (eng.mod == NULL) { dmMsg(1, "* Trying JSSMOD ...\n"); dmfreset(file); if ((res = jssLoadJSSMOD(file, &eng.mod, TRUE)) == DMERR_OK) { dmfreset(file); res = jssLoadJSSMOD(file, &eng.mod, FALSE); } } #endif dmf_close(file); // Check for errors, we still might have some data tho if (res != DMERR_OK) { dmErrorMsg("Error loading module file: %s\n", dmErrorStr(res)); goto out; } // Check if we have anything if (eng.mod == NULL) { res = dmError(DMERR_INIT_FAIL, "Could not load module file.\n"); goto out; } // Try to convert it if ((res = jssConvertModuleForPlaying(eng.mod)) != DMERR_OK) { dmErrorMsg("Could not convert module for playing: %s\n", dmErrorStr(res)); goto out; } // Initialize SDL components if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) { res = dmError(DMERR_INIT_FAIL, "Could not initialize SDL: %s\n", SDL_GetError()); goto out; } initSDL = TRUE; // Initialize mixing device dmMsg(2, "Initializing miniJSS mixer with: %d, %d, %d\n", optOutFormat, optOutChannels, optOutFreq); eng.dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); if (eng.dev == NULL) { res = dmError(DMERR_INIT_FAIL, "jvmInit() returned NULL\n"); goto out; } switch (optOutFormat) { case JSS_AUDIO_S16: eng.afmt.format = AUDIO_S16SYS; break; case JSS_AUDIO_U16: eng.afmt.format = AUDIO_U16SYS; break; case JSS_AUDIO_S8: eng.afmt.format = AUDIO_S8; break; case JSS_AUDIO_U8: eng.afmt.format = AUDIO_U8; break; default: res = dmError(DMERR_NOT_SUPPORTED, "Unsupported audio format %d (could not set matching SDL format)\n", optOutFormat); goto out; } eng.afmt.freq = optOutFreq; eng.afmt.channels = optOutChannels; eng.afmt.samples = optOutFreq / 16; eng.afmt.callback = audioCallback; eng.afmt.userdata = (void *) eng.dev; // Open the audio device if (SDL_OpenAudio(&eng.afmt, NULL) < 0) { res = dmError(DMERR_INIT_FAIL, "Couldn't open SDL audio: %s\n", SDL_GetError()); goto out; } audioInit = TRUE; // Initialize player if ((eng.plr = jmpInit(eng.dev)) == NULL) { res = dmError(DMERR_INIT_FAIL, "jmpInit() returned NULL\n"); goto out; } jvmSetCallback(eng.dev, jmpExec, eng.plr); jmpSetModule(eng.plr, eng.mod); jmpPlayOrder(eng.plr, optStartOrder); jvmSetGlobalVol(eng.dev, 200); if (optMuteOChannels >= 0 && optMuteOChannels < eng.mod->nchannels) { dmMuteChannels(TRUE); jvmMute(eng.dev, optMuteOChannels, FALSE); eng.actChannel = optMuteOChannels; muteState = TRUE; } if (optUseGUI) { // Get font static const char *engineFontName = "pplfont.fnt"; res = dmf_open_memio(NULL, engineFontName, engineSetupFont, sizeof(engineSetupFont), &file); if (res != DMERR_OK) { dmErrorMsg("Error opening font file '%s': %s\n", engineFontName, dmErrorStr(res)); goto out; } res = dmLoadBitmapFont(file, &font); dmf_close(file); if (res != DMERR_OK) { dmErrorMsg("Could not load font data from '%s': %s\n", engineFontName, dmErrorStr(res)); goto out; } SDL_Color pal[DMFONT_NPALETTE]; for (int n = 0; n < DMFONT_NPALETTE; n++) { pal[n].r = pal[n].g = pal[n].b = 0; pal[n].a = n > 0 ? 255 : 0; } dmSetBitmapFontPalette(font, pal, 0, DMFONT_NPALETTE); // Open window if ((eng.window = SDL_CreateWindow(dmProgName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, eng.optScrWidth, eng.optScrHeight, eng.optVFlags | SDL_WINDOW_RESIZABLE //| SDL_WINDOW_HIDDEN )) == NULL) { res = dmError(DMERR_INIT_FAIL, "Can't create an SDL window: %s\n", SDL_GetError()); goto out; } SDL_SetWindowTitle(eng.window, dmProgDesc); if ((eng.renderer = SDL_CreateRenderer(eng.window, -1, SDL_RENDERER_PRESENTVSYNC)) == NULL) { res = dmError(DMERR_INIT_FAIL, "Can't create an SDL renderer: %s\n", SDL_GetError()); goto out; } if (!dmInitializeVideo()) { res = DMERR_INIT_FAIL; goto out; } //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 (!eng.exitFlag) { currTick = SDL_GetTicks(); BOOL needUpdate = (currTick - prevTick > 500), needRender = FALSE; while (SDL_PollEvent(&eng.event)) switch (eng.event.type) { case SDL_KEYDOWN: switch (eng.event.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_q: eng.exitFlag = TRUE; break; case SDLK_SPACE: eng.pauseFlag = !eng.pauseFlag; SDL_PauseAudio(eng.pauseFlag); break; case SDLK_LEFT: if (eng.actChannel > 0) { eng.actChannel--; needUpdate = TRUE; } break; case SDLK_RIGHT: if (eng.actChannel < eng.mod->nchannels - 1) { eng.actChannel++; needUpdate = TRUE; } break; case SDLK_m: if (eng.event.key.keysym.mod & KMOD_SHIFT) { muteState = !muteState; dmMuteChannels(muteState); } else if (eng.event.key.keysym.mod & KMOD_CTRL) { dmMuteChannels(FALSE); } else { jvmMute(eng.dev, eng.actChannel, !jvmGetMute(eng.dev, eng.actChannel)); } needUpdate = TRUE; break; case SDLK_PAGEUP: JSS_LOCK(eng.dev); JSS_LOCK(eng.plr); jmpChangeOrder(eng.plr, dmClamp(eng.plr->order - 1, 0, eng.mod->norders)); JSS_UNLOCK(eng.plr); JSS_UNLOCK(eng.dev); needUpdate = TRUE; break; case SDLK_PAGEDOWN: JSS_LOCK(eng.dev); JSS_LOCK(eng.plr); jmpChangeOrder(eng.plr, dmClamp(eng.plr->order + 1, 0, eng.mod->norders)); JSS_UNLOCK(eng.plr); JSS_UNLOCK(eng.dev); needUpdate = TRUE; break; case SDLK_f: eng.optVFlags ^= SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(eng.window, eng.optVFlags) != 0) goto out; needUpdate = TRUE; break; default: break; } break; case SDL_WINDOWEVENT: switch (eng.event.window.event) { case SDL_WINDOWEVENT_EXPOSED: needUpdate = TRUE; break; case SDL_WINDOWEVENT_RESIZED: eng.optScrWidth = eng.event.window.data1; eng.optScrHeight = eng.event.window.data2; if (!dmInitializeVideo()) goto out; needUpdate = TRUE; break; } break; case SDL_QUIT: eng.exitFlag = TRUE; break; } // Check for end of song JSS_LOCK(eng.plr); if (!eng.plr->isPlaying) eng.exitFlag = TRUE; JSS_UNLOCK(eng.plr); if (optUseGUI) { // Check if we need to update screen JSS_LOCK(eng.plr); JSSPattern *currPattern = eng.plr->pattern; int currRow = eng.plr->row; JSS_UNLOCK(eng.plr); if (currRow != prevRow || needUpdate) { prevRow = currRow; needUpdate = TRUE; } // Draw frame if (needUpdate) { dmClearSurface(eng.screen, col.boxBg); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012-2019 TNSP", dmProgDesc, dmProgVersion); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5 + (font->height + 2), "Song: '%s'", eng.mod->moduleName); dmDisplayPattern(eng.screen, 5, 5 + (font->height + 2) * 3 + 4, eng.screen->w - 6, eng.screen->h * 0.8, currPattern, currRow); JSS_LOCK(eng.plr); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5 + (font->height + 2) * 2, "Tempo: %3d | Speed: %3d | Row: %3d/%-3d | Order: %3d/%-3d | Pattern: %3d/%-3d", eng.plr->tempo, eng.plr->speed, eng.plr->row, (eng.plr->pattern != NULL) ? eng.plr->pattern->nrows : 0, eng.plr->order + 1, eng.mod->norders, eng.plr->npattern, eng.mod->npatterns); JSS_UNLOCK(eng.plr); needRender = TRUE; } if (needUpdate || currTick - prevTick >= (eng.pauseFlag ? 100 : 20)) { JSS_LOCK(eng.dev); dmDisplayChannels(eng.screen, 5, eng.screen->h * 0.8 + 5, eng.screen->w - 5, eng.screen->h - 5, eng.dev); JSS_UNLOCK(eng.dev); needRender = TRUE; } if (needUpdate) prevTick = currTick; // Flip screen if (needRender) { SDL_Surface dst; SDL_LockTexture(eng.texture, NULL, &dst.pixels, &dst.pitch); if (dst.pitch != eng.screen->pitch) eng.exitFlag = TRUE; else memcpy(dst.pixels, eng.screen->pixels, eng.screen->h * dst.pitch); SDL_UnlockTexture(eng.texture); //SDL_RenderClear(eng.renderer); SDL_RenderCopy(eng.renderer, eng.texture, NULL, NULL); SDL_RenderPresent(eng.renderer); } } // optUseGUI SDL_Delay(eng.pauseFlag ? 100 : 30); } out: // Cleanup if (optUseGUI) { dmMsg(1, "GUI shutdown.\n"); if (eng.texture != NULL) SDL_DestroyTexture(eng.texture); if (eng.renderer != NULL) SDL_DestroyRenderer(eng.renderer); if (eng.window != NULL) SDL_DestroyWindow(eng.window); if (eng.screen != NULL) SDL_FreeSurface(eng.screen); dmFreeBitmapFont(font); } dmMsg(1, "Audio shutdown.\n"); if (audioInit) { SDL_LockAudio(); SDL_PauseAudio(1); SDL_UnlockAudio(); SDL_CloseAudio(); } jmpClose(eng.plr); jvmClose(eng.dev); jssFreeModule(eng.mod); dmMsg(1, "Shut down SDL.\n"); if (initSDL) SDL_Quit(); jssClose(); return res; }