MikmodDecoderPlugin.cxx 4.8 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 30

#include <mikmod.h>
31

32
#include <cassert>
33

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

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

38
static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
39

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

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

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

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

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

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

102
static bool mikmod_loop;
103 104
static unsigned mikmod_sample_rate;

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

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

116 117
	md_device = 0;
	md_reverb = 0;
118

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

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

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

134
	return true;
135 136
}

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

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

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

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

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

162
	handle->loop = mikmod_loop;
163

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

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

169
	Player_Start(handle);
170 171 172

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

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

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

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

190
	if (handle == nullptr) {
191
		FormatDebug(mikmod_domain,
192
			    "Failed to open file: %s", path_fs.c_str());
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);