GmeDecoderPlugin.cxx 7.48 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * 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.
 */

20
#include "config.h"
21
#include "GmeDecoderPlugin.hxx"
22
#include "../DecoderAPI.hxx"
23
#include "CheckAudioFormat.hxx"
24
#include "tag/TagHandler.hxx"
25
#include "fs/Path.hxx"
26
#include "util/Alloc.hxx"
27
#include "util/FormatString.hxx"
Max Kellermann's avatar
Max Kellermann committed
28
#include "util/UriUtil.hxx"
29
#include "util/Error.hxx"
30 31
#include "util/Domain.hxx"
#include "Log.hxx"
32

33 34
#include <glib.h>
#include <assert.h>
35 36 37
#include <stdlib.h>
#include <string.h>

38 39
#include <gme/gme.h>

40 41
#define SUBTUNE_PREFIX "tune_"

42 43
static constexpr Domain gme_domain("gme");

44 45 46 47 48
static constexpr unsigned GME_SAMPLE_RATE = 44100;
static constexpr unsigned GME_CHANNELS = 2;
static constexpr unsigned GME_BUFFER_FRAMES = 2048;
static constexpr unsigned GME_BUFFER_SAMPLES =
	GME_BUFFER_FRAMES * GME_CHANNELS;
49

50 51 52 53 54
/**
 * returns the file path stripped of any /tune_xxx.* subtune
 * suffix
 */
static char *
55
get_container_name(Path path_fs)
56
{
57 58
	const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
	char *path_container = xstrdup(path_fs.c_str());
59 60 61 62 63

	char pat[64];
	snprintf(pat, sizeof(pat), "%s%s",
		 "*/" SUBTUNE_PREFIX "???.",
		 subtune_suffix);
64 65
	GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
	if (!g_pattern_match(path_with_subtune,
66
			     strlen(path_container), path_container, nullptr)) {
67 68 69 70 71
		g_pattern_spec_free(path_with_subtune);
		return path_container;
	}

	char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
72
	if (ptr != nullptr)
73 74 75 76 77 78 79 80 81 82 83
		*ptr='\0';

	g_pattern_spec_free(path_with_subtune);
	return path_container;
}

/**
 * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
 * is appended.
 */
static int
84
get_song_num(Path path_fs)
85
{
86
	const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
87 88 89 90 91

	char pat[64];
	snprintf(pat, sizeof(pat), "%s%s",
		 "*/" SUBTUNE_PREFIX "???.",
		 subtune_suffix);
92 93 94
	GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);

	if (g_pattern_match(path_with_subtune,
95 96
			    path_fs.length(), path_fs.data(), nullptr)) {
		char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX);
97
		g_pattern_spec_free(path_with_subtune);
98
		if (!sub)
99 100 101
			return 0;

		sub += strlen("/" SUBTUNE_PREFIX);
102
		int song_num = strtol(sub, nullptr, 10);
103 104 105 106 107 108 109 110 111

		return song_num - 1;
	} else {
		g_pattern_spec_free(path_with_subtune);
		return 0;
	}
}

static char *
112
gme_container_scan(Path path_fs, const unsigned int tnum)
113 114
{
	Music_Emu *emu;
115 116
	const char *gme_err = gme_open_file(path_fs.c_str(), &emu,
					    GME_SAMPLE_RATE);
117
	if (gme_err != nullptr) {
118
		LogWarning(gme_domain, gme_err);
119
		return nullptr;
120 121
	}

122
	const unsigned num_songs = gme_track_count(emu);
123
	gme_delete(emu);
124 125
	/* if it only contains a single tune, don't treat as container */
	if (num_songs < 2)
126
		return nullptr;
127

128
	const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
129
	if (tnum <= num_songs){
130 131
		return FormatNew(SUBTUNE_PREFIX "%03u.%s",
				 tnum, subtune_suffix);
132
	} else
133
		return nullptr;
134 135
}

136
static void
137
gme_file_decode(Decoder &decoder, Path path_fs)
138
{
139
	char *path_container = get_container_name(path_fs);
140

141 142 143
	Music_Emu *emu;
	const char *gme_err =
		gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
144
	free(path_container);
145
	if (gme_err != nullptr) {
146
		LogWarning(gme_domain, gme_err);
147 148
		return;
	}
149

150 151 152 153
	gme_info_t *ti;
	const int song_num = get_song_num(path_fs);
	gme_err = gme_track_info(emu, &ti, song_num);
	if (gme_err != nullptr) {
154
		LogWarning(gme_domain, gme_err);
155 156 157 158
		gme_delete(emu);
		return;
	}

159 160 161
	const SignedSongTime song_len = ti->length > 0
		? SignedSongTime::FromMS(ti->length)
		: SignedSongTime::Negative();
162 163 164

	/* initialize the MPD decoder */

165
	Error error;
166 167 168
	AudioFormat audio_format;
	if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
				       SampleFormat::S16, GME_CHANNELS,
169
				       error)) {
170
		LogError(error);
171 172 173 174 175
		gme_free_info(ti);
		gme_delete(emu);
		return;
	}

