rockbox/apps/plugins/mikmod/mikmod.c
Thomas Martitz baa070cca6 GSoC/Buflib: Enable compaction in buflib.
This enables the ability to allocate (and free) memory dynamically
without fragmentation, through compaction. This means allocations can move
and fragmentation be reduced. Most changes are preparing Rockbox for this,
which many times means adding a move callback which can temporarily disable
movement when the corresponding code is in a critical section.

For now, the audio buffer allocation has a central role, because it's the one
having allocated most. This buffer is able to shrink itself, for which it
needs to stop playback for a very short moment. For this,
audio_buffer_available() returns the size of the audio buffer which can
possibly be used by other allocations because the audio buffer can shrink.

lastfm scrobbling and timestretch can now be toggled at runtime without
requiring a reboot.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30381 a1c6a512-1295-4272-9138-f99709370657
2011-08-30 14:01:45 +00:00

943 lines
21 KiB
C

#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 SHAREDBSS_ATTR;
/* 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 = rb->tree_get_entries(tree);
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; i<MAX_LINES && i+vscroll<module->numsmp; 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; i<MAX_LINES && i+vscroll<module->numins; 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; l<LINE_LENGTH; l++ )
{
statustext[l] = 0;
}
k++;
j=0;
}
}
if (j>0)
{
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;
}