SidplayDecoderPlugin.cxx 11.8 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 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
#include "tag/TagHandler.hxx"
24
#include "fs/Path.hxx"
25
#include "fs/AllocatedPath.hxx"
26
#include "util/Macros.hxx"
27
#include "util/FormatString.hxx"
28
#include "util/AllocatedString.hxx"
29
#include "util/Domain.hxx"
30
#include "util/Error.hxx"
31
#include "system/ByteOrder.hxx"
32
#include "system/FatalError.hxx"
33
#include "Log.hxx"
34

Max Kellermann's avatar
Max Kellermann committed
35
#include <string.h>
36

37 38 39 40 41 42 43 44 45 46
#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
47 48
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
49
#include <sidplay/utils/SidTuneMod.h>
50
#include <sidplay/utils/SidDatabase.h>
51
#endif
52

Mike Dawson's avatar
Mike Dawson committed
53 54
#define SUBTUNE_PREFIX "tune_"

55 56
static constexpr Domain sidplay_domain("sidplay");

57
static SidDatabase *songlength_database;
Mike Dawson's avatar
Mike Dawson committed
58 59

static bool all_files_are_containers;
60
static unsigned default_songlength;
Mike Dawson's avatar
Mike Dawson committed
61

62 63
static bool filter_setting;

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

	return db;
}

Mike Dawson's avatar
Mike Dawson committed
84
static bool
85
sidplay_init(const ConfigBlock &block)
Mike Dawson's avatar
Mike Dawson committed
86
{
87
	/* read the songlengths database file */
88
	Error error;
89
	const auto database_path = block.GetBlockPath("songlength_database", error);
90 91 92 93
	if (!database_path.IsNull())
		songlength_database = sidplay_load_songlength_db(database_path);
	else if (error.IsDefined())
		FatalError(error);
94

95
	default_songlength = block.GetBlockValue("default_songlength", 0u);
96

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

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

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

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

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

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

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

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

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

/**
134 135
 * 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
136
 */
137 138
static SidplayContainerPath
ParseContainerPath(Path path_fs)
Mike Dawson's avatar
Mike Dawson committed
139
{
140 141 142 143 144 145 146
	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
147 148
}

149 150 151 152 153
#ifdef HAVE_SIDPLAYFP

static SignedSongTime
get_song_length(SidTune &tune)
{
154 155
	assert(tune.getStatus());

156 157 158 159 160 161 162 163 164 165 166 167
	if (songlength_database == nullptr)
		return SignedSongTime::Negative();

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

	return SignedSongTime::FromS(length);
}

#else

168
static SignedSongTime
169
get_song_length(SidTuneMod &tune)
170
{
171
	assert(tune);
172

173
	if (songlength_database == nullptr)
174
		return SignedSongTime::Negative();
175

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

180
	return SignedSongTime::FromS(length);
181 182
}

183 184
#endif

185
static void
186
sidplay_file_decode(Decoder &decoder, Path path_fs)
187
{
188
	int channels;
189 190 191

	/* load the tune */

192
	const auto container = ParseContainerPath(path_fs);
193 194 195
#ifdef HAVE_SIDPLAYFP
	SidTune tune(container.path.c_str());
#else
196
	SidTuneMod tune(container.path.c_str());
197
#endif
198
	if (!tune.getStatus()) {
199 200 201 202 203
#ifdef HAVE_SIDPLAYFP
		const char *error = tune.statusString();
#else
		const char *error = tune.getInfo().statusString;
#endif
204
		FormatWarning(sidplay_domain, "failed to load file: %s",
205
			      error);
206 207 208
		return;
	}

209
	const int song_num = container.track;
Mike Dawson's avatar
Mike Dawson committed
210
	tune.selectSong(song_num);
211

212
	auto duration = get_song_length(tune);
213 214
	if (duration.IsNegative() && default_songlength > 0)
		duration = SongTime::FromS(default_songlength);
215

216 217
	/* initialize the player */

218 219 220
#ifdef HAVE_SIDPLAYFP
	sidplayfp player;
#else
221
	sidplay2 player;
222 223 224 225 226 227 228
#endif
#ifdef HAVE_SIDPLAYFP
	bool error = !player.load(&tune);
#else
	bool error = player.load(&tune) < 0;
#endif
	if (error) {
229 230
		FormatWarning(sidplay_domain,
			      "sidplay2.load() failed: %s", player.error());
231 232 233 234 235
		return;
	}

	/* initialize the builder */

236 237 238 239 240 241
#ifdef HAVE_SIDPLAYFP
	ReSIDfpBuilder builder("ReSID");
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "failed to initialize ReSIDfpBuilder: %s",
			      builder.error());
242 243 244
		return;
	}