176
	decoder_initialized(decoder, audio_format, true, song_len);
177

178 179
	gme_err = gme_start_track(emu, song_num);
	if (gme_err != nullptr)
180
		LogWarning(gme_domain, gme_err);
181

182
	if (ti->length > 0)
183 184
		gme_set_fade(emu, ti->length);

185
	/* play */
186
	DecoderCommand cmd;
187
	do {
188
		short buf[GME_BUFFER_SAMPLES];
189
		gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
190
		if (gme_err != nullptr) {
191
			LogWarning(gme_domain, gme_err);
192 193 194
			return;
		}

195
		cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
196
		if (cmd == DecoderCommand::SEEK) {
197
			unsigned where = decoder_seek_time(decoder).ToMS();
198
			gme_err = gme_seek(emu, where);
199
			if (gme_err != nullptr)
200
				LogWarning(gme_domain, gme_err);
201 202 203
			decoder_command_finished(decoder);
		}

204
		if (gme_track_ended(emu))
205
			break;
206
	} while (cmd != DecoderCommand::STOP);
207 208 209 210 211

	gme_free_info(ti);
	gme_delete(emu);
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
static void
ScanGmeInfo(const gme_info_t &info, int song_num, int track_count,
	    const struct tag_handler *handler, void *handler_ctx)
{
	if (info.length > 0)
		tag_handler_invoke_duration(handler, handler_ctx,
					    SongTime::FromMS(info.length));

	if (info.song != nullptr) {
		if (track_count > 1) {
			/* start numbering subtunes from 1 */
			char tag_title[1024];
			snprintf(tag_title, sizeof(tag_title),
				 "%s (%d/%d)",
				 info.song, song_num + 1,
				 track_count);
			tag_handler_invoke_tag(handler, handler_ctx,
					       TAG_TITLE, tag_title);
		} else
			tag_handler_invoke_tag(handler, handler_ctx,
					       TAG_TITLE, info.song);
	}

	if (info.author != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_ARTIST, info.author);

	if (info.game != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_ALBUM, info.game);

	if (info.comment != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_COMMENT, info.comment);

	if (info.copyright != nullptr)
		tag_handler_invoke_tag(handler, handler_ctx,
				       TAG_DATE, info.copyright);
}

252
static bool
253
gme_scan_file(Path path_fs,
254
	      const struct tag_handler *handler, void *handler_ctx)
255
{
256
	char *path_container = get_container_name(path_fs);
257

258 259 260
	Music_Emu *emu;
	const char *gme_err =
		gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
261
	free(path_container);
262
	if (gme_err != nullptr) {
263
		LogWarning(gme_domain, gme_err);
264
		return false;
265
	}
266 267 268 269 270 271

	const int song_num = get_song_num(path_fs);

	gme_info_t *ti;
	gme_err = gme_track_info(emu, &ti, song_num);
	if (gme_err != nullptr) {
272
		LogWarning(gme_domain, gme_err);
273
		gme_delete(emu);
274
		return false;
275 276
	}

277
	assert(ti != nullptr);
278

279 280
	ScanGmeInfo(*ti, song_num, gme_track_count(emu),
		    handler, handler_ctx);
281 282 283

	gme_free_info(ti);
	gme_delete(emu);
284 285

	return true;
286 287 288 289 290
}

static const char *const gme_suffixes[] = {
	"ay", "gbs", "gym", "hes", "kss", "nsf",
	"nsfe", "sap", "spc", "vgm", "vgz",
291
	nullptr
292 293
};

294 295
extern const struct DecoderPlugin gme_decoder_plugin;
const struct DecoderPlugin gme_decoder_plugin = {
296 297 298 299 300 301 302 303 304 305
	"gme",
	nullptr,
	nullptr,
	nullptr,
	gme_file_decode,
	gme_scan_file,
	nullptr,
	gme_container_scan,
	gme_suffixes,
	nullptr,
306
};