Add openmpt decoder plugin
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
#include "plugins/WildmidiDecoderPlugin.hxx"
|
||||
#include "plugins/MikmodDecoderPlugin.hxx"
|
||||
#include "plugins/ModplugDecoderPlugin.hxx"
|
||||
#include "plugins/OpenmptDecoderPlugin.hxx"
|
||||
#include "plugins/MpcdecDecoderPlugin.hxx"
|
||||
#include "plugins/FluidsynthDecoderPlugin.hxx"
|
||||
#include "plugins/SidplayDecoderPlugin.hxx"
|
||||
@@ -90,6 +91,9 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
|
||||
#ifdef ENABLE_WAVPACK
|
||||
&wavpack_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_OPENMPT
|
||||
&openmpt_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_MODPLUG
|
||||
&modplug_decoder_plugin,
|
||||
#endif
|
||||
|
208
src/decoder/plugins/OpenmptDecoderPlugin.cxx
Normal file
208
src/decoder/plugins/OpenmptDecoderPlugin.cxx
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2003-2021 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "OpenmptDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/Type.h"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <libopenmpt/libopenmpt.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
static constexpr Domain openmpt_domain("openmpt");
|
||||
|
||||
static constexpr size_t OPENMPT_FRAME_SIZE = 4096;
|
||||
static constexpr size_t OPENMPT_PREALLOC_BLOCK = 256 * 1024;
|
||||
static constexpr int32_t OPENMPT_SAMPLE_RATE = 48000;
|
||||
|
||||
static int openmpt_stereo_separation;
|
||||
static int openmpt_interpolation_filter;
|
||||
static bool openmpt_override_mptm_interp_filter;
|
||||
static int openmpt_volume_ramping;
|
||||
static bool openmpt_sync_samples;
|
||||
static bool openmpt_emulate_amiga;
|
||||
static std::string_view openmpt_emulate_amiga_type;
|
||||
|
||||
static bool
|
||||
openmpt_decoder_init(const ConfigBlock &block)
|
||||
{
|
||||
openmpt_stereo_separation = block.GetBlockValue("stereo_separation", 100);
|
||||
openmpt_interpolation_filter = block.GetBlockValue("interpolation_filter", 0);
|
||||
openmpt_override_mptm_interp_filter = block.GetBlockValue("override_mptm_interp_filter", false);
|
||||
openmpt_volume_ramping = block.GetBlockValue("volume_ramping", -1);
|
||||
openmpt_sync_samples = block.GetBlockValue("sync_samples", true);
|
||||
openmpt_emulate_amiga = block.GetBlockValue("emulate_amiga", true);
|
||||
openmpt_emulate_amiga_type = block.GetBlockValue("emulate_amiga_type", "auto");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static WritableBuffer<uint8_t>
|
||||
mod_loadfile(DecoderClient *client, InputStream &is)
|
||||
{
|
||||
//known/unknown size, preallocate array, lets read in chunks
|
||||
|
||||
const bool is_stream = !is.KnownSize();
|
||||
|
||||
WritableBuffer<uint8_t> buffer;
|
||||
if (is_stream)
|
||||
buffer.size = OPENMPT_PREALLOC_BLOCK;
|
||||
else {
|
||||
const auto size = is.GetSize();
|
||||
|
||||
if (size == 0) {
|
||||
LogWarning(openmpt_domain, "file is empty");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
buffer.size = size;
|
||||
}
|
||||
|
||||
buffer.data = new uint8_t[buffer.size];
|
||||
|
||||
uint8_t *const end = buffer.end();
|
||||
uint8_t *p = buffer.begin();
|
||||
|
||||
while (true) {
|
||||
size_t ret = decoder_read(client, is, p, end - p);
|
||||
if (ret == 0) {
|
||||
if (is.LockIsEOF())
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
/* I/O error - skip this song */
|
||||
delete[] buffer.data;
|
||||
buffer.data = nullptr;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
p += ret;
|
||||
if (p == end) {
|
||||
if (!is_stream)
|
||||
break;
|
||||
|
||||
LogWarning(openmpt_domain, "stream too large");
|
||||
delete[] buffer.data;
|
||||
buffer.data = nullptr;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.size = p - buffer.data;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void
|
||||
mod_decode(DecoderClient &client, InputStream &is)
|
||||
{
|
||||
int ret;
|
||||
char audio_buffer[OPENMPT_FRAME_SIZE];
|
||||
|
||||
const auto buffer = mod_loadfile(&client, is);
|
||||
if (buffer.IsNull()) {
|
||||
LogWarning(openmpt_domain, "could not load stream");
|
||||
return;
|
||||
}
|
||||
|
||||
openmpt::module mod(buffer.data, buffer.size);
|
||||
delete[] buffer.data;
|
||||
|
||||
/* alter settings */
|
||||
mod.set_render_param(mod.RENDER_STEREOSEPARATION_PERCENT, openmpt_stereo_separation);
|
||||
mod.set_render_param(mod.RENDER_INTERPOLATIONFILTER_LENGTH, openmpt_interpolation_filter);
|
||||
if (!openmpt_override_mptm_interp_filter && mod.get_metadata("type") == "mptm") {
|
||||
/* The MPTM format has a setting for which interpolation filter should be used.
|
||||
* If we want to play the module back the way the composer intended it,
|
||||
* we have to set the interpolation filter setting in libopenmpt back to 0: internal default. */
|
||||
mod.set_render_param(mod.RENDER_INTERPOLATIONFILTER_LENGTH, 0);
|
||||
}
|
||||
mod.set_render_param(mod.RENDER_VOLUMERAMPING_STRENGTH, openmpt_volume_ramping);
|
||||
mod.ctl_set_boolean("seek.sync_samples", openmpt_sync_samples);
|
||||
mod.ctl_set_boolean("render.resampler.emulate_amiga", openmpt_emulate_amiga);
|
||||
mod.ctl_set_text("render.resampler.emulate_amiga_type", openmpt_emulate_amiga_type);
|
||||
|
||||
static constexpr AudioFormat audio_format(OPENMPT_SAMPLE_RATE, SampleFormat::FLOAT, 2);
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
client.Ready(audio_format, is.IsSeekable(),
|
||||
SongTime::FromS(mod.get_duration_seconds()));
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
ret = mod.read_interleaved_stereo(OPENMPT_SAMPLE_RATE, OPENMPT_FRAME_SIZE / 2 / sizeof(float), (float*)audio_buffer);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
cmd = client.SubmitData(nullptr,
|
||||
audio_buffer, ret * 2 * sizeof(float),
|
||||
0);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
mod.set_position_seconds(client.GetSeekTime().ToS());
|
||||
client.CommandFinished();
|
||||
}
|
||||
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
static bool
|
||||
openmpt_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
{
|
||||
const auto buffer = mod_loadfile(nullptr, is);
|
||||
if (buffer.IsNull()) {
|
||||
LogWarning(openmpt_domain, "could not load stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
openmpt::module mod(buffer.data, buffer.size);
|
||||
delete[] buffer.data;
|
||||
|
||||
handler.OnDuration(SongTime::FromS(mod.get_duration_seconds()));
|
||||
|
||||
/* Tagging */
|
||||
handler.OnTag(TAG_TITLE, mod.get_metadata("title").c_str());
|
||||
handler.OnTag(TAG_ARTIST, mod.get_metadata("artist").c_str());
|
||||
handler.OnTag(TAG_COMMENT, mod.get_metadata("message").c_str());
|
||||
handler.OnTag(TAG_DATE, mod.get_metadata("date").c_str());
|
||||
handler.OnTag(TAG_PERFORMER, mod.get_metadata("tracker").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mod_suffixes[] = {
|
||||
"mptm", "mod", "s3m", "xm", "it", "669", "amf", "ams",
|
||||
"c67", "dbm", "digi", "dmf", "dsm", "dtm", "far", "imf",
|
||||
"ice", "j2b", "m15", "mdl", "med", "mms", "mt2", "mtm",
|
||||
"nst", "okt", "plm", "psm", "pt36", "ptm", "sfx", "sfx2",
|
||||
"st26", "stk", "stm", "stp", "ult", "wow", "gdm", "mo3",
|
||||
"oxm", "umx", "xpk", "ppm", "mmcmp",
|
||||
nullptr
|
||||
};
|
||||
|
||||
constexpr DecoderPlugin openmpt_decoder_plugin =
|
||||
DecoderPlugin("openmpt", mod_decode, openmpt_scan_stream)
|
||||
.WithInit(openmpt_decoder_init)
|
||||
.WithSuffixes(mod_suffixes);
|
25
src/decoder/plugins/OpenmptDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/OpenmptDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2003-2021 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_OPENMPT_HXX
|
||||
#define MPD_DECODER_OPENMPT_HXX
|
||||
|
||||
extern const struct DecoderPlugin openmpt_decoder_plugin;
|
||||
|
||||
#endif
|
@@ -108,6 +108,12 @@ if libmodplug_dep.found()
|
||||
]
|
||||
endif
|
||||
|
||||
libopenmpt_dep = dependency('libopenmpt', required: get_option('openmpt'))
|
||||
decoder_features.set('ENABLE_OPENMPT', libopenmpt_dep.found())
|
||||
if libopenmpt_dep.found()
|
||||
decoder_plugins_sources += 'OpenmptDecoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
libmpcdec_dep = c_compiler.find_library('mpcdec', required: get_option('mpcdec'))
|
||||
decoder_features.set('ENABLE_MPCDEC', libmpcdec_dep.found())
|
||||
if libmpcdec_dep.found()
|
||||
@@ -188,6 +194,7 @@ decoder_plugins = static_library(
|
||||
libmad_dep,
|
||||
libmikmod_dep,
|
||||
libmodplug_dep,
|
||||
libopenmpt_dep,
|
||||
libmpcdec_dep,
|
||||
libmpg123_dep,
|
||||
libopus_dep,
|
||||
|
Reference in New Issue
Block a user