Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
/*
* AdPlay/UNIX - OPL2 audio player
* Copyright (C) 2001 - 2017 Simon Peter <dn.tlp@gmx.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <adplug/adplug.h>
#include <adplug/emuopl.h>
#include <adplug/kemuopl.h>
#include <adplug/wemuopl.h>
/*
* Apple (OS X) and Sun systems declare getopt in unistd.h, other systems
* (Linux) use getopt.h.
*/
#if defined (__APPLE__) || (defined(__SVR4) && defined(__sun))
# include <unistd.h>
#else
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# else
# include "getopt.h"
// ALSA now includes the system-wide getopt under Linux, so stop this
# define _GETOPT_POSIX_H 1
# endif
#endif
#include "defines.h"
#ifdef HAVE_ADPLUG_NUKEDOPL
#include <adplug/nemuopl.h>
#endif
#ifdef HAVE_ADPLUG_SURROUND
#include <adplug/surroundopl.h>
#endif
#include "output.h"
#include "players.h"
/***** Defines *****/
// Default file name of AdPlug's database file
#define ADPLUGDB_FILE "adplug.db"
// Default AdPlug user's configuration subdirectory
#define ADPLUG_CONFDIR ".adplug"
// Default path to AdPlug's system-wide database file
#ifdef ADPLUG_DATA_DIR
# define ADPLUGDB_PATH ADPLUG_DATA_DIR "/" ADPLUGDB_FILE
#else
# define ADPLUGDB_PATH ADPLUGDB_FILE
#endif
/***** Typedefs *****/
typedef enum {
Emu_Satoh,
Emu_Ken,
Emu_Woody,
#ifdef HAVE_ADPLUG_NUKEDOPL
Emu_Nuked,
#endif
} EmuType;
/***** Global variables *****/
static const char *program_name;
static Player *player = 0; // global player object
static CAdPlugDatabase mydb;
static Copl *opl = 0;
/***** Configuration (and defaults) *****/
static struct {
int buf_size, freq, channels, bits, harmonic, message_level;
unsigned int subsong;
const char *device;
char *userdb;
bool endless, showinsts, songinfo, songmessage;
EmuType emutype;
Outputs output;
} cfg = {
2048, 44100,
#ifdef HAVE_ADPLUG_SURROUND
2, 16, 1, // Default to surround if available
#else
1, 16, 0, // Else default to mono (until stereo w/ single OPL is fixed)
#endif
MSG_NOTE,
(unsigned int)-1,
NULL,
NULL,
true, false, false, false,
Emu_Woody,
DEFAULT_DRIVER
};
/***** Global functions *****/
void message(int level, const char *fmt, ...)
{
va_list argptr;
if(cfg.message_level < level) return;
fprintf(stderr, "%s: ", program_name);
va_start(argptr, fmt);
vfprintf(stderr, fmt, argptr);
va_end(argptr);
fprintf(stderr, "\n");
}
/***** Local functions *****/
static void usage()
/* Print usage information. */
{
printf("Usage: %s [OPTION]... FILE...\n\n"
"Output selection:\n"
" -e, --emulator=EMULATOR specify emulator to use\n"
" -O, --output=OUTPUT specify output mechanism\n\n"
"OSS driver (oss) specific:\n"
" -d, --device=FILE set sound device file to FILE\n"
" -b, --buffer=SIZE set output buffer size to SIZE\n\n"
"Disk writer (disk) specific:\n"
" -d, --device=FILE output to FILE ('-' is stdout)\n\n"
"EsounD driver (esound) specific:\n"
" -d, --device=URL URL to EsounD server host (hostname:port)\n\n"
"SDL driver (sdl) specific:\n"
" -b, --buffer=SIZE set output buffer size to SIZE\n\n"
"ALSA driver (alsa) specific:\n"
" -d, --device=DEVICE set sound device to DEVICE\n"
" -b, --buffer=SIZE set output buffer size to SIZE\n\n"
"Playback quality:\n"
" -8, --8bit 8-bit sample quality\n"
" --16bit 16-bit sample quality\n"
" -f, --freq=FREQ set sample frequency to FREQ\n"
" --surround stereo/surround stream\n"
" --stereo stereo stream\n"
" --mono mono stream\n\n"
"Informative output:\n"
" -i, --instruments display instrument names\n"
" -r, --realtime display realtime song info\n"
" -m, --message display song message\n\n"
"Playback:\n"
" -s, --subsong=N play subsong number N\n"
" -o, --once play only once, don't loop\n\n"
"Generic:\n"
" -D, --database=FILE additionally use database file FILE\n"
" -q, --quiet be more quiet\n"
" -v, --verbose be more verbose\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n\n",
program_name);
// Print list of available output mechanisms
printf("Available emulators: satoh ken woody ");
#ifdef HAVE_ADPLUG_NUKEDOPL
printf("nuked");
#endif
printf("\n");
printf("Available output mechanisms: "
#ifdef DRIVER_OSS
"oss "
#endif
#ifdef DRIVER_NULL
"null "
#endif
#ifdef DRIVER_DISK
"disk "
#endif
#ifdef DRIVER_ESOUND
"esound "
#endif
#ifdef DRIVER_QSA
"qsa "
#endif
#ifdef DRIVER_SDL
"sdl "
#endif
#ifdef DRIVER_AO
"ao "
#endif
#ifdef DRIVER_ALSA
"alsa "
#endif
"\n");
}
static int decode_switches(int argc, char **argv)
/*
* Set all the option flags according to the switches specified.
* Return the index of the first non-option argument.
*/
{
int c;
struct option const long_options[] = {
{"8bit", no_argument, NULL, '8'}, // 8-bit replay
{"16bit", no_argument, NULL, '1'}, // 16-bit replay
{"freq", required_argument, NULL, 'f'}, // set frequency
{"surround", no_argument, NULL, '4'}, // stereo/harmonic replay
{"stereo", no_argument, NULL, '3'}, // stereo replay
{"mono", no_argument, NULL, '2'}, // mono replay
{"buffer", required_argument, NULL, 'b'}, // buffer size
{"device", required_argument, NULL, 'd'}, // device file
{"instruments", no_argument, NULL, 'i'}, // show instruments
{"realtime", no_argument, NULL, 'r'}, // realtime song info
{"message", no_argument, NULL, 'm'}, // song message
{"subsong", no_argument, NULL, 's'}, // play subsong
{"once", no_argument, NULL, 'o'}, // don't loop
{"help", no_argument, NULL, 'h'}, // display help
{"version", no_argument, NULL, 'V'}, // version information
{"emulator", required_argument, NULL, 'e'}, // emulator to use
{"output", required_argument, NULL, 'O'}, // output mechanism
{"database", required_argument, NULL, 'D'}, // different database
{"quiet", no_argument, NULL, 'q'}, // be more quiet
{"verbose", no_argument, NULL, 'v'}, // be more verbose
{NULL, 0, NULL, 0} // end of options
};
while ((c = getopt_long(argc, argv, "8f:b:d:irms:ohVe:O:D:qv",
long_options, (int *)0)) != EOF) {
switch (c) {
case '8': cfg.bits = 8; break;
case '1': cfg.bits = 16; break;
case 'f': cfg.freq = atoi(optarg); break;
case '4': cfg.channels = 2; cfg.harmonic = 1; break;
case '3': cfg.channels = 2; cfg.harmonic = 0; break;
case '2': cfg.channels = 1; cfg.harmonic = 0; break;
case 'b': cfg.buf_size = atoi(optarg); break;
case 'd': cfg.device = optarg; break;
case 'i': cfg.showinsts = true; break;
case 'r': cfg.songinfo = true; break;
case 'm': cfg.songmessage = true; break;
case 's': cfg.subsong = atoi(optarg); break;
case 'o': cfg.endless = false; break;
case 'V': puts(ADPLAY_VERSION); exit(EXIT_SUCCESS);
case 'h': usage(); exit(EXIT_SUCCESS); break;
case 'D':
if(!mydb.load(optarg))
message(MSG_WARN, "could not open database -- %s", optarg);
break;
case 'O':
if(!strcmp(optarg,"oss")) cfg.output = oss;
else if(!strcmp(optarg,"null")) cfg.output = null;
else if(!strcmp(optarg,"disk")) {
cfg.output = disk;
cfg.endless = false; // endless output is almost never desired here
}
else if(!strcmp(optarg,"esound")) cfg.output = esound;
else if(!strcmp(optarg,"qsa")) cfg.output = qsa;
else if(!strcmp(optarg,"alsa")) cfg.output = alsa;
else if(!strcmp(optarg,"sdl")) cfg.output = sdl;
else if(!strcmp(optarg,"ao")) cfg.output = ao;
else {
message(MSG_ERROR, "unknown output method -- %s", optarg);
exit(EXIT_FAILURE);
}
break;
case 'e':
if(!strcmp(optarg, "satoh")) cfg.emutype = Emu_Satoh;
else if(!strcmp(optarg, "ken")) cfg.emutype = Emu_Ken;
else if(!strcmp(optarg, "woody")) cfg.emutype = Emu_Woody;
#ifdef HAVE_ADPLUG_NUKEDOPL
else if(!strcmp(optarg, "nuked")) cfg.emutype = Emu_Nuked;
#endif
else {
message(MSG_ERROR, "unknown emulator -- %s", optarg);
exit(EXIT_FAILURE);
}
case 'q': if(cfg.message_level) cfg.message_level--; break;
case 'v': cfg.message_level++; break;
}
}
return optind;
}
static void play(const char *fn, Player *pl, int subsong = -1)
/*
* Start playback of subsong 'subsong' of file 'fn', using player
* 'player'. If 'subsong' is not given or -1, start playback of
* default subsong of file.
*/
{
unsigned long i;
// initialize output & player
pl->get_opl()->init();
delete pl->p;
pl->p = CAdPlug::factory(fn,pl->get_opl());
if(!pl->p) {
message(MSG_WARN, "unknown filetype -- %s", fn);
return;
}
if(subsong != -1)
pl->p->rewind(subsong);
#ifdef HAVE_ADPLUG_GETSUBSONG
else
subsong = pl->p->getsubsong();
#endif
fprintf(stderr, "Playing '%s'...\n"
"Type : %s\n"
"Title : %s\n"
"Author: %s\n\n", fn, pl->p->gettype().c_str(),
pl->p->gettitle().c_str(), pl->p->getauthor().c_str());
if(cfg.showinsts) { // display instruments
fprintf(stderr, "Instrument names:\n");
for(i = 0;i < pl->p->getinstruments(); i++)
fprintf(stderr, "%2lu: %s\n", i, pl->p->getinstrument(i).c_str());
fprintf(stderr, "\n");
}
if(cfg.songmessage) // display song message
fprintf(stderr, "Song message:\n%s\n\n", pl->p->getdesc().c_str());
// play loop
do {
if(cfg.songinfo) // display song info
fprintf(stderr, "Subsong: %d/%d, Order: %d/%d, Pattern: %d/%d, Row: %d, "
"Speed: %d, Timer: %.2fHz \r",
subsong, pl->p->getsubsongs()-1, pl->p->getorder(),
pl->p->getorders(), pl->p->getpattern(), pl->p->getpatterns(),
pl->p->getrow(), pl->p->getspeed(), pl->p->getrefresh());
pl->frame();
} while(pl->playing || cfg.endless);
}
static void shutdown(void)
/* General deinitialization handler. */
{
if(player) delete player;
if(opl) delete opl;
// Try to properly reposition terminal cursor, if Ctrl+C is used to exit.
printf("\n\n");
}
static void sighandler(int signal)
/* Handles all kinds of signals. */
{
switch(signal) {
case SIGINT:
case SIGTERM:
exit(EXIT_SUCCESS);
}
}
/***** Main program *****/
int main(int argc, char **argv)
{
int optind, i;
const char *homedir;
char *userdb;
// init
program_name = argv[0];
atexit(shutdown);
signal(SIGINT, sighandler); signal(SIGTERM, sighandler);
// Try user's home directory first, before trying the default location.
homedir = getenv("HOME");
if(homedir) {
userdb = (char *)malloc(strlen(homedir) + strlen(ADPLUG_CONFDIR) +
strlen(ADPLUGDB_FILE) + 3);
strcpy(userdb, homedir); strcat(userdb, "/" ADPLUG_CONFDIR "/");
strcat(userdb, ADPLUGDB_FILE);
}
// parse commandline
optind = decode_switches(argc,argv);
if(optind == argc) { // no filename given
fprintf(stderr, "%s: need at least one file for playback\n", program_name);
fprintf(stderr, "Try '%s --help' for more information.\n", program_name);
if(userdb) free(userdb);
exit(EXIT_FAILURE);
}
if(argc - optind > 1) cfg.endless = false; // more than 1 file given
// init emulator
switch(cfg.emutype) {
case Emu_Satoh:
if (cfg.harmonic) {
#ifdef HAVE_ADPLUG_SURROUND
COPLprops a, b;
a.use16bit = b.use16bit = cfg.bits == 16;
a.stereo = b.stereo = false;
a.opl = new CEmuopl(cfg.freq, a.use16bit, a.stereo);
b.opl = new CEmuopl(cfg.freq, b.use16bit, b.stereo);
opl = new CSurroundopl(&a, &b, cfg.bits == 16);
// CSurroundopl now owns a.opl and b.opl and will free upon destruction
#else
fprintf(stderr, "Surround requires AdPlug v2.2 or newer. Use --mono "
"or upgrade and recompile AdPlay.\n");
if(userdb) free(userdb);
exit(EXIT_FAILURE);
#endif
} else {
opl = new CEmuopl(cfg.freq, cfg.bits == 16, cfg.channels == 2);
}
break;
case Emu_Ken:
if (cfg.harmonic) {
#ifdef HAVE_ADPLUG_SURROUND
fprintf(stderr, "%s: Sorry, Ken's emulator only supports one instance "
"so does not work properly in surround mode.\n", program_name);
// Leave the code though for future use (once Ken's emu is wrapped up
// in a class or something. It works, it just sounds really bad.)
COPLprops a, b;
a.use16bit = b.use16bit = cfg.bits == 16;
a.stereo = b.stereo = false;
a.opl = new CKemuopl(cfg.freq, a.use16bit, a.stereo);
b.opl = new CKemuopl(cfg.freq, b.use16bit, b.stereo);
opl = new CSurroundopl(&a, &b, cfg.bits == 16);
// CSurroundopl now owns a and b and will free upon destruction
#else
fprintf(stderr, "Surround requires AdPlug v2.2 or newer. Use --mono "
"or upgrade and recompile AdPlay.\n");
if(userdb) free(userdb);
exit(EXIT_FAILURE);
#endif
} else {
opl = new CKemuopl(cfg.freq, cfg.bits == 16, cfg.channels == 2);
}
break;
case Emu_Woody:
if (cfg.harmonic) {
#ifdef HAVE_ADPLUG_SURROUND
COPLprops a, b;
a.use16bit = b.use16bit = cfg.bits == 16;
a.stereo = b.stereo = false;
a.opl = new CWemuopl(cfg.freq, a.use16bit, a.stereo);
b.opl = new CWemuopl(cfg.freq, b.use16bit, b.stereo);
opl = new CSurroundopl(&a, &b, cfg.bits == 16);
// CSurroundopl now owns a and b and will free upon destruction
#else
fprintf(stderr, "Surround requires AdPlug v2.2 or newer. Use --mono "
"or upgrade and recompile AdPlay.\n");
if(userdb) free(userdb);
exit(EXIT_FAILURE);
#endif
} else {
opl = new CWemuopl(cfg.freq, cfg.bits == 16, cfg.channels == 2);
}
break;
#ifdef HAVE_ADPLUG_NUKEDOPL
case Emu_Nuked:
if (cfg.harmonic) {
COPLprops a, b;
a.use16bit = b.use16bit = true; // Nuked only supports 16-bit
a.stereo = b.stereo = true; // Nuked only supports stereo
a.opl = new CNemuopl(cfg.freq);
b.opl = new CNemuopl(cfg.freq);
opl = new CSurroundopl(&a, &b, cfg.bits == 16); // SurroundOPL can convert to 8-bit though
// CSurroundopl now owns a and b and will free upon destruction
} else {
if(cfg.bits != 16 || cfg.channels != 2) {
fprintf(stderr, "Sorry, Nuked OPL3 emulator only works in stereo 16 bits. "
"Use --stereo and --16bit options.\n");
if(userdb) free(userdb);
exit(EXIT_FAILURE);
} else {
opl = new CNemuopl(cfg.freq);
}
}
break;
#endif
}
// init player
switch(cfg.output) {
case none:
message(MSG_PANIC, "no output methods compiled in");
exit(EXIT_FAILURE);
#ifdef DRIVER_OSS
case oss:
player = new OSSPlayer(opl, cfg.device, cfg.bits, cfg.channels, cfg.freq,
cfg.buf_size);
break;
#endif
#ifdef DRIVER_NULL
case null:
player = new NullOutput();
break;
#endif
#ifdef DRIVER_DISK
case disk:
player = new DiskWriter(opl, cfg.device, cfg.bits, cfg.channels, cfg.freq);
break;
#endif
#ifdef DRIVER_ESOUND
case esound:
player = new EsoundPlayer(opl, cfg.bits, cfg.channels, cfg.freq,
cfg.device);
break;
#endif
#ifdef DRIVER_QSA
case qsa:
player = new QSAPlayer(opl, cfg.bits, cfg.channels, cfg.freq);
break;
#endif
#ifdef DRIVER_AO
case ao:
player = new AOPlayer(opl, cfg.device, cfg.bits, cfg.channels, cfg.freq, cfg.buf_size);
break;
#endif
#
#ifdef DRIVER_SDL
case sdl:
player = new SDLPlayer(opl, cfg.bits, cfg.channels, cfg.freq, cfg.buf_size);
break;
#endif
#ifdef DRIVER_ALSA
case alsa:
player = new ALSAPlayer(opl, cfg.device, cfg.bits, cfg.channels, cfg.freq,
cfg.buf_size);
break;
#endif
default:
message(MSG_ERROR, "output method not available");
exit(EXIT_FAILURE);
}
// load database
if(userdb) { mydb.load(userdb); free(userdb); }
mydb.load(ADPLUGDB_PATH);
CAdPlug::set_database(&mydb);
// play all files from commandline
for(i=optind;i<argc;i++)
play(argv[i],player,cfg.subsong);
// deinit
exit(EXIT_SUCCESS);
}