SidplayDecoderPlugin.cxx 12.1 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/StringFormat.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
107
sidplay_finish() noexcept
Mike Dawson's avatar
Mike Dawson committed
108
{
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
static void
ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
408
		TagHandler &handler) noexcept
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 418
		const auto tag_title =
			StringFormat<1024>("%s (%u/%u)",
					   title, track, n_tracks);
419
		handler.OnTag(TAG_TITLE, tag_title);
Mike Dawson's avatar
Mike Dawson committed
420
	} else
421
		handler.OnTag(TAG_TITLE, title);
Mike Dawson's avatar
Mike Dawson committed
422 423

	/* artist */
424 425
	const char *artist = GetInfoString(info, 1);
	if (artist != nullptr)
426
		handler.OnTag(TAG_ARTIST, artist);
427

428 429 430
	/* date */
	const char *date = GetInfoString(info, 2);
	if (date != nullptr)
431
		handler.OnTag(TAG_DATE, date);
432

Mike Dawson's avatar
Mike Dawson committed
433
	/* track */
434
	handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
435 436 437
}

static bool
438
sidplay_scan_file(Path path_fs, TagHandler &handler) noexcept
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
{
	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

461
	ScanSidTuneInfo(info, song_num, n_tracks, handler);
Mike Dawson's avatar
Mike Dawson committed
462

463
	/* time */
464
	const auto duration = get_song_length(tune);
465
	if (!duration.IsNegative())
466
		handler.OnDuration(SongTime(duration));
467

468
	return true;
469 470
}

471
static std::forward_list<DetachedSong>
472
sidplay_container_scan(Path path_fs)
Mike Dawson's avatar
Mike Dawson committed
473
{
474
	std::forward_list<DetachedSong> list;
475

476 477 478 479 480
#ifdef HAVE_SIDPLAYFP
	SidTune tune(path_fs.c_str());
#else
	SidTuneMod tune(path_fs.c_str());
#endif
481
	if (!tune.getStatus())
482
		return list;
Mike Dawson's avatar
Mike Dawson committed
483

484 485 486 487 488 489 490
#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
491 492 493

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

497 498
	TagBuilder tag_builder;

499
	auto tail = list.before_begin();
500
	for (unsigned i = 1; i <= n_tracks; ++i) {
501 502
		tune.selectSong(i);

503 504
		AddTagHandler h(tag_builder);
		ScanSidTuneInfo(info, i, n_tracks, h);
505

506 507 508 509
		char track_name[32];
		/* Construct container/tune path names, eg.
		   Delta.sid/tune_001.sid */
		sprintf(track_name, SUBTUNE_PREFIX "%03u.sid", i);
510 511
		tail = list.emplace_after(tail, track_name,
					  tag_builder.Commit());
512
	}
Mike Dawson's avatar
Mike Dawson committed
513

514
	return list;
Mike Dawson's avatar
Mike Dawson committed
515 516
}

517 518
static const char *const sidplay_suffixes[] = {
	"sid",
519 520 521 522
	"mus",
	"str",
	"prg",
	"P00",
523
	nullptr
524 525
};

526 527
extern const struct DecoderPlugin sidplay_decoder_plugin;
const struct DecoderPlugin sidplay_decoder_plugin = {
528
	"sidplay",
Mike Dawson's avatar
Mike Dawson committed
529 530
	sidplay_init,
	sidplay_finish,
531
	nullptr, /* stream_decode() */
532
	sidplay_file_decode,
533
	sidplay_scan_file,
534
	nullptr, /* stream_tag() */
Mike Dawson's avatar
Mike Dawson committed
535
	sidplay_container_scan,
536
	sidplay_suffixes,
537
	nullptr, /* mime_types */
538
};