Commit 541c31c8 authored by GrimReaperFloof's avatar GrimReaperFloof

Add openmpt decoder plugin

parent 4ee0a06e
......@@ -494,6 +494,32 @@ 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
* - **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".
mpcdec
------
......
......@@ -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')
......
......@@ -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',
......
......@@ -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
......
/*
* 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);
/*
* 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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment