You need to sign in or sign up before continuing.
SidplayDecoderPlugin.cxx 12.5 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20
#include "config.h"
21
#include "SidplayDecoderPlugin.hxx"
22
#include "../DecoderAPI.hxx"
23 24
#include "tag/Handler.hxx"
#include "tag/Builder.hxx"
25
#include "DetachedSong.hxx"
26
#include "fs/Path.hxx"
27
#include "fs/AllocatedPath.hxx"
28
#include "util/Macros.hxx"
29
#include "util/FormatString.hxx"
30
#include "util/Domain.hxx"
31
#include "system/ByteOrder.hxx"
32
#include "Log.hxx"
33

34 35 36 37 38 39 40 41 42 43
#ifdef HAVE_SIDPLAYFP
#include <sidplayfp/sidplayfp.h>
#include <sidplayfp/SidInfo.h>
#include <sidplayfp/SidConfig.h>
#include <sidplayfp/SidTune.h>
#include <sidplayfp/SidTuneInfo.h>
#include <sidplayfp/builders/resid.h>
#include <sidplayfp/builders/residfp.h>
#include <sidplayfp/SidDatabase.h>
#else
44 45
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
46
#include <sidplay/utils/SidTuneMod.h>
47
#include <sidplay/utils/SidDatabase.h>
48
#endif
49

50 51 52
#include <string.h>
#include <stdio.h>

53 54 55 56
#ifdef HAVE_SIDPLAYFP
#define LIBSIDPLAYFP_VERSION GCC_MAKE_VERSION(LIBSIDPLAYFP_VERSION_MAJ, LIBSIDPLAYFP_VERSION_MIN, LIBSIDPLAYFP_VERSION_LEV)
#endif

Mike Dawson's avatar
Mike Dawson committed
57 58
#define SUBTUNE_PREFIX "tune_"

59 60
static constexpr Domain sidplay_domain("sidplay");

61
static SidDatabase *songlength_database;
Mike Dawson's avatar
Mike Dawson committed
62 63

static bool all_files_are_containers;
64
static unsigned default_songlength;
Mike Dawson's avatar
Mike Dawson committed
65

66 67
static bool filter_setting;

68
static SidDatabase *
69
sidplay_load_songlength_db(const Path path)
70
{
71
	SidDatabase *db = new SidDatabase();
72 73 74 75 76 77
#ifdef HAVE_SIDPLAYFP
	bool error = !db->open(path.c_str());
#else
	bool error = db->open(path.c_str()) < 0;
#endif
	if (error) {
78 79
		FormatError(sidplay_domain,
			    "unable to read songlengths file %s: %s",
80 81
			    path.c_str(), db->error());
		delete db;
82
		return nullptr;
83 84 85 86 87
	}

	return db;
}

Mike Dawson's avatar
Mike Dawson committed
88
static bool
89
sidplay_init(const ConfigBlock &block)
Mike Dawson's avatar
Mike Dawson committed
90
{
91
	/* read the songlengths database file */
92
	const auto database_path = block.GetPath("songlength_database");
93 94
	if (!database_path.IsNull())
		songlength_database = sidplay_load_songlength_db(database_path);
95

96
	default_songlength = block.GetPositiveValue("default_songlength", 0u);
97

98
	all_files_are_containers =
99
		block.GetBlockValue("all_files_are_containers", true);
Mike Dawson's avatar
Mike Dawson committed
100

101
	filter_setting = block.GetBlockValue("filter", true);
102

Mike Dawson's avatar
Mike Dawson committed
103 104 105
	return true;
}

106
static void
Mike Dawson's avatar
Mike Dawson committed
107 108
sidplay_finish()
{
109
	delete songlength_database;
Mike Dawson's avatar
Mike Dawson committed
110 111
}

112 113 114 115 116 117 118
struct SidplayContainerPath {
	AllocatedPath path;
	unsigned track;
};

gcc_pure
static unsigned
119
ParseSubtuneName(const char *base) noexcept
Mike Dawson's avatar
Mike Dawson committed
120
{
121 122
	if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
		return 0;
Mike Dawson's avatar
Mike Dawson committed
123

124
	base += sizeof(SUBTUNE_PREFIX) - 1;
Mike Dawson's avatar
Mike Dawson committed
125

126 127 128 129
	char *endptr;
	auto track = strtoul(base, &endptr, 10);
	if (endptr == base || *endptr != '.')
		return 0;
Mike Dawson's avatar
Mike Dawson committed
130

131
	return track;
Mike Dawson's avatar
Mike Dawson committed
132 133 134
}

