1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "ModplugDecoderPlugin.hxx"
21 #include "ModCommon.hxx"
22 #include "../DecoderAPI.hxx"
23 #include "input/InputStream.hxx"
24 #include "tag/Handler.hxx"
25 #include "util/Domain.hxx"
26 #include "util/RuntimeError.hxx"
27 #include "util/StringView.hxx"
28 #include "Log.hxx"
29 
30 #ifdef _WIN32
31 /* assume ModPlug is built as static library on Windows; without
32    this, linking to the static library would fail */
33 #define MODPLUG_STATIC
34 #endif
35 
36 #include <libmodplug/modplug.h>
37 
38 #include <cassert>
39 
40 static constexpr Domain modplug_domain("modplug");
41 
42 static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
43 
44 static int modplug_loop_count;
45 static unsigned char modplug_resampling_mode;
46 
47 static bool
modplug_decoder_init(const ConfigBlock & block)48 modplug_decoder_init(const ConfigBlock &block)
49 {
50 	const char* modplug_resampling_mode_value = block.GetBlockValue("resampling_mode", "fir");
51 	if (strcmp(modplug_resampling_mode_value, "nearest") == 0) {
52 		modplug_resampling_mode = MODPLUG_RESAMPLE_NEAREST;
53 	} else if (strcmp(modplug_resampling_mode_value, "linear") == 0) {
54 		modplug_resampling_mode = MODPLUG_RESAMPLE_LINEAR;
55 	} else if (strcmp(modplug_resampling_mode_value, "spline") == 0) {
56 		modplug_resampling_mode = MODPLUG_RESAMPLE_SPLINE;
57 	} else if (strcmp(modplug_resampling_mode_value, "fir") == 0) {
58 		modplug_resampling_mode = MODPLUG_RESAMPLE_FIR;
59 	} else {
60 		throw FormatRuntimeError("Invalid resampling mode in line %d: %s",
61 				block.line, modplug_resampling_mode_value);
62 	}
63 
64 	modplug_loop_count = block.GetBlockValue("loop_count", 0);
65 	if (modplug_loop_count < -1)
66 		throw FormatRuntimeError("Invalid loop count in line %d: %i",
67 					 block.line, modplug_loop_count);
68 
69 	return true;
70 }
71 
72 static ModPlugFile *
LoadModPlugFile(DecoderClient * client,InputStream & is)73 LoadModPlugFile(DecoderClient *client, InputStream &is)
74 {
75 	const auto buffer = mod_loadfile(&modplug_domain, client, is);
76 	if (buffer.IsNull()) {
77 		LogWarning(modplug_domain, "could not load stream");
78 		return nullptr;
79 	}
80 
81 	ModPlugFile *f = ModPlug_Load(buffer.data(), buffer.size());
82 	return f;
83 }
84 
85 static void
mod_decode(DecoderClient & client,InputStream & is)86 mod_decode(DecoderClient &client, InputStream &is)
87 {
88 	ModPlug_Settings settings;
89 	int ret;
90 	char audio_buffer[MODPLUG_FRAME_SIZE];
91 
92 	ModPlug_GetSettings(&settings);
93 	/* alter setting */
94 	settings.mResamplingMode = modplug_resampling_mode;
95 	settings.mChannels = 2;
96 	settings.mBits = 16;
97 	settings.mFrequency = 44100;
98 	settings.mLoopCount = modplug_loop_count;
99 	/* insert more setting changes here */
100 	ModPlug_SetSettings(&settings);
101 
102 	ModPlugFile *f = LoadModPlugFile(&client, is);
103 	if (f == nullptr) {
104 		LogWarning(modplug_domain, "could not decode stream");
105 		return;
106 	}
107 
108 	static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
109 	assert(audio_format.IsValid());
110 
111 	client.Ready(audio_format, is.IsSeekable(),
112 		     SongTime::FromMS(ModPlug_GetLength(f)));
113 
114 	DecoderCommand cmd;
115 	do {
116 		ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
117 		if (ret <= 0)
118 			break;
119 
120 		cmd = client.SubmitData(nullptr,
121 					audio_buffer, ret,
122 					0);
123 
124 		if (cmd == DecoderCommand::SEEK) {
125 			ModPlug_Seek(f, client.GetSeekTime().ToMS());
126 			client.CommandFinished();
127 		}
128 
129 	} while (cmd != DecoderCommand::STOP);
130 
131 	ModPlug_Unload(f);
132 }
133 
134 static bool
modplug_scan_stream(InputStream & is,TagHandler & handler)135 modplug_scan_stream(InputStream &is, TagHandler &handler) noexcept
136 {
137 	ModPlugFile *f = LoadModPlugFile(nullptr, is);
138 	if (f == nullptr)
139 		return false;
140 
141 	handler.OnDuration(SongTime::FromMS(ModPlug_GetLength(f)));
142 
143 	const char *title = ModPlug_GetName(f);
144 	if (title != nullptr)
145 		handler.OnTag(TAG_TITLE, title);
146 
147 	ModPlug_Unload(f);
148 
149 	return true;
150 }
151 
152 static const char *const mod_suffixes[] = {
153 	"669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it",
154 	"med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm",
155 	"ult", "umx", "xm",
156 	nullptr
157 };
158 
159 constexpr DecoderPlugin modplug_decoder_plugin =
160 	DecoderPlugin("modplug", mod_decode, modplug_scan_stream)
161 	.WithInit(modplug_decoder_init)
162 	.WithSuffixes(mod_suffixes);
163