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