/**
135 136
 * returns the file path stripped of any /tune_xxx.* subtune suffix
 * and the track number (or 1 if no "tune_xxx" suffix is present).
Mike Dawson's avatar
Mike Dawson committed
137
 */
138 139
static SidplayContainerPath
ParseContainerPath(Path path_fs)
Mike Dawson's avatar
Mike Dawson committed
140
{
141 142 143 144 145 146 147
	const Path base = path_fs.GetBase();
	unsigned track;
	if (base.IsNull() ||
	    (track = ParseSubtuneName(base.c_str())) < 1)
		return { AllocatedPath(path_fs), 1 };

	return { path_fs.GetDirectoryName(), track };
Mike Dawson's avatar
Mike Dawson committed
148 149
}

150 151 152 153 154
/**
 * This is a template, because libsidplay requires SidTuneMod while
 * libsidplayfp requires just a plain Sidtune.
 */
template<typename T>
155
static SignedSongTime
156
get_song_length(T &tune)
157
{
158 159
	assert(tune.getStatus());

160 161 162 163 164 165 166 167 168 169
	if (songlength_database == nullptr)
		return SignedSongTime::Negative();

	const auto length = songlength_database->length(tune);
	if (length < 0)
		return SignedSongTime::Negative();

	return SignedSongTime::FromS(length);
}

170
static void
171
sidplay_file_decode(DecoderClient &client, Path path_fs)
172
{
173
	int channels;
174 175 176

	/* load the tune */

177
	const auto container = ParseContainerPath(path_fs);
178 179 180
#ifdef HAVE_SIDPLAYFP
	SidTune tune(container.path.c_str());
#else
181
	SidTuneMod tune(container.path.c_str());
182
#endif
183
	if (!tune.getStatus()) {
184 185 186 187 188
#ifdef HAVE_SIDPLAYFP
		const char *error = tune.statusString();
#else
		const char *error = tune.getInfo().statusString;
#endif
189
		FormatWarning(sidplay_domain, "failed to load file: %s",
190
			      error);
191 192 193
		return;
	}

194
	const int song_num = container.track;
Mike Dawson's avatar
Mike Dawson committed
195
	tune.selectSong(song_num);
196

197
	auto duration = get_song_length(tune);
198 199
	if (duration.IsNegative() && default_songlength > 0)
		duration = SongTime::FromS(default_songlength);
200

201 202
	/* initialize the player */

203 204 205
#ifdef HAVE_SIDPLAYFP
	sidplayfp player;
#else
206
	sidplay2 player;
207 208 209 210 211 212 213
#endif
#ifdef HAVE_SIDPLAYFP
	bool error = !player.load(&tune);
#else
	bool error = player.load(&tune) < 0;
#endif
	if (error) {
214 215
		FormatWarning(sidplay_domain,
			      "sidplay2.load() failed: %s", player.error());
216 217 218 219 220
		return;
	}

	/* initialize the builder */

221 222 223 224 225 226
#ifdef HAVE_SIDPLAYFP
	ReSIDfpBuilder builder("ReSID");
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "failed to initialize ReSIDfpBuilder: %s",
			      builder.error());
227 228 229
		return;
	}

230 231 232 233 234 235 236 237
	builder.create(player.info().maxsids());
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "ReSIDfpBuilder.create() failed: %s",
			      builder.error());
		return;
	}
#else
238 239 240
	ReSIDBuilder builder("ReSID");
	builder.create(player.info().maxsids);
	if (!builder) {
241 242
		FormatWarning(sidplay_domain, "ReSIDBuilder.create() failed: %s",
			      builder.error());
243 244
		return;
	}
245
#endif
246

247
	builder.filter(filter_setting);
248 249 250 251 252 253 254 255
#ifdef HAVE_SIDPLAYFP
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "ReSIDfpBuilder.filter() failed: %s",
			      builder.error());
		return;
	}
#else
256
	if (!builder) {
257 258
		FormatWarning(sidplay_domain, "ReSIDBuilder.filter() failed: %s",
			      builder.error());
259 260
		return;
	}
261
#endif
262 263 264

	/* configure the player */

265
	auto config = player.config();
266

267
#ifndef HAVE_SIDPLAYFP
268 269 270
	config.clockDefault = SID2_CLOCK_PAL;
	config.clockForced = true;
	config.clockSpeed = SID2_CLOCK_CORRECT;
271
#endif
272
	config.frequency = 48000;
273
#ifndef HAVE_SIDPLAYFP
274
	config.optimisation = SID2_DEFAULT_OPTIMISATION;
275

276 277
	config.precision = 16;
	config.sidDefault = SID2_MOS6581;
278
#endif
279
	config.sidEmulation = &builder;
280 281 282 283
#ifdef HAVE_SIDPLAYFP
	config.samplingMethod = SidConfig::INTERPOLATE;
	config.fastSampling = false;
#else
284 285
	config.sidModel = SID2_MODEL_CORRECT;
	config.sidSamples = true;
286 287 288
	config.sampleFormat = IsLittleEndian()
		? SID2_LITTLE_SIGNED
		: SID2_BIG_SIGNED;
289 290 291
#endif

#ifdef HAVE_SIDPLAYFP
292
#if LIBSIDPLAYFP_VERSION >= GCC_MAKE_VERSION(1,8,0)
293
	const bool stereo = tune.getInfo()->sidChips() >= 2;
294 295 296
#else
	const bool stereo = tune.getInfo()->isStereo();
#endif
297 298 299 300 301 302 303 304
#else
	const bool stereo = tune.isStereo();
#endif

	if (stereo) {
#ifdef HAVE_SIDPLAYFP
		config.playback = SidConfig::STEREO;
#else
305
		config.playback = sid2_stereo;
306
#endif
307 308
		channels = 2;
	} else {
309 310 311
#ifdef HAVE_SIDPLAYFP
		config.playback = SidConfig::MONO;
#else
312
		config.playback = sid2_mono;
313
#endif
314 315
		channels = 1;
	}
316

317 318 319 320 321 322
#ifdef HAVE_SIDPLAYFP
	error = !player.config(config);
#else
	error = player.config(config) < 0;
#endif
	if (error) {
323 324
		FormatWarning(sidplay_domain,
			      "sidplay2.config() failed: %s", player.error());
325 326 327 328 329
		return;
	}

	/* initialize the MPD decoder */

330 331
	const AudioFormat audio_format(48000, SampleFormat::S16, channels);
	assert(audio_format.IsValid());
332

333
	client.Ready(audio_format, true, duration);
334 335 336

	/* .. and play */

337 338 339
#ifdef HAVE_SIDPLAYFP
	constexpr unsigned timebase = 1;
#else
340
	const unsigned timebase = player.timebase();
341
#endif
342 343 344
	const unsigned end = duration.IsNegative()
		? 0u
		: duration.ToScale<uint64_t>(timebase);
345

346
	DecoderCommand cmd;
347
	do {
348
		short buffer[4096];
349

350 351
		const auto result = player.play(buffer, ARRAY_SIZE(buffer));
		if (result <= 0)
352 353
			break;

354 355 356 357 358 359 360 361
#ifdef HAVE_SIDPLAYFP
		/* libsidplayfp returns the number of samples */
		const size_t nbytes = result * sizeof(buffer[0]);
#else
		/* libsidplay2 returns the number of bytes */
		const size_t nbytes = result;
#endif

362
		client.SubmitTimestamp((double)player.time() / timebase);
363

364
		cmd = client.SubmitData(nullptr, buffer, nbytes, 0);
365

366
		if (cmd == DecoderCommand::SEEK) {
367
			unsigned data_time = player.time();
368
			unsigned target_time =
369
				client.GetSeekTime().ToScale(timebase);
370 371 372 373 374 375 376 377

			/* 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 */
378 379
			while (data_time < target_time &&
			       player.play(buffer, ARRAY_SIZE(buffer)) > 0)
380
				data_time = player.time();
381

382
			client.CommandFinished();
383 384
		}

385
		if (end > 0 && player.time() >= end)
386 387
			break;

388
	} while (cmd != DecoderCommand::STOP);
389 390
}

391 392
gcc_pure
static const char *
393
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
394
{
395 396 397 398 399
#ifdef HAVE_SIDPLAYFP
	return info.numberOfInfoStrings() > i
		? info.infoString(i)
		: nullptr;
#else
400 401 402
	return info.numberOfInfoStrings > i
		? info.infoString[i]
		: nullptr;
403
#endif
404 405
}

406 407 408
static void
ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
		const TagHandler &handler, void *handler_ctx)
409
{
Mike Dawson's avatar
Mike Dawson committed
410
	/* title */
411 412 413
	const char *title = GetInfoString(info, 0);
	if (title == nullptr)
		title = "";
Mike Dawson's avatar
Mike Dawson committed
414

415
	if (n_tracks > 1) {
416 417
		char tag_title[1024];
		snprintf(tag_title, sizeof(tag_title),
418 419
			 "%s (%u/%u)",
			 title, track, n_tracks);
420 421
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_TITLE, tag_title);
Mike Dawson's avatar
Mike Dawson committed
422
	} else
