view src/xmms-sid.cc @ 1:183e7cbc1036

Initial revision
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 03 Jun 2003 10:23:04 +0000
parents
children 5b7009eef767
line wrap: on
line source

/*
   xmms-sid - SIDPlay input plugin for X MultiMedia System (XMMS)

   Main source file

   Originally by Willem Monsuwe <willem@stack.nl>
   Additions, fixes, etc by Matti "ccr" Hamalainen <mhamalai@ratol.fi>

   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 of the License, 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 "xmms-sid.h"
#include <sidplay/player.h>
#include <sidplay/myendian.h>
#include <sidplay/fformat.h>


extern "C" {
#include <pthread.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <xmms/plugin.h>
#include <xmms/util.h>
}


/*
 *	General variables
 */
static struct emuConfig	xs_emuConf;
static emuEngine	xs_emuEngine;
static pthread_t	xs_decode_thread;
static int		xs_error = 0, xs_going = 0, xs_songs = 0;
struct T_sid_cfg	xs_cfg;


/*
 *	Initialize xmms-sid plugin
 */
void xs_init(void)
{

	if (!xs_emuEngine) {
		xs_error = 1;
		XSERR("Couldn't start SIDPlay emulator engine!\n");
		return;
	}

	if (!xs_emuEngine.verifyEndianess()) {
		xs_error = 1;
		XSERR("Wrong hardware endianess (SIDPlay error)!\n");
		return;
	}

	// Initialize STIL structures
	memset(&xs_stil_info, 0, sizeof(xs_stil_info));
	xs_stil_clear();

	// Get configuration
	xs_get_configure();
}


/*
 *	Special, custom hand-made strcpy with smooth leather coating.
 */
int xs_strcpy(char *dest, const char *src, unsigned int *j)
{
 unsigned int i;

 if ((dest == NULL) || (src == NULL)) return -1;

 for (i = 0; i < strlen(src); i++) {
	dest[(*j)++] = src[i];
	}

 return 0;
}


/*
	Create the SID-tune description string from the
	tune's information formatted by the user-specified
	format-string.
*/
static char * xs_make_filedesc(struct sidTuneInfo *s)
{
	unsigned int i, len, j;
	char *result;

	/* Check the info strings */
	if (s->numberOfInfoStrings != 3) {
		if (s->numberOfInfoStrings < 1) {
			return 0;
		}
		return g_strdup(s->infoString[0]);
	}

	/* Check the format-string for NULL */
	if (xs_cfg.fileInfo == NULL) {
		return g_strdup_printf("%s - %s", s->nameString, s->authorString);
		}

	/* Pre-calculate the length of the result-string */
	len = 2;
	for (i = 0; i < strlen(xs_cfg.fileInfo); i++) {
	if (xs_cfg.fileInfo[i] == '%') {
		switch (xs_cfg.fileInfo[++i]) {
		case '1': len += strlen(s->authorString); break;
		case '2': len += strlen(s->nameString); break;
		case '3': len += strlen(s->copyrightString); break;
		case '4': len += strlen(s->formatString); break;
		}
	} else len++;
	}

	/* Allocate the result-string */
	result = (char *) g_malloc(len);

	/* Construct the final result info */
	j = 0;
	for (i = 0; i < strlen(xs_cfg.fileInfo); i++) {

	if (xs_cfg.fileInfo[i] == '%') {
		switch (xs_cfg.fileInfo[++i]) {
		case '1': xs_strcpy(result, s->authorString, &j); break;
		case '2': xs_strcpy(result, s->nameString, &j); break;
		case '3': xs_strcpy(result, s->copyrightString, &j); break;
		case '4': xs_strcpy(result, s->formatString, &j); break;
		}

		} else {
		result[j++] = xs_cfg.fileInfo[i];
		} /* if */

	} /* for */

	result[j] = '\0';

	return result;
}


/*
 *	Check whether the given file is handled by this plugin
 */
int xs_is_our_file(char *filename) 
{
	if (xs_cfg.detectMagic) {
		sidTune *t = new sidTune(filename);

		if (!t->getStatus()) {
			delete t;
			return FALSE;
			}

		delete t;
		return TRUE;
	}

	char *ext = strrchr(filename, '.');
	if (ext) {
		ext++;
		if (!strcasecmp(ext, "psid")) return TRUE;
		if (!strcasecmp(ext, "sid")) return TRUE;
		if (!strcasecmp(ext, "dat")) return TRUE;
		if (!strcasecmp(ext, "inf")) return TRUE;
		if (!strcasecmp(ext, "info")) return TRUE;
	}
	return FALSE;
}


/*
 *	Playing thread loop function
 */
static void * xs_play_loop(void *arg)
{
	sidTune *tune = (sidTune *)arg;
	char data[XMMS_SID_BUFSIZE];
	int fxlen, tn;
	struct sidTuneInfo sidInf;
	char *name;
	enum AFormat fmt = (xs_emuConf.bitsPerSample == 16) ? FMT_S16_NE : FMT_U8;
	gint chn = xs_emuConf.channels;

	tune->getInfo(sidInf);
	name = xs_make_filedesc(&sidInf);

play_loop_new_tune:
	tn = xs_going;
	if (tn <= 0) tn = 1;
	if (!xmms_sid_ip.output->open_audio(fmt, xs_emuConf.frequency, chn))
	{
		xs_error = 1;
		XSERR("Couldn't open XMMS audio output!\n");
		delete tune;
		pthread_exit(NULL);
		xs_stop();
	}

	if (!sidEmuInitializeSong(xs_emuEngine, *tune, tn)) {
		xs_error = 1;
		XSERR("Couldn't initialize SIDPlay emulator engine!\n");
		delete tune;
		pthread_exit(NULL);
		xs_stop();
	}

	tune->getInfo(sidInf);

	xmms_sid_ip.set_info(name, -1,
		1000 * (sidInf.songSpeed ? sidInf.songSpeed : (sidInf.clockSpeed == SIDTUNE_CLOCK_NTSC) ? 60 : 50),
		xs_emuConf.frequency, chn);

	while (xs_going == tn)
	{
		fxlen = XMMS_SID_BUFSIZE;
		sidEmuFillBuffer(xs_emuEngine, *tune, data, fxlen);

		xmms_sid_ip.add_vis_pcm(xmms_sid_ip.output->written_time(),
					fmt, chn, fxlen, data);

		while ((xs_going == tn) && (xmms_sid_ip.output->buffer_free() < fxlen))
			xmms_usleep(10000);

		if (xs_going == tn)
			xmms_sid_ip.output->write_audio(data, fxlen);
	}

	/* Exit the playing thread */
	xmms_sid_ip.output->close_audio();

	if (xs_going) goto play_loop_new_tune;

	g_free(name);

	delete tune;

	pthread_exit(NULL);
}


/*
 *	Start playing the given file
 */
