MikmodDecoderPlugin.cxx 4.98 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4
 * http://www.musicpd.org
 *
5 6 7 8 9 10 11 12 13
 * 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 "MikmodDecoderPlugin.hxx"
22
#include "../DecoderAPI.hxx"
23
#include "tag/Handler.hxx"
24
#include "fs/Path.hxx"
25
#include "util/Domain.hxx"
26
#include "util/RuntimeError.hxx"
27
#include "Log.hxx"
28 29

#include <mikmod.h>
30

31
#include <assert.h>
32

33
static constexpr Domain mikmod_domain("mikmod");
34

35 36
/* this is largely copied from alsaplayer */

37
static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
38

39 40
static BOOL
mikmod_mpd_init(void)
Avuton Olrich's avatar
Avuton Olrich committed
41
{
42 43 44
	return VC_Init();
}

45 46
static void
mikmod_mpd_exit(void)
Avuton Olrich's avatar
Avuton Olrich committed
47
{
48 49 50
	VC_Exit();
}

51 52
static void
mikmod_mpd_update(void)
Avuton Olrich's avatar
Avuton Olrich committed
53
{
54 55
}

56 57
static BOOL
mikmod_mpd_is_present(void)
Avuton Olrich's avatar
Avuton Olrich committed
58
{
59
	return true;
60 61
}

62 63
static char drv_name[] = PACKAGE_NAME;
static char drv_version[] = VERSION;
64 65

#if (LIBMIKMOD_VERSION > 0x030106)
66
static char drv_alias[] = PACKAGE;
67 68
#endif

Avuton Olrich's avatar
Avuton Olrich committed
69
static MDRIVER drv_mpd = {
70
	nullptr,
71 72
	drv_name,
	drv_version,
73 74
	0,
	255,
75
#if (LIBMIKMOD_VERSION > 0x030106)
76
	drv_alias,
77
#if (LIBMIKMOD_VERSION >= 0x030200)
78
	nullptr,  /* CmdLineHelp */
79
#endif
80
	nullptr,  /* CommandLine */
81
#endif
82
	mikmod_mpd_is_present,
83 84 85 86
	VC_SampleLoad,
	VC_SampleUnload,
	VC_SampleSpace,
	VC_SampleLength,
87 88
	mikmod_mpd_init,
	mikmod_mpd_exit,
89
	nullptr,
90 91 92
	VC_SetNumVoices,
	VC_PlayStart,
	VC_PlayStop,
93
	mikmod_mpd_update,
94
	nullptr,
95 96 97 98 99 100 101 102 103 104 105 106 107
	VC_VoiceSetVolume,
	VC_VoiceGetVolume,
	VC_VoiceSetFrequency,
	VC_VoiceGetFrequency,
	VC_VoiceSetPanning,
	VC_VoiceGetPanning,
	VC_VoicePlay,
	VC_VoiceStop,
	VC_VoiceStopped,
	VC_VoiceGetPosition,
	VC_VoiceRealVolume
};

108
static bool mikmod_loop;
109 110
static unsigned mikmod_sample_rate;

111
static bool
112
mikmod_decoder_init(const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
113
{
114 115
	static char params[] = "";

116
	mikmod_loop = block.GetBlockValue("loop", false);
117
	mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u);
118
	if (!audio_valid_sample_rate(mikmod_sample_rate))
119 120
		throw FormatRuntimeError("Invalid sample rate in line %d: %u",
					 block.line, mikmod_sample_rate);
121

122 123
	md_device = 0;
	md_reverb = 0;
124

125 126
	MikMod_RegisterDriver(&drv_mpd);
	MikMod_RegisterAllLoaders();
127 128

	md_pansep = 64;
129
	md_mixfreq = mikmod_sample_rate;
130
	md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
Avuton Olrich's avatar
Avuton Olrich committed
131
		   DMODE_16BITS);
132

133
	if (MikMod_Init(params)) {
134 135 136
		FormatError(mikmod_domain,
			    "Could not init MikMod: %s",
			    MikMod_strerror(MikMod_errno));
137
		return false;
138 139
	}

140
	return true;
141 142
}

143
static void
144
mikmod_decoder_finish() noexcept
Avuton Olrich's avatar
Avuton Olrich committed
145
{
146 147 148
	MikMod_Exit();
}

149
static void
150
mikmod_decoder_file_decode(DecoderClient &client, Path path_fs)
Avuton Olrich's avatar
Avuton Olrich committed
151
{
152 153
	/* deconstify the path because libmikmod wants a non-const
	   string pointer */
154
	char *const path2 = const_cast<char *>(path_fs.c_str());
155

156
	MODULE *handle;
157
	int ret;
158
	SBYTE buffer[MIKMOD_FRAME_SIZE];
159

160
	handle = Player_Load(path2, 128, 0);
161

162
	if (handle == nullptr) {
163
		FormatError(mikmod_domain,
164
			    "failed to open mod: %s", path_fs.c_str());
165
		return;
166
	}
Avuton Olrich's avatar
Avuton Olrich committed
167

168
	handle->loop = mikmod_loop;
169

170 171
	const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
	assert(audio_format.IsValid());
Avuton Olrich's avatar
Avuton Olrich committed
172

173
	client.Ready(audio_format, false, SignedSongTime::Negative());
174

175
	Player_Start(handle);
176 177 178

	DecoderCommand cmd = DecoderCommand::NONE;
	while (cmd == DecoderCommand::NONE && Player_Active()) {
179
		ret = VC_WriteBytes(buffer, sizeof(buffer));
180
		cmd = client.SubmitData(nullptr, buffer, ret, 0);
181 182
	}

183
	Player_Stop();
184
	Player_Free(handle);
185 186
}

187
static bool
188
mikmod_decoder_scan_file(Path path_fs, TagHandler &handler) noexcept
Avuton Olrich's avatar
Avuton Olrich committed
189
{
190 191
	/* deconstify the path because libmikmod wants a non-const
	   string pointer */
192
	char *const path2 = const_cast<char *>(path_fs.c_str());
193

194
	MODULE *handle = Player_Load(path2, 128, 0);
195

196
	if (handle == nullptr) {
197
		FormatDebug(mikmod_domain,
198
			    "Failed to open file: %s", path_fs.c_str());
199
		return false;
200
	}
201

202
	Player_Free(handle);
203

204
	char *title = Player_LoadTitle(path2);
205
	if (title != nullptr) {
206
		handler.OnTag(TAG_TITLE, title);
207 208 209
#if (LIBMIKMOD_VERSION >= 0x030200)
		MikMod_free(title);
#else
210
		free(title);
211
#endif
212
	}
213

214
	return true;
215 216
}

217
static const char *const mikmod_decoder_suffixes[] = {
218
	"amf",
Avuton Olrich's avatar
Avuton Olrich committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232
	"dsm",
	"far",
	"gdm",
	"imf",
	"it",
	"med",
	"mod",
	"mtm",
	"s3m",
	"stm",
	"stx",
	"ult",
	"uni",
	"xm",
233
	nullptr
Avuton Olrich's avatar
Avuton Olrich committed
234 235
};

236
const struct DecoderPlugin mikmod_decoder_plugin = {
237 238 239 240 241 242 243 244 245 246
	"mikmod",
	mikmod_decoder_init,
	mikmod_decoder_finish,
	nullptr,
	mikmod_decoder_file_decode,
	mikmod_decoder_scan_file,
	nullptr,
	nullptr,
	mikmod_decoder_suffixes,
	nullptr,
247
};