423
		tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
Mike Dawson's avatar
Mike Dawson committed
424 425

	/* artist */
426 427
	const char *artist = GetInfoString(info, 1);
	if (artist != nullptr)
428
		tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
429
				       artist);
430

431 432 433 434 435
	/* date */
	const char *date = GetInfoString(info, 2);
	if (date != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx, TAG_DATE,
				       date);
436

Mike Dawson's avatar
Mike Dawson committed
437
	/* track */
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
	char track_buffer[16];
	sprintf(track_buffer, "%d", track);
	tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track_buffer);
}

static bool
sidplay_scan_file(Path path_fs,
		  const TagHandler &handler, void *handler_ctx)
{
	const auto container = ParseContainerPath(path_fs);
	const unsigned song_num = container.track;

#ifdef HAVE_SIDPLAYFP
	SidTune tune(container.path.c_str());
#else
	SidTuneMod tune(container.path.c_str());
#endif
	if (!tune.getStatus())
		return false;

	tune.selectSong(song_num);

#ifdef HAVE_SIDPLAYFP
	const SidTuneInfo &info = *tune.getInfo();
	const unsigned n_tracks = info.songs();
#else
	const SidTuneInfo &info = tune.getInfo();
	const unsigned n_tracks = info.songs;
#endif

	ScanSidTuneInfo(info, song_num, n_tracks, handler, handler_ctx);
Mike Dawson's avatar
Mike Dawson committed
469

470
	/* time */
471
	const auto duration = get_song_length(tune);
472 473
	if (!duration.IsNegative())
		tag_handler_invoke_duration(handler, handler_ctx,
474
					    SongTime(duration));
475

476
	return true;
477 478
}

479
static std::forward_list<DetachedSong>
480
sidplay_container_scan(Path path_fs)
Mike Dawson's avatar
Mike Dawson committed
481
{
482
	std::forward_list<DetachedSong> list;
483

484 485 486 487 488
#ifdef HAVE_SIDPLAYFP
	SidTune tune(path_fs.c_str());
#else
	SidTuneMod tune(path_fs.c_str());
#endif
489
	if (!tune.getStatus())
490
		return list;
Mike Dawson's avatar
Mike Dawson committed
491

492 493 494 495 496 497 498
#ifdef HAVE_SIDPLAYFP
	const SidTuneInfo &info = *tune.getInfo();
	const unsigned n_tracks = info.songs();
#else
	const SidTuneInfo &info = tune.getInfo();
	const unsigned n_tracks = info.songs;
#endif
Mike Dawson's avatar
Mike Dawson committed
499 500 501

	/* Don't treat sids containing a single tune
		as containers */
502
	if(!all_files_are_containers && n_tracks < 2)
503 504
		return list;

505 506
	TagBuilder tag_builder;

507
	auto tail = list.before_begin();
508
	for (unsigned i = 1; i <= n_tracks; ++i) {
509 510 511 512 513
		tune.selectSong(i);

		ScanSidTuneInfo(info, i, n_tracks,
				add_tag_handler, &tag_builder);

514 515 516 517
		char track_name[32];
		/* Construct container/tune path names, eg.
		   Delta.sid/tune_001.sid */
		sprintf(track_name, SUBTUNE_PREFIX "%03u.sid", i);
518 519
		tail = list.emplace_after(tail, track_name,
					  tag_builder.Commit());
520
	}
Mike Dawson's avatar
Mike Dawson committed
521

522
	return list;
Mike Dawson's avatar
Mike Dawson committed
523 524
}

525 526
static const char *const sidplay_suffixes[] = {
	"sid",
527 528 529 530
	"mus",
	"str",
	"prg",
	"P00",
531
	nullptr
532 533
};

534 535
extern const struct DecoderPlugin sidplay_decoder_plugin;
const struct DecoderPlugin sidplay_decoder_plugin = {
536
	"sidplay",
Mike Dawson's avatar
Mike Dawson committed
537 538
	sidplay_init,
	sidplay_finish,
539
	nullptr, /* stream_decode() */
540
	sidplay_file_decode,
541
	sidplay_scan_file,
542
	nullptr, /* stream_tag() */
Mike Dawson's avatar
Mike Dawson committed
543
	sidplay_container_scan,
544
	sidplay_suffixes,
545
	nullptr, /* mime_types */
546
};