2009-02-11 20:31:17 +01:00
|
|
|
/*
|
2011-01-29 10:13:54 +01:00
|
|
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
2009-02-11 20:31:17 +01:00
|
|
|
* http://www.musicpd.org
|
|
|
|
*
|
|
|
|
* 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.
|
2009-03-13 18:43:16 +01:00
|
|
|
*
|
|
|
|
* 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.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2009-02-11 20:31:17 +01:00
|
|
|
*/
|
|
|
|
|
2009-11-12 09:12:38 +01:00
|
|
|
#include "config.h"
|
2013-07-28 13:18:48 +02:00
|
|
|
#include "../DecoderAPI.hxx"
|
2009-11-12 09:12:38 +01:00
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
extern "C" {
|
2013-07-29 07:32:36 +02:00
|
|
|
#include "TagHandler.hxx"
|
2009-02-11 20:31:17 +01:00
|
|
|
}
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
#include <errno.h>
|
|
|
|
#include <stdlib.h>
|
2013-07-30 20:11:57 +02:00
|
|
|
#include <string.h>
|
2009-02-11 20:31:17 +01:00
|
|
|
#include <glib.h>
|
|
|
|
|
|
|
|
#include <sidplay/sidplay2.h>
|
|
|
|
#include <sidplay/builders/resid.h>
|
2009-08-30 19:49:04 +02:00
|
|
|
#include <sidplay/utils/SidTuneMod.h>
|
2009-02-11 20:31:17 +01:00
|
|
|
|
|
|
|
#undef G_LOG_DOMAIN
|
|
|
|
#define G_LOG_DOMAIN "sidplay"
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
#define SUBTUNE_PREFIX "tune_"
|
|
|
|
|
|
|
|
static GPatternSpec *path_with_subtune;
|
2009-08-30 19:49:04 +02:00
|
|
|
static const char *songlength_file;
|
|
|
|
static GKeyFile *songlength_database;
|
2009-08-30 19:48:56 +02:00
|
|
|
|
|
|
|
static bool all_files_are_containers;
|
2009-08-30 19:49:04 +02:00
|
|
|
static unsigned default_songlength;
|
2009-08-30 19:48:56 +02:00
|
|
|
|
2009-09-26 02:20:17 +02:00
|
|
|
static bool filter_setting;
|
|
|
|
|
2009-09-24 10:14:05 +02:00
|
|
|
static GKeyFile *
|
|
|
|
sidplay_load_songlength_db(const char *path)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
gchar *data;
|
|
|
|
gsize size;
|
|
|
|
|
|
|
|
if (!g_file_get_contents(path, &data, &size, &error)) {
|
|
|
|
g_warning("unable to read songlengths file %s: %s",
|
|
|
|
path, error->message);
|
|
|
|
g_error_free(error);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* replace any ; comment characters with # */
|
|
|
|
for (gsize i = 0; i < size; i++)
|
|
|
|
if (data[i] == ';')
|
|
|
|
data[i] = '#';
|
|
|
|
|
|
|
|
GKeyFile *db = g_key_file_new();
|
2009-09-24 10:17:58 +02:00
|
|
|
bool success = g_key_file_load_from_data(db, data, size,
|
|
|
|
G_KEY_FILE_NONE, &error);
|
|
|
|
g_free(data);
|
|
|
|
if (!success) {
|
2009-09-24 10:14:05 +02:00
|
|
|
g_warning("unable to parse songlengths file %s: %s",
|
|
|
|
path, error->message);
|
|
|
|
g_error_free(error);
|
|
|
|
g_key_file_free(db);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_key_file_set_list_separator(db, ' ');
|
|
|
|
return db;
|
|
|
|
}
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
static bool
|
2013-08-04 11:30:26 +02:00
|
|
|
sidplay_init(const config_param ¶m)
|
2009-08-30 19:48:56 +02:00
|
|
|
{
|
2009-08-30 19:49:04 +02:00
|
|
|
/* read the songlengths database file */
|
2013-08-04 11:30:26 +02:00
|
|
|
songlength_file = param.GetBlockValue("songlength_database");
|
2009-09-24 10:14:05 +02:00
|
|
|
if (songlength_file != NULL)
|
|
|
|
songlength_database = sidplay_load_songlength_db(songlength_file);
|
2009-08-30 19:49:04 +02:00
|
|
|
|
2013-08-04 11:30:26 +02:00
|
|
|
default_songlength = param.GetBlockValue("default_songlength", 0u);
|
2009-08-30 19:49:04 +02:00
|
|
|
|
2013-08-04 11:30:26 +02:00
|
|
|
all_files_are_containers =
|
|
|
|
param.GetBlockValue("all_files_are_containers", true);
|
2009-08-30 19:48:56 +02:00
|
|
|
|
|
|
|
path_with_subtune=g_pattern_spec_new(
|
|
|
|
"*/" SUBTUNE_PREFIX "???.sid");
|
|
|
|
|
2013-08-04 11:30:26 +02:00
|
|
|
filter_setting = param.GetBlockValue("filter", true);
|
2009-09-26 02:20:17 +02:00
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-07-30 11:33:06 +02:00
|
|
|
static void
|
2009-08-30 19:48:56 +02:00
|
|
|
sidplay_finish()
|
|
|
|
{
|
|
|
|
g_pattern_spec_free(path_with_subtune);
|
2009-08-30 19:49:04 +02:00
|
|
|
|
|
|
|
if(songlength_database)
|
|
|
|
g_key_file_free(songlength_database);
|
2009-08-30 19:48:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns the file path stripped of any /tune_xxx.sid subtune
|
|
|
|
* suffix
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
get_container_name(const char *path_fs)
|
|
|
|
{
|
|
|
|
char *path_container=g_strdup(path_fs);
|
|
|
|
|
|
|
|
if(!g_pattern_match(path_with_subtune,
|
|
|
|
strlen(path_container), path_container, NULL))
|
|
|
|
return path_container;
|
|
|
|
|
|
|
|
char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
|
|
|
if(ptr) *ptr='\0';
|
|
|
|
|
|
|
|
return path_container;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns tune number from file.sid/tune_xxx.sid style path or 1 if
|
|
|
|
* no subtune is appended
|
|
|
|
*/
|
2012-07-30 11:33:06 +02:00
|
|
|
static unsigned
|
2009-08-30 19:48:56 +02:00
|
|
|
get_song_num(const char *path_fs)
|
|
|
|
{
|
|
|
|
if(g_pattern_match(path_with_subtune,
|
|
|
|
strlen(path_fs), path_fs, NULL)) {
|
|
|
|
char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
|
|
|
if(!sub) return 1;
|
|
|
|
|
|
|
|
sub+=strlen("/" SUBTUNE_PREFIX);
|
|
|
|
int song_num=strtol(sub, NULL, 10);
|
|
|
|
|
|
|
|
if (errno == EINVAL)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return song_num;
|
|
|
|
} else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-08-30 19:49:04 +02:00
|
|
|
/* get the song length in seconds */
|
|
|
|
static int
|
|
|
|
get_song_length(const char *path_fs)
|
|
|
|
{
|
|
|
|
if (songlength_database == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
gchar *sid_file=get_container_name(path_fs);
|
|
|
|
SidTuneMod tune(sid_file);
|
|
|
|
g_free(sid_file);
|
|
|
|
if(!tune) {
|
|
|
|
g_warning("failed to load file for calculating md5 sum");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
char md5sum[SIDTUNE_MD5_LENGTH+1];
|
|
|
|
tune.createMD5(md5sum);
|
|
|
|
|
2012-07-30 11:33:06 +02:00
|
|
|
const unsigned song_num = get_song_num(path_fs);
|
2009-08-30 19:49:04 +02:00
|
|
|
|
|
|
|
gsize num_items;
|
|
|
|
gchar **values=g_key_file_get_string_list(songlength_database,
|
|
|
|
"Database", md5sum, &num_items, NULL);
|
|
|
|
if(!values || song_num>num_items) {
|
|
|
|
g_strfreev(values);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int minutes=strtol(values[song_num-1], NULL, 10);
|
|
|
|
if(errno==EINVAL) minutes=0;
|
|
|
|
|
|
|
|
int seconds;
|
|
|
|
char *ptr=strchr(values[song_num-1], ':');
|
|
|
|
if(ptr) {
|
|
|
|
seconds=strtol(ptr+1, NULL, 10);
|
|
|
|
if(errno==EINVAL) seconds=0;
|
|
|
|
} else
|
|
|
|
seconds=0;
|
|
|
|
|
|
|
|
g_strfreev(values);
|
|
|
|
|
|
|
|
return (minutes*60)+seconds;
|
|
|
|
}
|
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
static void
|
|
|
|
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
|
|
|
|
{
|
2010-10-27 21:16:24 +02:00
|
|
|
int channels;
|
2009-02-11 20:31:17 +01:00
|
|
|
|
|
|
|
/* load the tune */
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
char *path_container=get_container_name(path_fs);
|
|
|
|
SidTune tune(path_container, NULL, true);
|
|
|
|
g_free(path_container);
|
2009-02-11 20:31:17 +01:00
|
|
|
if (!tune) {
|
|
|
|
g_warning("failed to load file");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
int song_num=get_song_num(path_fs);
|
|
|
|
tune.selectSong(song_num);
|
2009-02-11 20:31:17 +01:00
|
|
|
|
2009-08-30 19:49:16 +02:00
|
|
|
int song_len=get_song_length(path_fs);
|
|
|
|
if(song_len==-1) song_len=default_songlength;
|
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
/* initialize the player */
|
|
|
|
|
|
|
|
sidplay2 player;
|
|
|
|
int iret = player.load(&tune);
|
|
|
|
if (iret != 0) {
|
|
|
|
g_warning("sidplay2.load() failed: %s", player.error());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the builder */
|
|
|
|
|
|
|
|
ReSIDBuilder builder("ReSID");
|
|
|
|
if (!builder) {
|
|
|
|
g_warning("failed to initialize ReSIDBuilder");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.create(player.info().maxsids);
|
|
|
|
if (!builder) {
|
|
|
|
g_warning("ReSIDBuilder.create() failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-09-26 02:20:17 +02:00
|
|
|
builder.filter(filter_setting);
|
2009-02-11 20:31:17 +01:00
|
|
|
if (!builder) {
|
|
|
|
g_warning("ReSIDBuilder.filter() failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* configure the player */
|
|
|
|
|
|
|
|
sid2_config_t config = player.config();
|
|
|
|
|
|
|
|
config.clockDefault = SID2_CLOCK_PAL;
|
|
|
|
config.clockForced = true;
|
|
|
|
config.clockSpeed = SID2_CLOCK_CORRECT;
|
|
|
|
config.frequency = 48000;
|
|
|
|
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
2010-10-27 21:16:24 +02:00
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
config.precision = 16;
|
|
|
|
config.sidDefault = SID2_MOS6581;
|
|
|
|
config.sidEmulation = &builder;
|
|
|
|
config.sidModel = SID2_MODEL_CORRECT;
|
|
|
|
config.sidSamples = true;
|
|
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
|
|
config.sampleFormat = SID2_LITTLE_SIGNED;
|
|
|
|
#else
|
|
|
|
config.sampleFormat = SID2_BIG_SIGNED;
|
|
|
|
#endif
|
2010-10-27 21:16:24 +02:00
|
|
|
if (tune.isStereo()) {
|
|
|
|
config.playback = sid2_stereo;
|
|
|
|
channels = 2;
|
|
|
|
} else {
|
|
|
|
config.playback = sid2_mono;
|
|
|
|
channels = 1;
|
|
|
|
}
|
2009-02-11 20:31:17 +01:00
|
|
|
|
|
|
|
iret = player.config(config);
|
|
|
|
if (iret != 0) {
|
|
|
|
g_warning("sidplay2.config() failed: %s", player.error());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the MPD decoder */
|
|
|
|
|
2013-08-03 21:00:50 +02:00
|
|
|
const AudioFormat audio_format(48000, SampleFormat::S16, channels);
|
|
|
|
assert(audio_format.IsValid());
|
2009-02-11 20:31:17 +01:00
|
|
|
|
2013-08-03 21:00:50 +02:00
|
|
|
decoder_initialized(decoder, audio_format, true, (float)song_len);
|
2009-02-11 20:31:17 +01:00
|
|
|
|
|
|
|
/* .. and play */
|
|
|
|
|
2009-11-14 00:15:04 +01:00
|
|
|
const unsigned timebase = player.timebase();
|
|
|
|
song_len *= timebase;
|
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
enum decoder_command cmd;
|
|
|
|
do {
|
|
|
|
char buffer[4096];
|
|
|
|
size_t nbytes;
|
|
|
|
|
|
|
|
nbytes = player.play(buffer, sizeof(buffer));
|
|
|
|
if (nbytes == 0)
|
|
|
|
break;
|
|
|
|
|
2009-12-25 19:47:33 +01:00
|
|
|
decoder_timestamp(decoder, (double)player.time() / timebase);
|
|
|
|
|
2010-01-03 22:44:23 +01:00
|
|
|
cmd = decoder_data(decoder, NULL, buffer, nbytes, 0);
|
2009-08-30 19:49:16 +02:00
|
|
|
|
|
|
|
if(cmd==DECODE_COMMAND_SEEK) {
|
2009-12-25 19:47:33 +01:00
|
|
|
unsigned data_time = player.time();
|
2009-11-14 00:15:04 +01:00
|
|
|
unsigned target_time = (unsigned)
|
|
|
|
(decoder_seek_where(decoder) * timebase);
|
2009-08-30 19:49:16 +02:00
|
|
|
|
|
|
|
/* can't rewind so return to zero and seek forward */
|
|
|
|
if(target_time<data_time) {
|
|
|
|
player.stop();
|
|
|
|
data_time=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ignore data until target time is reached */
|
|
|
|
while(data_time<target_time) {
|
|
|
|
nbytes=player.play(buffer, sizeof(buffer));
|
|
|
|
if(nbytes==0)
|
|
|
|
break;
|
2009-11-14 00:15:04 +01:00
|
|
|
data_time = player.time();
|
2009-08-30 19:49:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
decoder_command_finished(decoder);
|
|
|
|
}
|
|
|
|
|
2012-07-30 11:33:06 +02:00
|
|
|
if (song_len > 0 && player.time() >= (unsigned)song_len)
|
2009-08-30 19:49:16 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
} while (cmd != DECODE_COMMAND_STOP);
|
2009-02-11 20:31:17 +01:00
|
|
|
}
|
|
|
|
|
2012-02-11 19:12:02 +01:00
|
|
|
static bool
|
|
|
|
sidplay_scan_file(const char *path_fs,
|
|
|
|
const struct tag_handler *handler, void *handler_ctx)
|
2009-02-11 20:31:17 +01:00
|
|
|
{
|
2009-08-30 19:48:56 +02:00
|
|
|
int song_num=get_song_num(path_fs);
|
|
|
|
char *path_container=get_container_name(path_fs);
|
|
|
|
|
|
|
|
SidTune tune(path_container, NULL, true);
|
|
|
|
g_free(path_container);
|
2009-02-11 20:31:17 +01:00
|
|
|
if (!tune)
|
2012-02-11 19:12:02 +01:00
|
|
|
return false;
|
2009-02-11 20:31:17 +01:00
|
|
|
|
|
|
|
const SidTuneInfo &info = tune.getInfo();
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
/* title */
|
|
|
|
const char *title;
|
2009-02-11 20:31:17 +01:00
|
|
|
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
|
2009-08-30 19:48:56 +02:00
|
|
|
title=info.infoString[0];
|
|
|
|
else
|
|
|
|
title="";
|
|
|
|
|
|
|
|
if(info.songs>1) {
|
|
|
|
char *tag_title=g_strdup_printf("%s (%d/%d)",
|
|
|
|
title, song_num, info.songs);
|
2012-02-11 19:12:02 +01:00
|
|
|
tag_handler_invoke_tag(handler, handler_ctx,
|
|
|
|
TAG_TITLE, tag_title);
|
2009-08-30 19:48:56 +02:00
|
|
|
g_free(tag_title);
|
|
|
|
} else
|
2012-02-11 19:12:02 +01:00
|
|
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
2009-08-30 19:48:56 +02:00
|
|
|
|
|
|
|
/* artist */
|
2009-02-11 20:31:17 +01:00
|
|
|
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
|
2012-02-11 19:12:02 +01:00
|
|
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
|
|
|
|
info.infoString[1]);
|
2009-02-11 20:31:17 +01:00
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
/* track */
|
|
|
|
char *track=g_strdup_printf("%d", song_num);
|
2012-02-11 19:12:02 +01:00
|
|
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
2009-08-30 19:48:56 +02:00
|
|
|
g_free(track);
|
|
|
|
|
2009-08-30 19:49:04 +02:00
|
|
|
/* time */
|
|
|
|
int song_len=get_song_length(path_fs);
|
2012-02-11 19:12:02 +01:00
|
|
|
if (song_len >= 0)
|
|
|
|
tag_handler_invoke_duration(handler, handler_ctx, song_len);
|
2009-08-30 19:49:04 +02:00
|
|
|
|
2012-02-11 19:12:02 +01:00
|
|
|
return true;
|
2009-02-11 20:31:17 +01:00
|
|
|
}
|
|
|
|
|
2009-08-30 19:48:56 +02:00
|
|
|
static char *
|
|
|
|
sidplay_container_scan(const char *path_fs, const unsigned int tnum)
|
|
|
|
{
|
|
|
|
SidTune tune(path_fs, NULL, true);
|
|
|
|
if (!tune)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
const SidTuneInfo &info=tune.getInfo();
|
|
|
|
|
|
|
|
/* Don't treat sids containing a single tune
|
|
|
|
as containers */
|
|
|
|
if(!all_files_are_containers && info.songs<2)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Construct container/tune path names, eg.
|
|
|
|
Delta.sid/tune_001.sid */
|
|
|
|
if(tnum<=info.songs) {
|
|
|
|
char *subtune= g_strdup_printf(
|
|
|
|
SUBTUNE_PREFIX "%03u.sid", tnum);
|
|
|
|
return subtune;
|
|
|
|
} else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-02-11 20:31:17 +01:00
|
|
|
static const char *const sidplay_suffixes[] = {
|
|
|
|
"sid",
|
2010-10-27 21:18:43 +02:00
|
|
|
"mus",
|
|
|
|
"str",
|
|
|
|
"prg",
|
|
|
|
"P00",
|
2009-02-11 20:31:17 +01:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
extern const struct decoder_plugin sidplay_decoder_plugin;
|
|
|
|
const struct decoder_plugin sidplay_decoder_plugin = {
|
|
|
|
"sidplay",
|
2009-08-30 19:48:56 +02:00
|
|
|
sidplay_init,
|
|
|
|
sidplay_finish,
|
2009-02-11 20:31:17 +01:00
|
|
|
NULL, /* stream_decode() */
|
|
|
|
sidplay_file_decode,
|
2012-02-11 19:12:02 +01:00
|
|
|
sidplay_scan_file,
|
2009-12-31 17:18:10 +01:00
|
|
|
NULL, /* stream_tag() */
|
2009-08-30 19:48:56 +02:00
|
|
|
sidplay_container_scan,
|
2009-02-11 20:31:17 +01:00
|
|
|
sidplay_suffixes,
|
|
|
|
NULL, /* mime_types */
|
|
|
|
};
|