You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

637 lines
12 KiB

/* Extended Module Player
* Copyright (C) 1996-2012 Claudio Matsuoka and Hipolito Carraro Jr
*
* This file is part of the Extended Module Player and is distributed
* under the terms of the GNU General Public License. See doc/COPYING
* for more information.
*/
/*
* Fri, 05 Jun 1998 16:46:13 -0700 "Mark R. Boyns" <boyns@sdsu.edu>
* I needed to use xmp as a filter and was able to use /dev/fd/0 as an
* input source but ran into problems with xmp trying to read interactive
* commands. So, I added a few lines of code to disable command reading
* with the --nocmd option.
*/
/*
* Wed, 10 Jun 1998 15:42:25 -0500 Douglas Carmichael <dcarmich@dcarmichael.net>
* Support for real-time priority in FreeBSD.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef __CYGWIN__
#include <sys/select.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <unistd.h>
#ifdef __AROS__
#define __AMIGA__
#endif
#ifdef __AMIGA__
#undef HAVE_TERMIOS_H
#define __USE_INLINE__
#include <proto/dos.h>
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef HAVE_SYS_RTPRIO_H
#include <sys/resource.h>
#include <sys/rtprio.h>
#endif
#ifdef __EMX__
#define INCL_BASE
#include <os2.h>
#endif
#ifdef WIN32
#include <windows.h>
#include "conio.h"
#endif
#include "xmp.h"
extern int optind;
/* Options not belonging to libxmp */
int probeonly = 0;
int randomize = 0;
int loadonly = 0;
int nocmd = 0;
int showtime = 0;
#ifdef HAVE_SYS_RTPRIO_H
int rt = 0;
#endif
static struct xmp_options *opt;
static int verbosity;
#ifdef HAVE_TERMIOS_H
static struct termios term;
#endif
static int background = 0;
#ifdef SIGTSTP
static int stopped = 0;
#endif
static int refresh_status = 0;
static int max_nch = 0;
static double rows, tot_nch;
int skip = 0;
#ifdef HAVE_SIGNAL_H
static int sigusr = 0;
#endif
void get_options (int, char **, struct xmp_options *, xmp_context);
void init_drivers (void);
static xmp_context ctx;
static int paused;
static int set_tty()
{
#ifdef HAVE_TERMIOS_H
struct termios t;
if (background)
return -1;
if (tcgetattr (0, &term) < 0)
return -1;
t = term;
t.c_lflag &= ~(ECHO | ICANON | TOSTOP);
t.c_cc[VMIN] = t.c_cc[VTIME] = 0;
if (tcsetattr (0, TCSAFLUSH, &t) < 0)
return -1;
#endif
return 0;
}
static int reset_tty()
{
#ifdef HAVE_TERMIOS_H
if (background)
return -1;
if (tcsetattr(0, TCSAFLUSH, &term) < 0) {
fprintf(stderr, "can't reset terminal!\n");
return -1;
}
#endif
return 0;
}
#ifdef HAVE_SIGNAL_H
#ifdef SIGTSTP
static void sigtstp_handler(int n)
{
if (!stopped) {
if (showtime)
fprintf(stderr, "\n");
else if (verbosity)
fprintf(stderr, "] - STOPPED\n");
xmp_timer_stop(ctx);
stopped = 1;
}
signal (SIGTSTP, SIG_DFL);
kill (getpid (), SIGTSTP);
}
static void sigcont_handler(int n)
{
#ifndef __AMIGA__
background = (tcgetpgrp(0) == getppid());
#endif
if (background)
reset_tty();
else
set_tty();
if (stopped)
xmp_timer_restart(ctx);
stopped = 0;
refresh_status = 1;
/* Unlike BSD systems, signals under Linux are reset to their
* default behavior when raised.
*/
signal(SIGCONT, sigcont_handler);
signal(SIGTSTP, sigtstp_handler);
}
#endif
static void sigusr_handler(int n)
{
signal(sigusr = n, sigusr_handler);
}
static void cleanup(int s)
{
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
#ifdef SIGQUIT
signal(SIGQUIT, SIG_DFL);
#endif
signal(SIGFPE, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
fprintf(stderr, "\n*** Interrupted: signal %d caught\n", s);
xmp_stop_module(ctx);
xmp_close_audio(ctx);
reset_tty ();
exit (-2);
}
#endif /* HAVE_SIGNAL_H */
#ifdef __CYGWIN__
/*
* from daniel åkerud <daniel.akerud@gmail.com>
* date Tue, Jul 28, 2009 at 9:59 AM
*
* Under Cygwin, the read() in process_echoback blocks because VTIME = 0
* is not handled correctly. To fix this you can either:
*
* 1. Enable "tty emulation" in Cygwin using an environment variable.
* http://www.mail-archive.com/cygwin@cygwin.com/msg99417.html
* For me this is _very_ slow and I can see the characters as they are
* typed out when running xmp. I have not investigated why this is
* happening, but there is of course a reason why this mode is not
* enabled by default.
*
* 2. Do a select() before read()ing if the platform is Cygwin.
* This makes Cygwin builds work out of the box with no fiddling around,
* but does impose a neglectible cpu overhead (for Cygwin builds only).
*/
static int stdin_ready_for_reading()
{
fd_set fds;
struct timeval tv;
int ret;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
if (ret > 0 && FD_ISSET(STDIN_FILENO, &fds))
return 1;
return 0;
}
#endif
void read_keyboard()
{
unsigned char cmd;
int k;
/* Interactive commands */
if (nocmd || background)
return;
#if defined HAVE_TERMIOS_H && !defined WIN32
#ifdef __CYGWIN__
k = 0;
if (stdin_ready_for_reading())
#endif
k = read(0, &cmd, 1);
#elif defined WIN32
k = cmd = kbhit() ? getch() : 0;
#elif defined __AMIGA__
/* Amiga CLI */
{
BPTR in = Input();
k = cmd = 0;
if (WaitForChar(in, 1)) {
char c;
Read(in, &c, 1);
cmd = k = c;
}
}
#else
k = cmd = 0;
#endif
if (k > 0) {
switch (cmd) {
case 'q': /* quit */
skip = -2;
xmp_mod_stop(ctx);
paused = 0;
break;
case 'f': /* jump to next order */
xmp_ord_next(ctx);
paused = 0;
break;
case 'b': /* jump to previous order */
xmp_ord_prev(ctx);
paused = 0;
break;
case 'n': /* skip to next module */
skip = 1;
xmp_mod_stop(ctx);
paused = 0;
break;
case 'p': /* skip to previous module */
skip = -1;
xmp_mod_stop(ctx);
paused = 0;
break;
case 'l':
if (xmp_test_flag(ctx, XMP_CTL_LOOP))
xmp_reset_flag(ctx, XMP_CTL_LOOP);
else
xmp_set_flag(ctx, XMP_CTL_LOOP);
break;
case ' ': /* paused module */
paused ^= 1;
if (verbosity) {
fprintf (stderr, "%s", paused ?
"] - PAUSED\b\b\b\b\b\b\b\b\b\b" :
"] \b\b\b\b\b\b\b\b\b\b");
}
if (paused)
xmp_timer_stop(ctx);
else
xmp_timer_restart(ctx);
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
xmp_channel_mute(ctx, cmd - '1', 1, -1);
break;
case '0':
xmp_channel_mute(ctx, 9, 1, -1);
break;
case '!':
xmp_channel_mute(ctx, 0, 8, 0);
break;
}
}
}
static void shuffle (int argc, char **argv)
{
int i, j;
char *x;
for (i = 1; i < argc; i++) {
j = 1 + rand () % (argc - 1);
x = argv[i];
argv[i] = argv[j];
argv[j] = x;
}
}
int main(int argc, char **argv)
{
int i, t, lf_flag, first, num_mod;
time_t t0, t1, t2, t3;
#ifndef WIN32
struct timeval tv;
struct timezone tz;
#endif
#ifdef HAVE_SYS_RTPRIO_H
struct rtprio rtp;
#endif
int getprevious = 0, skipprev = 0;
#ifdef __EMX__
long rc;
#endif
struct xmp_module_info mi;
init_drivers();
ctx = xmp_create_context();
xmp_init(ctx, argc, argv);
opt = xmp_get_options(ctx);
//opt->verbosity = 1;
get_options(argc, argv, opt, ctx);
//verbosity = opt->verbosity;
if (!(probeonly || argv[optind])) {
fprintf (stderr, "%s: no modules to play\n"
"Use `%s --help' for more information.\n", argv[0], argv[0]);
exit (-1);
}
#ifndef WIN32
gettimeofday(&tv, &tz);
srand(tv.tv_usec);
#else
srand(GetTickCount());
#endif
if (randomize)
shuffle(argc - optind + 1, &argv[optind - 1]);
if (opt->outfile && (!opt->drv_id || strcmp(opt->drv_id, "wav")))
opt->drv_id = "file";
global_filename = argv[optind];
#ifdef HAVE_TERMIOS_H
if ((background = (tcgetpgrp (0) == getppid ()))) {
//verb = opt->verbosity;
//opt->verbosity = 0;
i = xmp_open_audio(ctx);
//xmp_verbosity_level(ctx, opt->verbosity = verb);
} else
#endif
{
i = xmp_open_audio(ctx);
}
if (i < 0) {
fprintf (stderr, "%s: ", argv[0]);
switch (i) {
case XMP_ERR_DINIT:
fprintf (stderr, "can't initialize driver\n");
return -1;
case XMP_ERR_NODRV:
fprintf (stderr, "driver does not exist\n");
return -2;
case XMP_ERR_DSPEC:
fprintf (stderr, "driver not specified\n");
return -3;
default:
fprintf (stderr, "unknown error\n");
return -128;
}
}
fprintf(stderr, "Extended Module Player " VERSION "\n"
"Copyright (C) 1996-2012 Claudio Matsuoka and Hipolito Carraro Jr\n");
if (1) {
int srate, res, chn, itpt;
xmp_get_driver_cfg(ctx, &srate, &res, &chn, &itpt);
fprintf(stderr, "Using %s\n", (char*)xmp_get_driver_description(ctx));
if (srate) {
fprintf(stderr, "Mixer set to %dbit, %d Hz, %s%s\n", res, srate,
itpt ? "interpolated " : "", chn > 1 ? "stereo" : "mono");
}
}
if (probeonly)
exit(0);
/*
* Support for real-time priority by Douglas Carmichael <dcarmich@mcs.com>
*/
#ifdef HAVE_SYS_RTPRIO_H
if (rt) {
rtp.type = RTP_PRIO_REALTIME;
rtp.prio = 0;
if (rtprio (RTP_SET, 0, &rtp) == -1) {
fprintf (stderr, "%s: Can't get realtime priority.\n", argv[0]);
exit(1);
}
}
#endif
#ifdef __EMX__
rc = DosSetPriority (PRTYS_THREAD, PRTYC_TIMECRITICAL, PRTYD_MINIMUM, 0);
if (rc != 0) {
printf ("Error setting Priority. Priority defaulting to the System Default.\n" );
}
#endif
#ifdef HAVE_SIGNAL_H
signal(SIGTERM, cleanup);
signal(SIGINT, cleanup);
signal(SIGFPE, cleanup);
signal(SIGSEGV, cleanup);
#ifdef SIGQUIT
signal(SIGQUIT, cleanup);
#endif
#ifdef SIGTSTP
signal(SIGTSTP, sigtstp_handler);
#endif
#ifdef SIGCONT
signal(SIGCONT, sigcont_handler);
#endif
#ifdef SIGUSR1
signal(SIGUSR1, sigusr_handler);
signal(SIGUSR2, sigusr_handler);
#endif
#endif
set_tty ();
paused = 0;
time (&t0);
num_mod = lf_flag = 0;
for (first = optind; optind < argc; optind++) {
if (getprevious) {
optind -= 2;
if (optind < first) {
optind += 2;
}
}
if (!background) {
if (lf_flag)
fprintf (stderr, "\n");
lf_flag = fprintf (stderr, "Loading %s... (%d of %d)\n",
argv[optind], optind - first + 1, argc - first);
}
if (background) {
//verb = xmp_verbosity_level(ctx, 0);
t = xmp_load_module(ctx, argv[optind]);
//xmp_verbosity_level(ctx, verb);
} else {
t = xmp_load_module(ctx, argv[optind]);
}
if (t < 0) {
switch (t) {
case -1:
fprintf (stderr, "%s: %s: unrecognized file format\n",
argv[0], argv[optind]);
getprevious = skipprev;
continue;
case -2:
fprintf (stderr, "%s: %s: possibly corrupted file\n",
argv[0], argv[optind]);
getprevious = skipprev;
continue;
case -3: {
char line[1024];
snprintf(line, 1024, "%s: %s", *argv, argv[optind]);
perror(line);
getprevious = skipprev;
continue; }
}
}
rows = tot_nch = max_nch = getprevious = skipprev = 0;
num_mod++;
//xmp_get_module_info(ctx, &mi);
if (loadonly)
goto skip_play;
/* Play the module */
time(&t2);
xmp_player_start(ctx);
for (;;) {
read_keyboard();
if (paused) {
usleep(100000);
} else {
if (xmp_player_frame(ctx) != 0)
break;
xmp_play_buffer(ctx);
xmp_player_get_info(ctx, &mi);
printf("%2d %2d %2d\r", mi.order, mi.pattern, mi.row);
fflush(stdout);
}
}
xmp_player_end(ctx);
time(&t3);
t = difftime(t3, t2);
xmp_release_module(ctx);
if (showtime) {
fprintf(stderr, "\r \r");
}
if (!background) {
fprintf (stderr,
"\rElapsed time : %dmin%02ds %s \n",
t / 60, t % 60, skip ? "(SKIPPED)" : " ");
#if 0
fprintf (stderr, "Channels used : %d/%d", max_nch, mi.chn);
if (max_nch)
fprintf (stderr, ", avg %.2f (%.1f%%)\n",
tot_nch / rows, 100.0 * tot_nch / rows / mi.chn);
else
fprintf (stderr, "\n");
#endif
}
skip_play:
if (skip == -1) {
optind -= optind > first ? 2 : 1;
skipprev = 1;
}
if (skip == -2)
break;
skip = 0;
}
time (&t1);
if (!loadonly && !background && num_mod > 1) {
t = difftime (t1, t0);
fprintf (stderr, "\n\t%d modules played, total time %dh%02dmin%02ds\n",
num_mod, t / 3600, (t % 3600) / 60, t % 60);
}
xmp_close_audio(ctx);
xmp_deinit(ctx);
xmp_free_context(ctx);
reset_tty();
return 0;
}