MikmodDecoderPlugin.cxx 4.82 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 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 "util/StringView.hxx"
28
#include "Log.hxx"
29
#include "Version.h"
30 31

#include <mikmod.h>
32

33
#include <cassert>
34

35
static constexpr Domain mikmod_domain("mikmod");
36

37 38
/* this is largely copied from alsaplayer */

39
static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
40

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

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

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

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

64 65 66
static char drv_name[] = PACKAGE_NAME;
static char drv_version[] = VERSION;
static char drv_alias[] = PACKAGE;
67

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

103
static bool mikmod_loop;
104 105
static unsigned mikmod_sample_rate;

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

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

117 118
	md_device = 0;
	md_reverb = 0;
119

120 121
	MikMod_RegisterDriver(&drv_mpd);
	MikMod_RegisterAllLoaders();
122 123

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

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

135
	return true;
136 137
}

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

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

151
	MODULE *handle;
152
	int ret;
153
	SBYTE buffer[MIKMOD_FRAME_SIZE];
154

155
	handle = Player_Load(path2, 128, 0);
156

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

163
	handle->loop = mikmod_loop;
164

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

168
	client.Ready(audio_format, false, SignedSongTime::Negative());
169

170
	Player_Start(handle);
171 172 173

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

178
	Player_Stop();
179
	Player_Free(handle);
180 181
}

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

189
	MODULE *handle = Player_Load(path2, 128, 0);
190

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

197
	Player_Free(handle);
198

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

205
	return true;
206 207
}

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

227 228 229 230 231
constexpr DecoderPlugin mikmod_decoder_plugin =
	DecoderPlugin("mikmod",
		      mikmod_decoder_file_decode, mikmod_decoder_scan_file)
	.WithInit(mikmod_decoder_init, mikmod_decoder_finish)
	.WithSuffixes(mikmod_decoder_suffixes);