From 26f2bfde03420edad4de1f22cb3d515dc063b20d Mon Sep 17 00:00:00 2001 From: Frank Gevaerts Date: Sun, 12 Dec 2010 15:03:30 +0000 Subject: Add MikMod plugin, ported by Jason Yu, with some minor work by Craig Mann and William Peters (FS#8806) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28810 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mikmod/mikmod.c | 943 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 943 insertions(+) create mode 100644 apps/plugins/mikmod/mikmod.c (limited to 'apps/plugins/mikmod/mikmod.c') diff --git a/apps/plugins/mikmod/mikmod.c b/apps/plugins/mikmod/mikmod.c new file mode 100644 index 0000000000..fe1768376e --- /dev/null +++ b/apps/plugins/mikmod/mikmod.c @@ -0,0 +1,943 @@ +#define NO_MMSUPP_DEFINES + +#include "plugin.h" +#include "lib/configfile.h" +#include "mikmod.h" + + +#undef SYNC +#ifdef SIMULATOR +#define SYNC +#elif NUM_CORES > 1 +#define USETHREADS +#endif + +#define MAX_CHARS LCD_WIDTH/6 +#define MAX_LINES LCD_HEIGHT/8 +#define LINE_LENGTH 80 + +#define DIR_PREV 1 +#define DIR_NEXT -1 +#define DIR_NONE 0 + +#define PLUGIN_NEWSONG 10 + +/* Persistent configuration */ +#define MIKMOD_CONFIGFILE "mikmod.cfg" +#define MIKMOD_SETTINGS_MINVERSION 1 +#define MIKMOD_SETTINGS_VERSION 1 + +#ifdef USETHREADS +#define EV_EXIT 9999 +#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200 +static unsigned int thread_id; +static struct event_queue thread_q; +/* use long for aligning */ +unsigned long thread_stack[THREAD_STACK_SIZE/sizeof(long)]; +#endif + +/* the current full file name */ +static char np_file[MAX_PATH]; +static int curfile = 0, direction = DIR_NEXT, entries = 0; + +/* list of the mod files */ +static char **file_pt; + + +/* The MP3 audio buffer which we will use as heap memory */ +static unsigned char* audio_buffer; +/* amount of bytes left in audio_buffer */ +static size_t audio_buffer_free; + + +/* The rockbox plugin interface */ +MEM_FUNCTION_WRAPPERS; + +bool quit; +int playingtime IBSS_ATTR; +MODULE *module IBSS_ATTR; +char gmbuf[BUF_SIZE*NBUF]; + + +int textlines; +int vscroll = 0; +int hscroll = 0; +bool screenupdated = false; + +enum { + DISPLAY_INFO = 0, + DISPLAY_SAMPLE, + DISPLAY_INST, + DISPLAY_COMMENTS, +} display; + + +/* +* strncat wrapper +*/ +char* mmsupp_strncat(char *s1, const char *s2, size_t n) +{ + char *s = s1; + /* Loop over the data in s1. */ + while (*s != '\0') + s++; + /* s now points to s1's trailing null character, now copy + up to n bytes from s2 into s1 stopping if a null character + is encountered in s2. + It is not safe to use strncpy here since it copies EXACTLY n + characters, NULL padding if necessary. */ + while (n != 0 && (*s = *s2++) != '\0') + { + n--; + s++; + } + if (*s != '\0') + *s = '\0'; + return s1; +} + +/* +* sprintf wrapper +*/ +int mmsupp_sprintf(char *buf, const char *fmt, ... ) +{ + bool ok; + va_list ap; + + va_start(ap, fmt); + ok = rb->vsnprintf(buf, LINE_LENGTH, fmt, ap); + va_end(ap); + + return ok; +} + +/* +* printf wrapper +*/ +void mmsupp_printf(const char *fmt, ...) +{ + static int p_xtpt = 0; + char p_buf[LINE_LENGTH]; + bool ok; + va_list ap; + + va_start(ap, fmt); + ok = rb->vsnprintf(p_buf, sizeof(p_buf), fmt, ap); + va_end(ap); + + int i=0; + + /* Device LCDs display newlines funny. */ + for(i=0; p_buf[i]!=0; i++) + if(p_buf[i] == '\n') + p_buf[i] = ' '; + + rb->lcd_putsxy(1, p_xtpt, (unsigned char *)p_buf); + rb->lcd_update(); + + p_xtpt += 8; + if(p_xtpt > LCD_HEIGHT-8) + { + p_xtpt = 0; + rb->lcd_clear_display(); + } +} + + +/************************* File Access ***************************/ + +/* support function for qsort() */ +static int compare(const void* p1, const void* p2) +{ + return rb->strcasecmp(*((char **)p1), *((char **)p2)); +} + +bool mod_ext(const char ext[]) +{ + if(!ext) + return false; + if(!rb->strcasecmp(ext,".669") || + !rb->strcasecmp(ext,".amf") || + !rb->strcasecmp(ext,".asy") || + !rb->strcasecmp(ext,".dsm") || + !rb->strcasecmp(ext,".far") || + !rb->strcasecmp(ext,".gdm") || + !rb->strcasecmp(ext,".gt2") || + !rb->strcasecmp(ext,".imf") || + !rb->strcasecmp(ext,".it") || + !rb->strcasecmp(ext,".m15") || + !rb->strcasecmp(ext,".med") || + !rb->strcasecmp(ext,".mod") || + !rb->strcasecmp(ext,".mtm") || + !rb->strcasecmp(ext,".okt") || + !rb->strcasecmp(ext,".s3m") || + !rb->strcasecmp(ext,".stm") || + !rb->strcasecmp(ext,".stx") || + !rb->strcasecmp(ext,".ult") || + !rb->strcasecmp(ext,".uni") || + !rb->strcasecmp(ext,".xm") ) + return true; + else + return false; +} + +/*Read directory contents for scrolling. */ +void get_mod_list(void) +{ + struct tree_context *tree = rb->tree_get_context(); + struct entry *dircache = tree->dircache; + int i; + char *pname; + + file_pt = (char **) audio_buffer; + + /* Remove path and leave only the name.*/ + pname = rb->strrchr(np_file,'/'); + pname++; + + for (i = 0; i < tree->filesindir && audio_buffer_free > sizeof(char**); i++) + { + if (!(dircache[i].attr & ATTR_DIRECTORY) + && mod_ext(rb->strrchr(dircache[i].name,'.'))) + { + file_pt[entries] = dircache[i].name; + /* Set Selected File. */ + if (!rb->strcmp(file_pt[entries], pname)) + curfile = entries; + entries++; + + audio_buffer += (sizeof(char**)); + audio_buffer_free -= (sizeof(char**)); + } + } +} + +int change_filename(int direct) +{ + bool file_erased = (file_pt[curfile] == NULL); + direction = direct; + + curfile += (direct == DIR_PREV? entries - 1: 1); + if (curfile >= entries) + curfile -= entries; + + if (file_erased) + { + /* remove 'erased' file names from list. */ + int count, i; + for (count = i = 0; i < entries; i++) + { + if (curfile == i) + curfile = count; + if (file_pt[i] != NULL) + file_pt[count++] = file_pt[i]; + } + entries = count; + } + + if (entries == 0) + { + rb->splash(HZ, "No supported files"); + return PLUGIN_ERROR; + } + + rb->strcpy(rb->strrchr(np_file, '/')+1, file_pt[curfile]); + + return PLUGIN_NEWSONG; +} + +/***************************************************************************** +* Playback +*/ + +bool swap = false; +bool lastswap = true; + +static inline void synthbuf(void) +{ + char *outptr; + +#ifndef SYNC + if (lastswap == swap) return; + lastswap = swap; + + outptr = (swap ? gmbuf : gmbuf + BUF_SIZE); +#else + outptr = gmbuf; +#endif + + VC_WriteBytes(outptr, BUF_SIZE); +} + +void get_more(unsigned char** start, size_t* size) +{ +#ifndef SYNC + if (lastswap != swap) + { + //printf("Buffer miss!"); + } + +#else + synthbuf(); +#endif + + *size = BUF_SIZE; +#ifndef SYNC + *start = (unsigned char*)((swap ? gmbuf : gmbuf + BUF_SIZE)); + swap = !swap; +#else + *start = (unsigned char*)(gmbuf); +#endif +} + +void showinfo() +{ + char statustext[LINE_LENGTH]; + + if (!module) + { + return; + } + + rb->lcd_clear_display(); + + playingtime = (int)(module->sngtime >> 10); + sprintf(statustext, "Name: %s", module->songname); + rb->lcd_putsxy(1, 1, statustext); + sprintf(statustext, "Type: %s", module->modtype); + rb->lcd_putsxy(1, 11, statustext); + + sprintf(statustext, "Samples: %d", module->numsmp); + rb->lcd_putsxy(1, 21, statustext); + + if ( module->flags & UF_INST ) + { + sprintf(statustext, "Instruments: %d", module->numins); + rb->lcd_putsxy(1, 31, statustext); + } + + sprintf(statustext, "pat: %03d/%03d %2.2X", + module->sngpos, module->numpos - 1, module->patpos); + rb->lcd_putsxy(1, 51, statustext); + + sprintf(statustext, "spd: %d/%d", + module->sngspd, module->bpm); + rb->lcd_putsxy(1, 61, statustext); + + sprintf(statustext, "vol: %ddB", rb->global_settings->volume); + rb->lcd_putsxy(1, 71, statustext); + + sprintf(statustext, "time: %d:%02d", + (playingtime / 60) % 60, playingtime % 60); + rb->lcd_putsxy(1, 81, statustext); + + if (module->flags & UF_NNA) + { + sprintf(statustext, "chn: %d/%d+%d->%d", + module->realchn, module->numchn, + module->totalchn - module->realchn, + module->totalchn); + } + else + { + sprintf(statustext, "chn: %d/%d", + module->realchn, module->numchn); + } + rb->lcd_putsxy(0, 91, statustext); + + rb->lcd_update(); +} + +void showsamples() +{ + int i, j; + char statustext[LINE_LENGTH]; + + if ( screenupdated ) + { + return; + } + rb->lcd_clear_display(); + for( i=0; inumsmp; i++ ) + { + sprintf(statustext, "%02d %s", i+vscroll+1, module->samples[i+vscroll].samplename); + rb->lcd_putsxy(1, 1+(8*i), statustext); + } + rb->lcd_update(); + screenupdated = true; +} + +void showinstruments() +{ + int i, j; + char statustext[LINE_LENGTH]; + + if ( screenupdated ) + { + return; + } + rb->lcd_clear_display(); + for( i=0; inumins; i++ ) + { + sprintf(statustext, "%02d %s", i+vscroll+1, module->instruments[i+vscroll].insname); + rb->lcd_putsxy(1, 1+(8*i), statustext); + } + rb->lcd_update(); + screenupdated = true; +} + +void showcomments() +{ + int i, j=0, k=0, l; + char statustext[LINE_LENGTH]; + + if ( screenupdated ) + { + return; + } + rb->lcd_clear_display(); + + for(i=0; module->comment[i]!='\0'; i++) + { + if(module->comment[i] != '\n') + { + statustext[j] = module->comment[i]; + j++; + } + + if(module->comment[i] == '\n' || j>LINE_LENGTH-1) + { + rb->lcd_putsxy(1-(6*hscroll), 1+(8*k)-(8*vscroll), statustext); + for( l=0; l0) + { + rb->lcd_putsxy(1-(6*hscroll), 1+(8*k)-(8*vscroll), statustext); + } + + rb->lcd_update(); + screenupdated = true; +} + +int changedisplay() +{ + display = (display+1) % 4; + + if (display == DISPLAY_SAMPLE) + { + textlines = module->numsmp; + } + + if (display == DISPLAY_INST) + { + if ( module->flags & UF_INST ) + { + textlines = module->numins; + } + else + { + display = DISPLAY_COMMENTS; + } + } + + if (display == DISPLAY_COMMENTS) + { + if (module->comment) + { + textlines = 100; + } + else + { + display = DISPLAY_INFO; + } + } + screenupdated = false; + vscroll = 0; + hscroll = 0; +} + +struct mikmod_settings +{ + int pansep; + int reverb; + bool interp; + bool reverse; + bool surround; + bool boost; +}; + +static struct mikmod_settings settings = +{ + 128, + 0, + 0, + 0, + 1, + 1 +}; + +static struct mikmod_settings old_settings; + +static struct configdata config[] = +{ + { TYPE_INT, 0, 128, { .int_p = &settings.pansep }, "Panning Separation", NULL}, + { TYPE_INT, 0, 15, { .int_p = &settings.reverb }, "Reverberation", NULL}, + { TYPE_BOOL, 0, 1, { .bool_p = &settings.interp }, "Interpolation", NULL}, + { TYPE_BOOL, 0, 1, { .bool_p = &settings.reverse }, "Reverse Channels", NULL}, + { TYPE_BOOL, 0, 1, { .bool_p = &settings.surround }, "Surround", NULL}, + { TYPE_BOOL, 0, 1, { .bool_p = &settings.boost }, "CPU Boost", NULL}, +}; + +void applysettings() +{ + md_pansep = settings.pansep; + md_reverb = settings.reverb; + md_mode = DMODE_STEREO | DMODE_16BITS | DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX; + if ( settings.interp ) + { + md_mode |= DMODE_INTERP; + } + if ( settings.reverse ) + { + md_mode |= DMODE_REVERSE; + } + if ( settings.surround ) + { + md_mode |= DMODE_SURROUND; + } +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + if ( Player_Active() ) + { + rb->cpu_boost(settings.boost); + } +#endif +} + +/** + Shows the settings menu + */ +int settings_menu(void) +{ + int selection = 0; + bool old_val; + + MENUITEM_STRINGLIST(settings_menu, "Mikmod Settings", NULL, "Panning Separation", + "Reverberation", "Interpolation", "Reverse Channels", "Surround", +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + "CPU Boost" +#endif + ); + + do + { + selection=rb->do_menu(&settings_menu,&selection, NULL, false); + switch(selection) + { + case 0: + rb->set_int("Panning Separation", "", 1, + &(settings.pansep), + NULL, 8, 0, 128, NULL ); + applysettings(); + break; + + case 1: + rb->set_int("Reverberation", "", 1, + &(settings.reverb), + NULL, 1, 0, 15, NULL ); + applysettings(); + break; + + case 2: + rb->set_bool("Interpolation", &(settings.interp)); + applysettings(); + break; + + case 3: + rb->set_bool("Reverse Channels", &(settings.reverse)); + applysettings(); + break; + + case 4: + rb->set_bool("Surround", &(settings.surround)); + applysettings(); + break; + + case 5: + rb->set_bool("CPU Boost", &(settings.boost)); + applysettings(); + break; + + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + } + } while ( selection >= 0 ); + return 0; +} + +/** + Show the main menu + */ +int main_menu(void) +{ + int selection = 0; + int result; + + MENUITEM_STRINGLIST(main_menu,"Mikmod Main Menu",NULL, + "Settings", "Return", "Quit"); + while (1) + { + switch (rb->do_menu(&main_menu,&selection, NULL, false)) + { + case 0: + result = settings_menu(); + if ( result != 0 ) return result; + break; + + case 1: + return 0; + + case 2: + return -1; + + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + + default: + return 0; + } + } +} + +#ifdef USETHREADS +/* double buffering thread */ +void thread(void) +{ + struct queue_event ev; + + while (1) + { + synthbuf(); + rb->queue_wait_w_tmo(&thread_q, &ev, HZ/20); + switch (ev.id) { + case EV_EXIT: + return; + } + } +} +#endif + +void mm_errorhandler(void) +{ + rb->splashf(HZ, "%s", MikMod_strerror(MikMod_errno)); + quit = true; +} + +int playfile(char* filename) +{ + int vol = 0; + int button; + int retval = PLUGIN_OK; + bool changingpos = false; + int menureturn; + + playingtime = 0; + + rb->splashf(HZ, "Loading %s", filename); + + module = Player_Load(filename, 64, 0); + + if (!module) + { + rb->splashf(HZ, "%s", MikMod_strerror(MikMod_errno)); + retval = PLUGIN_ERROR; + quit = true; + } + else + { + display = DISPLAY_INFO; + Player_Start(module); + rb->pcm_play_data(&get_more, NULL, 0); + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + if ( settings.boost ) + rb->cpu_boost(true); +#endif +#ifdef USETHREADS + rb->queue_init(&thread_q, true); + if ((thread_id = rb->create_thread(thread, thread_stack, + sizeof(thread_stack), 0, "render buffering thread" + IF_PRIO(, PRIORITY_PLAYBACK) + IF_COP(, CPU))) == 0) + { + rb->splash(HZ, "Cannot create thread!"); + return PLUGIN_ERROR; + } +#endif + + while (!quit && Player_Active() && retval == PLUGIN_OK) + { +#if !defined(SYNC) && !defined(USETHREADS) + synthbuf(); +#endif + switch (display) + { + case DISPLAY_SAMPLE: + showsamples(); + break; + case DISPLAY_INST: + showinstruments(); + break; + case DISPLAY_COMMENTS: + showcomments(); + break; + default: + showinfo(); + } + + rb->yield(); + + /* Prevent idle poweroff */ + rb->reset_poweroff_timer(); + + button = rb->get_action(CONTEXT_WPS, TIMEOUT_NOBLOCK); + switch (button) + { + case ACTION_WPS_VOLUP: + if ( display != DISPLAY_INFO ) + { + if ( textlines-vscroll >= MAX_LINES ) + { + vscroll++; + screenupdated = false; + } + break; + } + vol = rb->global_settings->volume; + if (vol < rb->sound_max(SOUND_VOLUME)) + { + vol++; + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; + } + break; + + case ACTION_WPS_VOLDOWN: + if ( display != DISPLAY_INFO ) + { + if ( vscroll > 0 ) + { + vscroll--; + screenupdated = false; + } + break; + } + vol = rb->global_settings->volume; + if (vol > rb->sound_min(SOUND_VOLUME)) + { + vol--; + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; + } + break; + + case ACTION_WPS_SKIPPREV: + if(entries>1 && !changingpos) + { + if ((int)(module->sngtime >> 10) > 2) + { + Player_SetPosition(0); + module->sngtime = 0; + } + else { + retval = change_filename(DIR_PREV); + } + } + else + { + changingpos = false; + } + break; + case ACTION_WPS_SEEKBACK: + if ( display != DISPLAY_INFO ) + { + if ( hscroll > 0 ) + { + hscroll--; + screenupdated = false; + } + break; + } + Player_PrevPosition(); + changingpos = true; + break; + + case ACTION_WPS_SKIPNEXT: + if(entries>1 && !changingpos) + { + retval = change_filename(DIR_NEXT); + } + else + { + changingpos = false; + } + break; + case ACTION_WPS_SEEKFWD: + if ( display != DISPLAY_INFO ) + { + hscroll++; + screenupdated = false; + break; + } + Player_NextPosition(); + changingpos = true; + break; + + case ACTION_WPS_PLAY: + if(!Player_Paused()) + { + rb->pcm_play_stop(); + } + else + { + rb->pcm_play_data(&get_more, NULL, 0); + } + Player_TogglePause(); + break; + + case ACTION_WPS_BROWSE: + changedisplay(); + break; + + case ACTION_WPS_MENU: + menureturn = main_menu(); + if ( menureturn != 0 ) + { + quit = true; + if ( menureturn == PLUGIN_USB_CONNECTED ) + { + retval = menureturn; + } + } + rb->lcd_setfont(0); + screenupdated = false; + break; + + case ACTION_WPS_STOP: + quit = true; + break; + + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + { + quit = true; + retval = PLUGIN_USB_CONNECTED; + } + } + } + +#ifdef USETHREADS + rb->queue_post(&thread_q, EV_EXIT, 0); + rb->thread_wait(thread_id); + rb->queue_delete(&thread_q); +#endif +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + if ( settings.boost ) + rb->cpu_boost(false); +#endif + + Player_Stop(); + Player_Free(module); + + memset(gmbuf, '\0', sizeof(gmbuf)); + + if ( retval == PLUGIN_OK && entries > 1 && !quit ) + { + retval = change_filename(DIR_NEXT); + } + + return retval; +} + +/* +* Plugin entry point +* +*/ +enum plugin_status plugin_start(const void* parameter) +{ + enum plugin_status retval; + + if (parameter == NULL) + { + rb->splash(HZ*2, " Play .mod, .it, .s3m, .xm file "); + return PLUGIN_OK; + } + + rb->lcd_setfont(0); + + rb->pcm_play_stop(); +#if INPUT_SRC_CAPS != 0 + /* Select playback */ + rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); +#endif + rb->pcm_set_frequency(SAMPLE_RATE); + + audio_buffer = rb->plugin_get_audio_buffer((size_t *)&audio_buffer_free); + + rb->strcpy(np_file, parameter); + get_mod_list(); + if(!entries) { + return PLUGIN_ERROR; + } + + //add_pool(audio_buffer, audio_buffer_free); + init_memory_pool(audio_buffer_free, audio_buffer); + + MikMod_RegisterDriver(&drv_nos); + MikMod_RegisterAllLoaders(); + MikMod_RegisterErrorHandler(mm_errorhandler); + + md_mixfreq = SAMPLE_RATE; + + configfile_load(MIKMOD_CONFIGFILE, config, + ARRAYLEN(config), MIKMOD_SETTINGS_MINVERSION); + rb->memcpy(&old_settings, &settings, sizeof (settings)); + applysettings(); + + if (MikMod_Init("")) + { + rb->splashf(HZ, "%s", MikMod_strerror(MikMod_errno)); + return PLUGIN_ERROR; + } + + do + { + retval = playfile(np_file); + } while (retval == PLUGIN_NEWSONG); + + MikMod_Exit(); + + rb->pcm_play_stop(); + rb->pcm_set_frequency(HW_SAMPR_DEFAULT); + + if (retval == PLUGIN_OK) + { + rb->splash(0, "Saving Settings"); + if (rb->memcmp(&settings, &old_settings, sizeof (settings))) + { + configfile_save(MIKMOD_CONFIGFILE, config, + ARRAYLEN(config), MIKMOD_SETTINGS_MINVERSION); + } + } + + destroy_memory_pool(audio_buffer); + + return retval; +} -- cgit v1.2.3