1 #define NO_MMSUPP_DEFINES
4 #include "lib/configfile.h"
15 #define MAX_CHARS LCD_WIDTH/6
16 #define MAX_LINES LCD_HEIGHT/8
17 #define LINE_LENGTH 80
23 #define PLUGIN_NEWSONG 10
25 /* Persistent configuration */
26 #define MIKMOD_CONFIGFILE "mikmod.cfg"
27 #define MIKMOD_SETTINGS_MINVERSION 1
28 #define MIKMOD_SETTINGS_VERSION 1
32 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
33 static unsigned int thread_id
;
34 static struct event_queue thread_q SHAREDBSS_ATTR
;
35 /* use long for aligning */
36 unsigned long thread_stack
[THREAD_STACK_SIZE
/sizeof(long)];
39 /* the current full file name */
40 static char np_file
[MAX_PATH
];
41 static int curfile
= 0, direction
= DIR_NEXT
, entries
= 0;
43 /* list of the mod files */
44 static char **file_pt
;
47 /* The MP3 audio buffer which we will use as heap memory */
48 static unsigned char* audio_buffer
;
49 /* amount of bytes left in audio_buffer */
50 static size_t audio_buffer_free
;
53 /* The rockbox plugin interface */
54 MEM_FUNCTION_WRAPPERS
;
57 int playingtime IBSS_ATTR
;
58 MODULE
*module IBSS_ATTR
;
59 char gmbuf
[BUF_SIZE
*NBUF
];
65 bool screenupdated
= false;
78 char* mmsupp_strncat(char *s1
, const char *s2
, size_t n
)
81 /* Loop over the data in s1. */
84 /* s now points to s1's trailing null character, now copy
85 up to n bytes from s2 into s1 stopping if a null character
87 It is not safe to use strncpy here since it copies EXACTLY n
88 characters, NULL padding if necessary. */
89 while (n
!= 0 && (*s
= *s2
++) != '\0')
102 int mmsupp_sprintf(char *buf
, const char *fmt
, ... )
108 ok
= rb
->vsnprintf(buf
, LINE_LENGTH
, fmt
, ap
);
117 void mmsupp_printf(const char *fmt
, ...)
119 static int p_xtpt
= 0;
120 char p_buf
[LINE_LENGTH
];
125 ok
= rb
->vsnprintf(p_buf
, sizeof(p_buf
), fmt
, ap
);
130 /* Device LCDs display newlines funny. */
131 for(i
=0; p_buf
[i
]!=0; i
++)
135 rb
->lcd_putsxy(1, p_xtpt
, (unsigned char *)p_buf
);
139 if(p_xtpt
> LCD_HEIGHT
-8)
142 rb
->lcd_clear_display();
147 /************************* File Access ***************************/
149 /* support function for qsort() */
150 static int compare(const void* p1
, const void* p2
)
152 return rb
->strcasecmp(*((char **)p1
), *((char **)p2
));
155 bool mod_ext(const char ext
[])
159 if(!rb
->strcasecmp(ext
,".669") ||
160 !rb
->strcasecmp(ext
,".amf") ||
161 !rb
->strcasecmp(ext
,".asy") ||
162 !rb
->strcasecmp(ext
,".dsm") ||
163 !rb
->strcasecmp(ext
,".far") ||
164 !rb
->strcasecmp(ext
,".gdm") ||
165 !rb
->strcasecmp(ext
,".gt2") ||
166 !rb
->strcasecmp(ext
,".imf") ||
167 !rb
->strcasecmp(ext
,".it") ||
168 !rb
->strcasecmp(ext
,".m15") ||
169 !rb
->strcasecmp(ext
,".med") ||
170 !rb
->strcasecmp(ext
,".mod") ||
171 !rb
->strcasecmp(ext
,".mtm") ||
172 !rb
->strcasecmp(ext
,".okt") ||
173 !rb
->strcasecmp(ext
,".s3m") ||
174 !rb
->strcasecmp(ext
,".stm") ||
175 !rb
->strcasecmp(ext
,".stx") ||
176 !rb
->strcasecmp(ext
,".ult") ||
177 !rb
->strcasecmp(ext
,".uni") ||
178 !rb
->strcasecmp(ext
,".xm") )
184 /*Read directory contents for scrolling. */
185 void get_mod_list(void)
187 struct tree_context
*tree
= rb
->tree_get_context();
188 struct entry
*dircache
= rb
->tree_get_entries(tree
);
192 file_pt
= (char **) audio_buffer
;
194 /* Remove path and leave only the name.*/
195 pname
= rb
->strrchr(np_file
,'/');
198 for (i
= 0; i
< tree
->filesindir
&& audio_buffer_free
> sizeof(char**); i
++)
200 if (!(dircache
[i
].attr
& ATTR_DIRECTORY
)
201 && mod_ext(rb
->strrchr(dircache
[i
].name
,'.')))
203 file_pt
[entries
] = dircache
[i
].name
;
204 /* Set Selected File. */
205 if (!rb
->strcmp(file_pt
[entries
], pname
))
209 audio_buffer
+= (sizeof(char**));
210 audio_buffer_free
-= (sizeof(char**));
215 int change_filename(int direct
)
217 bool file_erased
= (file_pt
[curfile
] == NULL
);
220 curfile
+= (direct
== DIR_PREV
? entries
- 1: 1);
221 if (curfile
>= entries
)
226 /* remove 'erased' file names from list. */
228 for (count
= i
= 0; i
< entries
; i
++)
232 if (file_pt
[i
] != NULL
)
233 file_pt
[count
++] = file_pt
[i
];
240 rb
->splash(HZ
, "No supported files");
244 rb
->strcpy(rb
->strrchr(np_file
, '/')+1, file_pt
[curfile
]);
246 return PLUGIN_NEWSONG
;
249 /*****************************************************************************
254 bool lastswap
= true;
256 static inline void synthbuf(void)
261 if (lastswap
== swap
) return;
264 outptr
= (swap
? gmbuf
: gmbuf
+ BUF_SIZE
);
269 VC_WriteBytes(outptr
, BUF_SIZE
);
272 void get_more(unsigned char** start
, size_t* size
)
275 if (lastswap
!= swap
)
277 //printf("Buffer miss!");
286 *start
= (unsigned char*)((swap
? gmbuf
: gmbuf
+ BUF_SIZE
));
289 *start
= (unsigned char*)(gmbuf
);
295 char statustext
[LINE_LENGTH
];
302 rb
->lcd_clear_display();
304 playingtime
= (int)(module
->sngtime
>> 10);
305 sprintf(statustext
, "Name: %s", module
->songname
);
306 rb
->lcd_putsxy(1, 1, statustext
);
307 sprintf(statustext
, "Type: %s", module
->modtype
);
308 rb
->lcd_putsxy(1, 11, statustext
);
310 sprintf(statustext
, "Samples: %d", module
->numsmp
);
311 rb
->lcd_putsxy(1, 21, statustext
);
313 if ( module
->flags
& UF_INST
)
315 sprintf(statustext
, "Instruments: %d", module
->numins
);
316 rb
->lcd_putsxy(1, 31, statustext
);
319 sprintf(statustext
, "pat: %03d/%03d %2.2X",
320 module
->sngpos
, module
->numpos
- 1, module
->patpos
);
321 rb
->lcd_putsxy(1, 51, statustext
);
323 sprintf(statustext
, "spd: %d/%d",
324 module
->sngspd
, module
->bpm
);
325 rb
->lcd_putsxy(1, 61, statustext
);
327 sprintf(statustext
, "vol: %ddB", rb
->global_settings
->volume
);
328 rb
->lcd_putsxy(1, 71, statustext
);
330 sprintf(statustext
, "time: %d:%02d",
331 (playingtime
/ 60) % 60, playingtime
% 60);
332 rb
->lcd_putsxy(1, 81, statustext
);
334 if (module
->flags
& UF_NNA
)
336 sprintf(statustext
, "chn: %d/%d+%d->%d",
337 module
->realchn
, module
->numchn
,
338 module
->totalchn
- module
->realchn
,
343 sprintf(statustext
, "chn: %d/%d",
344 module
->realchn
, module
->numchn
);
346 rb
->lcd_putsxy(0, 91, statustext
);
354 char statustext
[LINE_LENGTH
];
360 rb
->lcd_clear_display();
361 for( i
=0; i
<MAX_LINES
&& i
+vscroll
<module
->numsmp
; i
++ )
363 sprintf(statustext
, "%02d %s", i
+vscroll
+1, module
->samples
[i
+vscroll
].samplename
);
364 rb
->lcd_putsxy(1, 1+(8*i
), statustext
);
367 screenupdated
= true;
370 void showinstruments()
373 char statustext
[LINE_LENGTH
];
379 rb
->lcd_clear_display();
380 for( i
=0; i
<MAX_LINES
&& i
+vscroll
<module
->numins
; i
++ )
382 sprintf(statustext
, "%02d %s", i
+vscroll
+1, module
->instruments
[i
+vscroll
].insname
);
383 rb
->lcd_putsxy(1, 1+(8*i
), statustext
);
386 screenupdated
= true;
392 char statustext
[LINE_LENGTH
];
398 rb
->lcd_clear_display();
400 for(i
=0; module
->comment
[i
]!='\0'; i
++)
402 if(module
->comment
[i
] != '\n')
404 statustext
[j
] = module
->comment
[i
];
408 if(module
->comment
[i
] == '\n' || j
>LINE_LENGTH
-1)
410 rb
->lcd_putsxy(1-(6*hscroll
), 1+(8*k
)-(8*vscroll
), statustext
);
411 for( l
=0; l
<LINE_LENGTH
; l
++ )
421 rb
->lcd_putsxy(1-(6*hscroll
), 1+(8*k
)-(8*vscroll
), statustext
);
425 screenupdated
= true;
430 display
= (display
+1) % 4;
432 if (display
== DISPLAY_SAMPLE
)
434 textlines
= module
->numsmp
;
437 if (display
== DISPLAY_INST
)
439 if ( module
->flags
& UF_INST
)
441 textlines
= module
->numins
;
445 display
= DISPLAY_COMMENTS
;
449 if (display
== DISPLAY_COMMENTS
)
457 display
= DISPLAY_INFO
;
460 screenupdated
= false;
465 struct mikmod_settings
475 static struct mikmod_settings settings
=
485 static struct mikmod_settings old_settings
;
487 static struct configdata config
[] =
489 { TYPE_INT
, 0, 128, { .int_p
= &settings
.pansep
}, "Panning Separation", NULL
},
490 { TYPE_INT
, 0, 15, { .int_p
= &settings
.reverb
}, "Reverberation", NULL
},
491 { TYPE_BOOL
, 0, 1, { .bool_p
= &settings
.interp
}, "Interpolation", NULL
},
492 { TYPE_BOOL
, 0, 1, { .bool_p
= &settings
.reverse
}, "Reverse Channels", NULL
},
493 { TYPE_BOOL
, 0, 1, { .bool_p
= &settings
.surround
}, "Surround", NULL
},
494 { TYPE_BOOL
, 0, 1, { .bool_p
= &settings
.boost
}, "CPU Boost", NULL
},
499 md_pansep
= settings
.pansep
;
500 md_reverb
= settings
.reverb
;
501 md_mode
= DMODE_STEREO
| DMODE_16BITS
| DMODE_SOFT_MUSIC
| DMODE_SOFT_SNDFX
;
502 if ( settings
.interp
)
504 md_mode
|= DMODE_INTERP
;
506 if ( settings
.reverse
)
508 md_mode
|= DMODE_REVERSE
;
510 if ( settings
.surround
)
512 md_mode
|= DMODE_SURROUND
;
514 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
515 if ( Player_Active() )
517 rb
->cpu_boost(settings
.boost
);
523 Shows the settings menu
525 int settings_menu(void)
530 MENUITEM_STRINGLIST(settings_menu
, "Mikmod Settings", NULL
, "Panning Separation",
531 "Reverberation", "Interpolation", "Reverse Channels", "Surround",
532 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
539 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
543 rb
->set_int("Panning Separation", "", 1,
545 NULL
, 8, 0, 128, NULL
);
550 rb
->set_int("Reverberation", "", 1,
552 NULL
, 1, 0, 15, NULL
);
557 rb
->set_bool("Interpolation", &(settings
.interp
));
562 rb
->set_bool("Reverse Channels", &(settings
.reverse
));
567 rb
->set_bool("Surround", &(settings
.surround
));
572 rb
->set_bool("CPU Boost", &(settings
.boost
));
576 case MENU_ATTACHED_USB
:
577 return PLUGIN_USB_CONNECTED
;
579 } while ( selection
>= 0 );
591 MENUITEM_STRINGLIST(main_menu
,"Mikmod Main Menu",NULL
,
592 "Settings", "Return", "Quit");
595 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false))
598 result
= settings_menu();
599 if ( result
!= 0 ) return result
;
608 case MENU_ATTACHED_USB
:
609 return PLUGIN_USB_CONNECTED
;
618 /* double buffering thread */
621 struct queue_event ev
;
626 rb
->queue_wait_w_tmo(&thread_q
, &ev
, HZ
/20);
635 void mm_errorhandler(void)
637 rb
->splashf(HZ
, "%s", MikMod_strerror(MikMod_errno
));
641 int playfile(char* filename
)
645 int retval
= PLUGIN_OK
;
646 bool changingpos
= false;
651 rb
->splashf(HZ
, "Loading %s", filename
);
653 module
= Player_Load(filename
, 64, 0);
657 rb
->splashf(HZ
, "%s", MikMod_strerror(MikMod_errno
));
658 retval
= PLUGIN_ERROR
;
663 display
= DISPLAY_INFO
;
664 Player_Start(module
);
665 rb
->pcm_play_data(&get_more
, NULL
, 0);
668 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
669 if ( settings
.boost
)
673 rb
->queue_init(&thread_q
, true);
674 if ((thread_id
= rb
->create_thread(thread
, thread_stack
,
675 sizeof(thread_stack
), 0, "render buffering thread"
676 IF_PRIO(, PRIORITY_PLAYBACK
)
677 IF_COP(, CPU
))) == 0)
679 rb
->splash(HZ
, "Cannot create thread!");
684 while (!quit
&& Player_Active() && retval
== PLUGIN_OK
)
686 #if !defined(SYNC) && !defined(USETHREADS)
697 case DISPLAY_COMMENTS
:
706 /* Prevent idle poweroff */
707 rb
->reset_poweroff_timer();
709 button
= rb
->get_action(CONTEXT_WPS
, TIMEOUT_NOBLOCK
);
712 case ACTION_WPS_VOLUP
:
713 if ( display
!= DISPLAY_INFO
)
715 if ( textlines
-vscroll
>= MAX_LINES
)
718 screenupdated
= false;
722 vol
= rb
->global_settings
->volume
;
723 if (vol
< rb
->sound_max(SOUND_VOLUME
))
726 rb
->sound_set(SOUND_VOLUME
, vol
);
727 rb
->global_settings
->volume
= vol
;
731 case ACTION_WPS_VOLDOWN
:
732 if ( display
!= DISPLAY_INFO
)
737 screenupdated
= false;
741 vol
= rb
->global_settings
->volume
;
742 if (vol
> rb
->sound_min(SOUND_VOLUME
))
745 rb
->sound_set(SOUND_VOLUME
, vol
);
746 rb
->global_settings
->volume
= vol
;
750 case ACTION_WPS_SKIPPREV
:
751 if(entries
>1 && !changingpos
)
753 if ((int)(module
->sngtime
>> 10) > 2)
755 Player_SetPosition(0);
759 retval
= change_filename(DIR_PREV
);
767 case ACTION_WPS_SEEKBACK
:
768 if ( display
!= DISPLAY_INFO
)
773 screenupdated
= false;
777 Player_PrevPosition();
781 case ACTION_WPS_SKIPNEXT
:
782 if(entries
>1 && !changingpos
)
784 retval
= change_filename(DIR_NEXT
);
791 case ACTION_WPS_SEEKFWD
:
792 if ( display
!= DISPLAY_INFO
)
795 screenupdated
= false;
798 Player_NextPosition();
802 case ACTION_WPS_PLAY
:
809 rb
->pcm_play_data(&get_more
, NULL
, 0);
811 Player_TogglePause();
814 case ACTION_WPS_BROWSE
:
818 case ACTION_WPS_MENU
:
819 menureturn
= main_menu();
820 if ( menureturn
!= 0 )
823 if ( menureturn
== PLUGIN_USB_CONNECTED
)
829 screenupdated
= false;
832 case ACTION_WPS_STOP
:
837 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
840 retval
= PLUGIN_USB_CONNECTED
;
846 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
847 rb
->thread_wait(thread_id
);
848 rb
->queue_delete(&thread_q
);
850 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
851 if ( settings
.boost
)
852 rb
->cpu_boost(false);
858 memset(gmbuf
, '\0', sizeof(gmbuf
));
860 if ( retval
== PLUGIN_OK
&& entries
> 1 && !quit
)
862 retval
= change_filename(DIR_NEXT
);
872 enum plugin_status
plugin_start(const void* parameter
)
874 enum plugin_status retval
;
876 if (parameter
== NULL
)
878 rb
->splash(HZ
*2, " Play .mod, .it, .s3m, .xm file ");
885 #if INPUT_SRC_CAPS != 0
886 /* Select playback */
887 rb
->audio_set_input_source(AUDIO_SRC_PLAYBACK
, SRCF_PLAYBACK
);
888 rb
->audio_set_output_source(AUDIO_SRC_PLAYBACK
);
890 rb
->pcm_set_frequency(SAMPLE_RATE
);
892 audio_buffer
= rb
->plugin_get_audio_buffer((size_t *)&audio_buffer_free
);
894 rb
->strcpy(np_file
, parameter
);
900 //add_pool(audio_buffer, audio_buffer_free);
901 init_memory_pool(audio_buffer_free
, audio_buffer
);
903 MikMod_RegisterDriver(&drv_nos
);
904 MikMod_RegisterAllLoaders();
905 MikMod_RegisterErrorHandler(mm_errorhandler
);
907 md_mixfreq
= SAMPLE_RATE
;
909 configfile_load(MIKMOD_CONFIGFILE
, config
,
910 ARRAYLEN(config
), MIKMOD_SETTINGS_MINVERSION
);
911 rb
->memcpy(&old_settings
, &settings
, sizeof (settings
));
916 rb
->splashf(HZ
, "%s", MikMod_strerror(MikMod_errno
));
922 retval
= playfile(np_file
);
923 } while (retval
== PLUGIN_NEWSONG
);
928 rb
->pcm_set_frequency(HW_SAMPR_DEFAULT
);
930 if (retval
== PLUGIN_OK
)
932 rb
->splash(0, "Saving Settings");
933 if (rb
->memcmp(&settings
, &old_settings
, sizeof (settings
)))
935 configfile_save(MIKMOD_CONFIGFILE
, config
,
936 ARRAYLEN(config
), MIKMOD_SETTINGS_MINVERSION
);
940 destroy_memory_pool(audio_buffer
);