245 246 247 248 249 250 251 252
	builder.create(player.info().maxsids());
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "ReSIDfpBuilder.create() failed: %s",
			      builder.error());
		return;
	}
#else
253 254 255
	ReSIDBuilder builder("ReSID");
	builder.create(player.info().maxsids);
	if (!builder) {
256 257
		FormatWarning(sidplay_domain, "ReSIDBuilder.create() failed: %s",
			      builder.error());
258 259
		return;
	}
260
#endif
261

262
	builder.filter(filter_setting);
263 264 265 266 267 268 269 270
#ifdef HAVE_SIDPLAYFP
	if (!builder.getStatus()) {
		FormatWarning(sidplay_domain,
			      "ReSIDfpBuilder.filter() failed: %s",
			      builder.error());
		return;
	}
#else
271
	if (!builder) {
272 273
		FormatWarning(sidplay_domain, "ReSIDBuilder.filter() failed: %s",
			      builder.error());
274 275
		return;
	}
276
#endif
277 278 279

	/* configure the player */

280
	auto config = player.config();
281

282
#ifndef HAVE_SIDPLAYFP
283 284 285
	config.clockDefault = SID2_CLOCK_PAL;
	config.clockForced = true;
	config.clockSpeed = SID2_CLOCK_CORRECT;
286
#endif
287
	config.frequency = 48000;
288
#ifndef HAVE_SIDPLAYFP
289
	config.optimisation = SID2_DEFAULT_OPTIMISATION;
290

291 292
	config.precision = 16;
	config.sidDefault = SID2_MOS6581;
293
#endif
294
	config.sidEmulation = &builder;
295 296 297 298
#ifdef HAVE_SIDPLAYFP
	config.samplingMethod = SidConfig::INTERPOLATE;
	config.fastSampling = false;
#else
299 300
	config.sidModel = SID2_MODEL_CORRECT;
	config.sidSamples = true;
301 302 303
	config.sampleFormat = IsLittleEndian()
		? SID2_LITTLE_SIGNED
		: SID2_BIG_SIGNED;
304 305 306 307 308 309 310 311 312 313 314 315
#endif

#ifdef HAVE_SIDPLAYFP
	const bool stereo = tune.getInfo()->sidChips() >= 2;
#else
	const bool stereo = tune.isStereo();
#endif

	if (stereo) {
#ifdef HAVE_SIDPLAYFP
		config.playback = SidConfig::STEREO;
#else
316
		config.playback = sid2_stereo;
317
#endif
318 319
		channels = 2;
	} else {
320 321 322
#ifdef HAVE_SIDPLAYFP
		config.playback = SidConfig::MONO;
#else
323
		config.playback = sid2_mono;
324
#endif
325 326
		channels = 1;
	}
327

328 329 330 331 332 333
#ifdef HAVE_SIDPLAYFP
	error = !player.config(config);
#else
	error = player.config(config) < 0;
#endif
	if (error) {
334 335
		FormatWarning(sidplay_domain,
			      "sidplay2.config() failed: %s", player.error());
336 337 338 339 340
		return;
	}

	/* initialize the MPD decoder */

341 342
	const AudioFormat audio_format(48000, SampleFormat::S16, channels);
	assert(audio_format.IsValid());
343

344
	decoder_initialized(decoder, audio_format, true, duration);
345 346 347

	/* .. and play */

348 349 350
#ifdef HAVE_SIDPLAYFP
	constexpr unsigned timebase = 1;
#else
351
	const unsigned timebase = player.timebase();
352
#endif
353 354 355
	const unsigned end = duration.IsNegative()
		? 0u
		: duration.ToScale<uint64_t>(timebase);
356

357
	DecoderCommand cmd;
358
	do {
359
		short buffer[4096];
360 361
		size_t nbytes;

362
		nbytes = player.play(buffer, ARRAY_SIZE(buffer));
363 364 365
		if (nbytes == 0)
			break;

366 367
		decoder_timestamp(decoder, (double)player.time() / timebase);

368
		cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0);
369

