/* * Copyright (C) 2003-2013 The Music Player Daemon Project * 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. * * 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. */ #include "config.h" #include "SidplayDecoderPlugin.hxx" #include "../DecoderAPI.hxx" #include "tag/TagHandler.hxx" #include "util/Domain.hxx" #include "system/ByteOrder.hxx" #include "Log.hxx" #include #include #include #include #include #include #include #define SUBTUNE_PREFIX "tune_" static constexpr Domain sidplay_domain("sidplay"); static GPatternSpec *path_with_subtune; static const char *songlength_file; static GKeyFile *songlength_database; static bool all_files_are_containers; static unsigned default_songlength; static bool filter_setting; static GKeyFile * sidplay_load_songlength_db(const char *path) { GError *error = nullptr; gchar *data; gsize size; if (!g_file_get_contents(path, &data, &size, &error)) { FormatError(sidplay_domain, "unable to read songlengths file %s: %s", path, error->message); g_error_free(error); return nullptr; } /* replace any ; comment characters with # */ for (gsize i = 0; i < size; i++) if (data[i] == ';') data[i] = '#'; GKeyFile *db = g_key_file_new(); bool success = g_key_file_load_from_data(db, data, size, G_KEY_FILE_NONE, &error); g_free(data); if (!success) { FormatError(sidplay_domain, "unable to parse songlengths file %s: %s", path, error->message); g_error_free(error); g_key_file_free(db); return nullptr; } g_key_file_set_list_separator(db, ' '); return db; } static bool sidplay_init(const config_param ¶m) { /* read the songlengths database file */ songlength_file = param.GetBlockValue("songlength_database"); if (songlength_file != nullptr) songlength_database = sidplay_load_songlength_db(songlength_file); default_songlength = param.GetBlockValue("default_songlength", 0u); all_files_are_containers = param.GetBlockValue("all_files_are_containers", true); path_with_subtune=g_pattern_spec_new( "*/" SUBTUNE_PREFIX "???.sid"); filter_setting = param.GetBlockValue("filter", true); return true; } static void sidplay_finish() { g_pattern_spec_free(path_with_subtune); if(songlength_database) g_key_file_free(songlength_database); } /** * 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, nullptr)) 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 */ static unsigned get_song_num(const char *path_fs) { if(g_pattern_match(path_with_subtune, strlen(path_fs), path_fs, nullptr)) { char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); if(!sub) return 1; sub+=strlen("/" SUBTUNE_PREFIX); int song_num=strtol(sub, nullptr, 10); if (errno == EINVAL) return 1; else return song_num; } else return 1; } /* get the song length in seconds */ static int get_song_length(const char *path_fs) { if (songlength_database == nullptr) return -1; gchar *sid_file=get_container_name(path_fs); SidTuneMod tune(sid_file); g_free(sid_file); if(!tune) { LogWarning(sidplay_domain, "failed to load file for calculating md5 sum"); return -1; } char md5sum[SIDTUNE_MD5_LENGTH+1]; tune.createMD5(md5sum); const unsigned song_num = get_song_num(path_fs); gsize num_items; gchar **values=g_key_file_get_string_list(songlength_database, "Database", md5sum, &num_items, nullptr); if(!values || song_num>num_items) { g_strfreev(values); return -1; } int minutes=strtol(values[song_num-1], nullptr, 10); if(errno==EINVAL) minutes=0; int seconds; char *ptr=strchr(values[song_num-1], ':'); if(ptr) { seconds=strtol(ptr+1, nullptr, 10); if(errno==EINVAL) seconds=0; } else seconds=0; g_strfreev(values); return (minutes*60)+seconds; } static void sidplay_file_decode(Decoder &decoder, const char *path_fs) { int channels; /* load the tune */ char *path_container=get_container_name(path_fs); SidTune tune(path_container, nullptr, true); g_free(path_container); if (!tune) { LogWarning(sidplay_domain, "failed to load file"); return; } int song_num=get_song_num(path_fs); tune.selectSong(song_num); int song_len=get_song_length(path_fs); if(song_len==-1) song_len=default_songlength; /* initialize the player */ sidplay2 player; int iret = player.load(&tune); if (iret != 0) { FormatWarning(sidplay_domain, "sidplay2.load() failed: %s", player.error()); return; } /* initialize the builder */ ReSIDBuilder builder("ReSID"); if (!builder) { LogWarning(sidplay_domain, "failed to initialize ReSIDBuilder"); return; } builder.create(player.info().maxsids); if (!builder) { LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); return; } builder.filter(filter_setting); if (!builder) { LogWarning(sidplay_domain, "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; config.precision = 16; config.sidDefault = SID2_MOS6581; config.sidEmulation = &builder; config.sidModel = SID2_MODEL_CORRECT; config.sidSamples = true; config.sampleFormat = IsLittleEndian() ? SID2_LITTLE_SIGNED : SID2_BIG_SIGNED; if (tune.isStereo()) { config.playback = sid2_stereo; channels = 2; } else { config.playback = sid2_mono; channels = 1; } iret = player.config(config); if (iret != 0) { FormatWarning(sidplay_domain, "sidplay2.config() failed: %s", player.error()); return; } /* initialize the MPD decoder */ const AudioFormat audio_format(48000, SampleFormat::S16, channels); assert(audio_format.IsValid()); decoder_initialized(decoder, audio_format, true, (float)song_len); /* .. and play */ const unsigned timebase = player.timebase(); song_len *= timebase; DecoderCommand cmd; do { char buffer[4096]; size_t nbytes; nbytes = player.play(buffer, sizeof(buffer)); if (nbytes == 0) break; decoder_timestamp(decoder, (double)player.time() / timebase); cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0); if (cmd == DecoderCommand::SEEK) { unsigned data_time = player.time(); unsigned target_time = (unsigned) (decoder_seek_where(decoder) * timebase); /* can't rewind so return to zero and seek forward */ if(target_time 0 && player.time() >= (unsigned)song_len) break; } while (cmd != DecoderCommand::STOP); } static bool sidplay_scan_file(const char *path_fs, const struct tag_handler *handler, void *handler_ctx) { int song_num=get_song_num(path_fs); char *path_container=get_container_name(path_fs); SidTune tune(path_container, nullptr, true); g_free(path_container); if (!tune) return false; const SidTuneInfo &info = tune.getInfo(); /* title */ const char *title; if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) title=info.infoString[0]; else title=""; if(info.songs>1) { char tag_title[1024]; snprintf(tag_title, sizeof(tag_title), "%s (%d/%d)", title, song_num, info.songs); tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, tag_title); } else tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); /* artist */ if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, info.infoString[1]); /* track */ char track[16]; sprintf(track, "%d", song_num); tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); /* time */ int song_len=get_song_length(path_fs); if (song_len >= 0) tag_handler_invoke_duration(handler, handler_ctx, song_len); return true; } static char * sidplay_container_scan(const char *path_fs, const unsigned int tnum) { SidTune tune(path_fs, nullptr, true); if (!tune) return nullptr; const SidTuneInfo &info=tune.getInfo(); /* Don't treat sids containing a single tune as containers */ if(!all_files_are_containers && info.songs<2) return nullptr; /* 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 nullptr; } static const char *const sidplay_suffixes[] = { "sid", "mus", "str", "prg", "P00", nullptr }; extern const struct DecoderPlugin sidplay_decoder_plugin; const struct DecoderPlugin sidplay_decoder_plugin = { "sidplay", sidplay_init, sidplay_finish, nullptr, /* stream_decode() */ sidplay_file_decode, sidplay_scan_file, nullptr, /* stream_tag() */ sidplay_container_scan, sidplay_suffixes, nullptr, /* mime_types */ };