diff options
author | Max Kellermann <max@musicpd.org> | 2021-05-28 13:53:09 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2021-05-28 13:53:43 +0200 |
commit | 38bfef7d0b33def4a9c9dc2236dc0dc9e4e0b439 (patch) | |
tree | a863e81b25d1d06012fa819a78472a2b5873432f | |
parent | 4d32454697acd9a4c96529bf71e7101b979468bb (diff) | |
parent | 724754f16c92d7ca2031acd6d7d29c2a9b4a3bac (diff) |
Merge branch 'add-openmpt-decoder' of git://github.com/GrimReaperFloof/MPD
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | doc/plugins.rst | 28 | ||||
-rw-r--r-- | meson_options.txt | 1 | ||||
-rw-r--r-- | python/build/libs.py | 9 | ||||
-rw-r--r-- | src/decoder/DecoderList.cxx | 4 | ||||
-rw-r--r-- | src/decoder/plugins/OpenmptDecoderPlugin.cxx | 163 | ||||
-rw-r--r-- | src/decoder/plugins/OpenmptDecoderPlugin.hxx | 25 | ||||
-rw-r--r-- | src/decoder/plugins/meson.build | 11 |
8 files changed, 243 insertions, 0 deletions
@@ -7,6 +7,8 @@ ver 0.23 (not yet released) - proxy: require libmpdclient 2.11 or later - proxy: split search into chunks to avoid exceeding the output buffer - upnp: support libnpupnp instead of libupnp +* decoder + - openmt: new plugin * output - pipewire: new plugin - snapcast: new plugin diff --git a/doc/plugins.rst b/doc/plugins.rst index 021b5f43f..739e506c1 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -494,6 +494,34 @@ Module player based on MODPlug. * - **loop_count** - Number of times to loop the module if it uses backward loops. Default is 0 which prevents looping. -1 loops forever. +openmpt +------- + +Module player based on `libopenmpt <https://lib.openmpt.org>`_. + +.. list-table:: + :widths: 20 80 + :header-rows: 1 + + * - Setting + - Description + * - **repeat_count** + - Set how many times the module repeats. -1: repeat forever. 0: play once, repeat zero times (the default). n>0: play once and repeat n times after that. + * - **stereo_separation** + - Sets the stereo separation. The supported value range is [0,200]. Defaults to 100. + * - **interpolation_filter 0|1|2|4|8** + - Sets the interpolation filter. 0: internal default. 1: no interpolation (zero order hold). 2: linear interpolation. 4: cubic interpolation. 8: windowed sinc with 8 taps. Defaults to 0. + * - **override_mptm_interp_filter yes|no** + - If `interpolation_filter` has been changed, setting this to yes will force all MPTM modules to use that interpolation filter. If set to no, MPTM modules will play with their own interpolation filter regardless of the value of `interpolation_filter`. Defaults to no. + * - **volume_ramping** + - Sets the amount of volume ramping done by the libopenmpt mixer. The default value is -1, which indicates a recommended default value. The meaningful value range is [-1..10]. A value of 0 completely disables volume ramping. This might cause clicks in sound output. Higher values imply slower/softer volume ramps. + * - **sync_samples yes|no** + - Syncs sample playback when seeking. Defaults to yes. + * - **emulate_amiga yes|no** + - Enables the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. Defaults to yes. + * - **emulate_amiga_type** + - Configures the filter type to use for the Amiga resampler. Supported values are: "auto": Filter type is chosen by the library and might change. This is the default. "a500": Amiga A500 filter. "a1200": Amiga A1200 filter. "unfiltered": BLEP synthesis without model-specific filters. The LED filter is ignored by this setting. This filter mode is considered to be experimental and might change in the future. Defaults to "auto". Requires libopenmpt 0.5 or higher. + mpcdec ------ diff --git a/meson_options.txt b/meson_options.txt index aaf978f92..3f0b6f71f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -138,6 +138,7 @@ option('gme', type: 'feature', description: 'Game Music Emulator decoder plugin' option('mad', type: 'feature', description: 'MP3 decoder using libmad') option('mikmod', type: 'feature', description: 'MikMod decoder plugin') option('modplug', type: 'feature', description: 'Modplug decoder plugin') +option('openmpt', type: 'feature', description: 'OpenMPT decoder plugin') option('mpcdec', type: 'feature', description: 'Musepack decoder plugin') option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123') option('opus', type: 'feature', description: 'Opus decoder plugin') diff --git a/python/build/libs.py b/python/build/libs.py index d3e50ab09..419a52a66 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -123,6 +123,15 @@ libmodplug = AutotoolsProject( ], ) +libopenmpt = AutotoolsProject( + 'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.8+release.autotools.tar.gz', + '61de7cc0c011b10472ca16adcc123689', + 'lib/libopenmpt.a', + [ + '--disable-shared', '--enable-static' + ], +) + wildmidi = CmakeProject( 'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3', '498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5', diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx index e57077920..1c3cdc834 100644 --- a/src/decoder/DecoderList.cxx +++ b/src/decoder/DecoderList.cxx @@ -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 diff --git a/src/decoder/plugins/OpenmptDecoderPlugin.cxx b/src/decoder/plugins/OpenmptDecoderPlugin.cxx new file mode 100644 index 000000000..bedb8341d --- /dev/null +++ b/src/decoder/plugins/OpenmptDecoderPlugin.cxx @@ -0,0 +1,163 @@ +/* + * 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 "decoder/Features.h" +#include "ModCommon.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "tag/Handler.hxx" +#include "tag/Type.h" +#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 int32_t OPENMPT_SAMPLE_RATE = 48000; + +static int openmpt_repeat_count; +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; +#ifdef HAVE_LIBOPENMPT_VERSION_0_5 +static std::string_view openmpt_emulate_amiga_type; +#endif + +static bool +openmpt_decoder_init(const ConfigBlock &block) +{ + openmpt_repeat_count = block.GetBlockValue("repeat_count", 0); + 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); +#ifdef HAVE_LIBOPENMPT_VERSION_0_5 + openmpt_emulate_amiga_type = block.GetBlockValue("emulate_amiga_type", "auto"); +#endif + + return true; +} + +static void +mod_decode(DecoderClient &client, InputStream &is) +{ + int ret; + char audio_buffer[OPENMPT_FRAME_SIZE]; + + const auto buffer = mod_loadfile(&openmpt_domain, &client, is); + if (buffer.IsNull()) { + LogWarning(openmpt_domain, "could not load stream"); + return; + } + + openmpt::module mod(buffer.data(), buffer.size()); + + /* alter settings */ + mod.set_repeat_count(openmpt_repeat_count); + 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); +#ifdef HAVE_LIBOPENMPT_VERSION_0_5 + 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); +#else + mod.ctl_set("seek.sync_samples", std::to_string((unsigned)openmpt_sync_samples)); + mod.ctl_set("render.resampler.emulate_amiga", std::to_string((unsigned)openmpt_emulate_amiga)); +#endif + + 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(&openmpt_domain, nullptr, is); + if (buffer.IsNull()) { + LogWarning(openmpt_domain, "could not load stream"); + return false; + } + + openmpt::module mod(buffer.data(), buffer.size()); + + 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); diff --git a/src/decoder/plugins/OpenmptDecoderPlugin.hxx b/src/decoder/plugins/OpenmptDecoderPlugin.hxx new file mode 100644 index 000000000..29377693c --- /dev/null +++ b/src/decoder/plugins/OpenmptDecoderPlugin.hxx @@ -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 diff --git a/src/decoder/plugins/meson.build b/src/decoder/plugins/meson.build index 3a5233074..6dfb5de7a 100644 --- a/src/decoder/plugins/meson.build +++ b/src/decoder/plugins/meson.build @@ -108,6 +108,16 @@ if libmodplug_dep.found() ] endif +libopenmpt_dep = dependency('libopenmpt', required: get_option('openmpt')) +decoder_features.set('ENABLE_OPENMPT', libopenmpt_dep.found()) +decoder_features.set('HAVE_LIBOPENMPT_VERSION_0_5', libopenmpt_dep.version().version_compare('>= 0.5')) +if libopenmpt_dep.found() + decoder_plugins_sources += [ + 'OpenmptDecoderPlugin.cxx', + 'ModCommon.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 +198,7 @@ decoder_plugins = static_library( libmad_dep, libmikmod_dep, libmodplug_dep, + libopenmpt_dep, libmpcdec_dep, libmpg123_dep, libopus_dep, |