370
		if (cmd == DecoderCommand::SEEK) {
371
			unsigned data_time = player.time();
372 373
			unsigned target_time =
				decoder_seek_time(decoder).ToScale(timebase);
374 375 376 377 378 379 380 381 382

			/* 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) {
383
				nbytes=player.play(buffer, ARRAY_SIZE(buffer));
384 385
				if(nbytes==0)
					break;
386
				data_time = player.time();
387 388 389 390 391
			}

			decoder_command_finished(decoder);
		}

392
		if (end > 0 && player.time() >= end)
393 394
			break;

395
	} while (cmd != DecoderCommand::STOP);
396 397
}

398 399 400 401
gcc_pure
static const char *
GetInfoString(const SidTuneInfo &info, unsigned i)
{
402 403 404 405 406
#ifdef HAVE_SIDPLAYFP
	return info.numberOfInfoStrings() > i
		? info.infoString(i)
		: nullptr;
#else
407 408 409
	return info.numberOfInfoStrings > i
		? info.infoString[i]
		: nullptr;
410
#endif
411 412
}

413
static bool
414
sidplay_scan_file(Path path_fs,
415
		  const TagHandler &handler, void *handler_ctx)
416
{
417 418
	const auto container = ParseContainerPath(path_fs);
	const unsigned song_num = container.track;
Mike Dawson's avatar
Mike Dawson committed
419

420 421 422
#ifdef HAVE_SIDPLAYFP
	SidTune tune(container.path.c_str());
#else
423
	SidTuneMod tune(container.path.c_str());
424
#endif
425
	if (!tune.getStatus())
426
		return false;
427

428 429
	tune.selectSong(song_num);

430 431 432 433
#ifdef HAVE_SIDPLAYFP
	const SidTuneInfo &info = *tune.getInfo();
	const unsigned n_tracks = info.songs();
#else
434
	const SidTuneInfo &info = tune.getInfo();
435 436
	const unsigned n_tracks = info.songs;
#endif
437

Mike Dawson's avatar
Mike Dawson committed
438
	/* title */
439 440 441
	const char *title = GetInfoString(info, 0);
	if (title == nullptr)
		title = "";
Mike Dawson's avatar
Mike Dawson committed
442

443
	if (n_tracks > 1) {
444 445
		char tag_title[1024];
		snprintf(tag_title, sizeof(tag_title),
446 447
			 "%s (%d/%u)",
			 title, song_num, n_tracks);
448 449
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_TITLE, tag_title);
Mike Dawson's avatar
Mike Dawson committed
450
	} else
451
		tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
Mike Dawson's avatar
Mike Dawson committed
452 453

	/* artist */
454 455
	const char *artist = GetInfoString(info, 1);
	if (artist != nullptr)
456
		tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
457
				       artist);
458

459 460 461 462 463
	/* date */
	const char *date = GetInfoString(info, 2);
	if (date != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx, TAG_DATE,
				       date);
464

Mike Dawson's avatar
Mike Dawson committed
465
	/* track */
466 467
	char track[16];
	sprintf(track, "%d", song_num);
468
	tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
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 AllocatedString<>
480
sidplay_container_scan(Path path_fs, const unsigned int tnum)
Mike Dawson's avatar
Mike Dawson committed
481
{
482
	SidTune tune(path_fs.c_str(), nullptr, true);
483
	if (!tune.getStatus())
484
		return nullptr;
Mike Dawson's avatar
Mike Dawson committed
485

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

	/* Don't treat sids containing a single tune
		as containers */
496
	if(!all_files_are_containers && n_tracks < 2)
497
		return nullptr;
Mike Dawson's avatar
Mike Dawson committed
498 499 500

	/* Construct container/tune path names, eg.
		Delta.sid/tune_001.sid */
501
	if (tnum <= n_tracks) {
502
		return FormatString(SUBTUNE_PREFIX "%03u.sid", tnum);
Mike Dawson's avatar
Mike Dawson committed
503
	} else
504
		return nullptr;
Mike Dawson's avatar
Mike Dawson committed
505 506
}

507 508
static const char *const sidplay_suffixes[] = {
	"sid",
509 510 511 512
	"mus",
	"str",
	"prg",
	"P00",
513
	nullptr
514 515
};

516 517
extern const struct DecoderPlugin sidplay_decoder_plugin;
const struct DecoderPlugin sidplay_decoder_plugin = {
518
	"sidplay",
Mike Dawson's avatar
Mike Dawson committed
519 520
	sidplay_init,
	sidplay_finish,
521
	nullptr, /* stream_decode() */
522
	sidplay_file_decode,
523
	sidplay_scan_file,
524
	nullptr, /* stream_tag() */
Mike Dawson's avatar
Mike Dawson committed
525
	sidplay_container_scan,
526
	sidplay_suffixes,
527
	nullptr, /* mime_types */
528
};