MikmodDecoderPlugin.cxx 4.79 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 64
static char drv_name[] = PACKAGE_NAME;
static char drv_version[] = VERSION;
static char drv_alias[] = PACKAGE;
65

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

101
static bool mikmod_loop;
102 103
static unsigned mikmod_sample_rate;

104
static bool
105
mikmod_decoder_init(const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
106
{
107 108
	static char params[] = "";

109
	mikmod_loop = block.GetBlockValue("loop", false);
110
	mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u);
111
	if (!audio_valid_sample_rate(mikmod_sample_rate))
112 113
		throw FormatRuntimeError("Invalid sample rate in line %d: %u",
					 block.line, mikmod_sample_rate);
114

115 116
	md_device = 0;
	md_reverb = 0;
117

118 119
	MikMod_RegisterDriver(&drv_mpd);
	MikMod_RegisterAllLoaders();
120 121

	md_pansep = 64;
122
	md_mixfreq = mikmod_sample_rate;
123
	md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
Avuton Olrich's avatar
Avuton Olrich committed
124
		   DMODE_16BITS);
125

126
	if (MikMod_Init(params)) {
127 128 129
		FormatError(mikmod_domain,
			    "Could not init MikMod: %s",
			    MikMod_strerror(MikMod_errno));
130
		return false;
131 132
	}

133
	return true;
134 135
}

136
static void
137
mikmod_decoder_finish() noexcept
Avuton Olrich's avatar
Avuton Olrich committed
138
{
139 140 141
	MikMod_Exit();
}

142
static void
143
mikmod_decoder_file_decode(DecoderClient &client, Path path_fs)
Avuton Olrich's avatar
Avuton Olrich committed
144
{
145 146
	/* deconstify the path because libmikmod wants a non-const
	   string pointer */
147
	char *const path2 = const_cast<char *>(path_fs.c_str());
148

149
	MODULE *handle;
150
	int ret;
151
	SBYTE buffer[MIKMOD_FRAME_SIZE];
152

153
	handle = Player_Load(path2, 128, 0);
154

155
	if (handle == nullptr) {
156
		FormatError(mikmod_domain,
157
			    "failed to open mod: %s", path_fs.c_str());
158
		return;
159
	}
Avuton Olrich's avatar
Avuton Olrich committed
160

161
	handle->loop = mikmod_loop;
162

163 164
	const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
	assert(audio_format.IsValid());
Avuton Olrich's avatar
Avuton Olrich committed
165

166
	client.Ready(audio_format, false, SignedSongTime::Negative());
167

168
	Player_Start(handle);
169 170 171

	DecoderCommand cmd = DecoderCommand::NONE;
	while (cmd == DecoderCommand::NONE && Player_Active()) {
172
		ret = VC_WriteBytes(buffer, sizeof(buffer));
173
		cmd = client.SubmitData(nullptr, buffer, ret, 0);
174 175
	}

176
	Player_Stop();
177
	Player_Free(handle);
178 179
}

180
static bool
181
mikmod_decoder_scan_file(Path path_fs, TagHandler &handler) noexcept
Avuton Olrich's avatar
Avuton Olrich committed
182
{
183 184
	/* deconstify the path because libmikmod wants a non-const
	   string pointer */
185
	char *const path2 = const_cast<char *>(path_fs.c_str());
186

187
	MODULE *handle = Player_Load(path2, 128, 0);
188

189
	if (handle == nullptr) {
190
		FormatDebug(mikmod_domain,
191
			    "Failed to open file: %s", path_fs.c_str());
192
		return false;
193
	}
194

195
	Player_Free(handle);
196

197
	char *title = Player_LoadTitle(path2);
198
	if (title != nullptr) {
199
		handler.OnTag(TAG_TITLE, title);
200
		MikMod_free(title);
201
	}
202

203
	return true;
204 205
}

206
static const char *const mikmod_decoder_suffixes[] = {
207
	"amf",
Avuton Olrich's avatar
Avuton Olrich committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221
	"dsm",
	"far",
	"gdm",
	"imf",
	"it",
	"med",
	"mod",
	"mtm",
	"s3m",
	"stm",
	"stx",
	"ult",
	"uni",
	"xm",
222
	nullptr
Avuton Olrich's avatar
Avuton Olrich committed
223 224
};

225
const struct DecoderPlugin mikmod_decoder_plugin = {
226 227 228 229 230 231 232 233 234 235
	"mikmod",
	mikmod_decoder_init,
	mikmod_decoder_finish,
	nullptr,
	mikmod_decoder_file_decode,
	mikmod_decoder_scan_file,
	nullptr,
	nullptr,
	mikmod_decoder_suffixes,
	nullptr,
236
};