MikmodDecoderPlugin.cxx 4.84 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "lib/fmt/PathFormatter.hxx"
24
#include "tag/Handler.hxx"
25
#include "fs/Path.hxx"
26
#include "util/Domain.hxx"
27
#include "util/RuntimeError.hxx"
28
#include "util/StringView.hxx"
29
#include "Log.hxx"
30
#include "Version.h"
31 32

#include <mikmod.h>
33

34
#include <cassert>
35

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

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

40
static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
41

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

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

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

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

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

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
	drv_alias,
76 77
	nullptr,  /* CmdLineHelp */
	nullptr,  /* CommandLine */
78
	mikmod_mpd_is_present,
79 80 81 82
	VC_SampleLoad,
	VC_SampleUnload,
	VC_SampleSpace,
	VC_SampleLength,
83 84
	mikmod_mpd_init,
	mikmod_mpd_exit,
85
	nullptr,
86 87 88
	VC_SetNumVoices,
	VC_PlayStart,
	VC_PlayStop,
89
	mikmod_mpd_update,
90
	nullptr,
91 92 93 94 95 96 97 98 99 100 101 102 103
	VC_VoiceSetVolume,
	VC_VoiceGetVolume,
	VC_VoiceSetFrequency,
	VC_VoiceGetFrequency,
	VC_VoiceSetPanning,
	VC_VoiceGetPanning,
	VC_VoicePlay,
	VC_VoiceStop,
	VC_VoiceStopped,
	VC_VoiceGetPosition,
	VC_VoiceRealVolume
};

104
static bool mikmod_loop;
105 106
static unsigned mikmod_sample_rate;

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

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

118 119
	md_device = 0;
	md_reverb = 0;
120

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

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

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

136
	return true;
137 138
}

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

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

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

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

158
	if (handle == nullptr) {
159
		FmtError(mikmod_domain, "failed to open mod: {}", path_fs);
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
		FmtDebug(mikmod_domain, "Failed to open file: {}", path_fs);
193
		return false;
194
	}
195

196
	Player_Free(handle);
197

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

204
	return true;
205 206
}

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

226 227 228 229 230
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);