void xs_play_file(char *filename)
{
	sidTune *tune = new sidTune(filename);
	struct sidTuneInfo sidInf;

	/* Get current configuration */
	xs_emuEngine.getConfig(xs_emuConf);

	/* Configure channels and stuff */
	switch (xs_cfg.channels) {

	case XMMS_SID_CHN_AUTOPAN:
		xs_emuConf.channels = SIDEMU_STEREO;
		xs_emuConf.autoPanning = SIDEMU_CENTEREDAUTOPANNING;
		xs_emuConf.volumeControl = SIDEMU_FULLPANNING;
		break;

	case XMMS_SID_CHN_STEREO:
		xs_emuConf.channels = SIDEMU_STEREO;
		xs_emuConf.autoPanning = SIDEMU_NONE;
		xs_emuConf.volumeControl = SIDEMU_NONE;
		break;

	case XMMS_SID_CHN_MONO:
		xs_emuConf.channels = SIDEMU_MONO;
		xs_emuConf.autoPanning = SIDEMU_NONE;
		xs_emuConf.volumeControl = SIDEMU_NONE;
		break;

	default:xs_error = 1;
		XSERR("Internal: Invalid channels setting. Please report to author!\n");
		delete tune;
		break;
	}

	/* Memory mode settings */
	switch (xs_cfg.memoryMode) {
	case XMMS_SID_MPU_BANK_SWITCHING:
		xs_emuConf.memoryMode = MPU_BANK_SWITCHING;
		break;

	case XMMS_SID_MPU_TRANSPARENT_ROM:
		xs_emuConf.memoryMode = MPU_TRANSPARENT_ROM;
		break;

	case XMMS_SID_MPU_PLAYSID_ENVIRONMENT:
		xs_emuConf.memoryMode = MPU_PLAYSID_ENVIRONMENT;
		break;

	default:xs_error = 1;
		XSERR("Internal: Invalid memoryMode setting. Please report to author!\n");
		delete tune;
		break;
	}


	/* Clockspeed settings */
	switch (xs_cfg.clockSpeed) {
	case XMMS_SID_CLOCK_PAL:
		xs_emuConf.clockSpeed = SIDTUNE_CLOCK_PAL;
		break;

	case XMMS_SID_CLOCK_NTSC:
		xs_emuConf.clockSpeed = SIDTUNE_CLOCK_NTSC;
		break;

	default:xs_error = 1;
		XSERR("Internal: Invalid clockSpeed setting. Please report to author!\n");
		delete tune;
		break;
	}

	/* Configure rest of the paske */
	xs_emuConf.bitsPerSample	= xs_cfg.bitsPerSample;
	xs_emuConf.frequency		= xs_cfg.frequency;
	xs_emuConf.sampleFormat		= SIDEMU_SIGNED_PCM;
	xs_emuConf.mos8580		= xs_cfg.mos8580;
	xs_emuConf.emulateFilter	= xs_cfg.emulateFilter;
	xs_emuConf.filterFs		= xs_cfg.filterFs;
	xs_emuConf.filterFm		= xs_cfg.filterFm;
	xs_emuConf.filterFt		= xs_cfg.filterFt;

	/* Now set the emulator configuration */
	xs_emuEngine.setConfig(xs_emuConf);
	tune->getInfo(sidInf);
	xs_error = 0;
	xs_going = sidInf.startSong;
	xs_songs = sidInf.songs;

	/* Start the playing thread! */
	if (pthread_create(&xs_decode_thread, NULL, xs_play_loop, tune) < 0) {
		xs_error = 1;
		XSERR("Couldn't start playing thread!\n");
		delete tune;
	}
}


/*
 *	Stop playing
 */
void xs_stop(void)
{
	if (xs_going)
	{
		xs_going = 0;
		pthread_join(xs_decode_thread, NULL);
	}
}


/*
 *	Pause the playing
 */
void xs_pause(short p)
{
	xmms_sid_ip.output->pause(p);
}


/*
 *	Set the time-seek position
 *	(the playing thread will do the "seeking" aka song-change)
 */
void xs_seek(int time)
{
	if ((time > 0) && (time <= xs_songs)) {
		xs_going = time;
	}
}


/*
 *	Return the playing "position/time" aka song number
 */
int xs_get_time(void)
{
	if (xs_error) return -2;
	if (!xs_going) return -1;
#ifdef HAVE_SONG_POSITION
	set_song_position(xs_going, 1, xs_songs);
#endif
	return xmms_sid_ip.output->output_time();
}
	

/*
 *	Return song information
 */
void xs_get_song_info(char *filename, char **title, int *length)
{
	struct sidTuneInfo sidInf;
	sidTune t(filename);

	if (!t) return;

	t.getInfo(sidInf);

	*title = xs_make_filedesc(&sidInf);

	*length = -1;
}