345 lines
12 KiB
C++
345 lines
12 KiB
C++
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(disable:4091)
|
|
#endif
|
|
|
|
#include "foobar2000/SDK/foobar2000.h"
|
|
#include "foobar2000/helpers/helpers.h"
|
|
|
|
#include "libopenmpt.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <locale>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
|
|
|
|
// Declaration of your component's version information
|
|
// Since foobar2000 v1.0 having at least one of these in your DLL is mandatory to let the troubleshooter tell different versions of your component apart.
|
|
// Note that it is possible to declare multiple components within one DLL, but it's strongly recommended to keep only one declaration per DLL.
|
|
// As for 1.1, the version numbers are used by the component update finder to find updates; for that to work, you must have ONLY ONE declaration per DLL. If there are multiple declarations, the component is assumed to be outdated and a version number of "0" is assumed, to overwrite the component with whatever is currently on the site assuming that it comes with proper version numbers.
|
|
DECLARE_COMPONENT_VERSION("OpenMPT component", OPENMPT_API_VERSION_STRING ,"libopenmpt based module file player");
|
|
|
|
|
|
|
|
// This will prevent users from renaming your component around (important for proper troubleshooter behaviors) or loading multiple instances of it.
|
|
VALIDATE_COMPONENT_FILENAME("foo_openmpt.dll");
|
|
|
|
|
|
// settings
|
|
|
|
|
|
// {FFD659CA-6AEA-479f-8E60-F03B297286FE}
|
|
static const GUID guid_openmpt_root =
|
|
{ 0xffd659ca, 0x6aea, 0x479f, { 0x8e, 0x60, 0xf0, 0x3b, 0x29, 0x72, 0x86, 0xfe } };
|
|
|
|
|
|
// {E698E101-FD93-4e6c-AF02-AEC7E8631D8E}
|
|
static const GUID guid_openmpt_samplerate =
|
|
{ 0xe698e101, 0xfd93, 0x4e6c, { 0xaf, 0x2, 0xae, 0xc7, 0xe8, 0x63, 0x1d, 0x8e } };
|
|
|
|
// {E4026686-02F9-4805-A3FD-2EECA655A92C}
|
|
static const GUID guid_openmpt_channels =
|
|
{ 0xe4026686, 0x2f9, 0x4805, { 0xa3, 0xfd, 0x2e, 0xec, 0xa6, 0x55, 0xa9, 0x2c } };
|
|
|
|
// {7C845F81-9BA3-4a9a-9C94-C7056DFD1B57}
|
|
static const GUID guid_openmpt_gain =
|
|
{ 0x7c845f81, 0x9ba3, 0x4a9a, { 0x9c, 0x94, 0xc7, 0x5, 0x6d, 0xfd, 0x1b, 0x57 } };
|
|
|
|
// {EDB0E1E5-BD2E-475c-B2FB-8448C92F6F29}
|
|
static const GUID guid_openmpt_stereo =
|
|
{ 0xedb0e1e5, 0xbd2e, 0x475c, { 0xb2, 0xfb, 0x84, 0x48, 0xc9, 0x2f, 0x6f, 0x29 } };
|
|
|
|
// {9115A820-67F5-4d0a-B0FB-D312F7FBBAFF}
|
|
static const GUID guid_openmpt_repeat =
|
|
{ 0x9115a820, 0x67f5, 0x4d0a, { 0xb0, 0xfb, 0xd3, 0x12, 0xf7, 0xfb, 0xba, 0xff } };
|
|
|
|
// {EAAD5E60-F75B-4071-B308-9956362ECB69}
|
|
static const GUID guid_openmpt_filter =
|
|
{ 0xeaad5e60, 0xf75b, 0x4071, { 0xb3, 0x8, 0x99, 0x56, 0x36, 0x2e, 0xcb, 0x69 } };
|
|
|
|
// {0CF7E156-44E3-4587-A727-864B045BEDDD}
|
|
static const GUID guid_openmpt_amiga =
|
|
{ 0x0cf7e156, 0x44e3, 0x4587,{ 0xa7, 027, 0x86, 0x4b, 0x04, 0x5b, 0xed, 0xdd } };
|
|
|
|
// {497BF503-D825-4A02-BE5C-02DB8911B1DC}
|
|
static const GUID guid_openmpt_ramping =
|
|
{ 0x497bf503, 0xd825, 0x4a02, { 0xbe, 0x5c, 0x2, 0xdb, 0x89, 0x11, 0xb1, 0xdc } };
|
|
|
|
|
|
static advconfig_branch_factory g_advconfigBranch("OpenMPT Component", guid_openmpt_root, advconfig_branch::guid_branch_decoding, 0);
|
|
|
|
static advconfig_integer_factory cfg_samplerate("Samplerate [6000..96000] (Hz)" , guid_openmpt_samplerate, guid_openmpt_root, 0, 48000, 6000, 96000);
|
|
static advconfig_integer_factory cfg_channels ("Channels [1=mono, 2=stereo, 4=quad]" , guid_openmpt_channels , guid_openmpt_root, 0, 2, 1, 4);
|
|
static advconfig_string_factory_MT cfg_gain ("Gain [-12...12] (dB)" , guid_openmpt_gain , guid_openmpt_root, 0, "0.0");
|
|
static advconfig_integer_factory cfg_stereo ("Stereo separation [0...200] (%)" , guid_openmpt_stereo , guid_openmpt_root, 0, 100, 0, 200);
|
|
static advconfig_string_factory_MT cfg_repeat ("Repeat [0=never, -1=forever, 1..] (#)" , guid_openmpt_repeat , guid_openmpt_root, 0, "0");
|
|
static advconfig_integer_factory cfg_filter ("Interpolation filter length [1=nearest, 2=linear, 4=cubic, 8=sinc]", guid_openmpt_filter , guid_openmpt_root, 0, 8, 1, 8);
|
|
static advconfig_checkbox_factory cfg_amiga ("Use Amiga Resampler for Amiga modules" , guid_openmpt_amiga, guid_openmpt_root, 0, false);
|
|
static advconfig_string_factory_MT cfg_ramping ("Volume ramping [-1=default, 0=off, 1..10] (ms)" , guid_openmpt_ramping , guid_openmpt_root, 0, "-1");
|
|
|
|
struct foo_openmpt_settings {
|
|
int samplerate;
|
|
int channels;
|
|
int mastergain_millibel;
|
|
int stereoseparation;
|
|
int repeatcount;
|
|
int interpolationfilterlength;
|
|
int ramping;
|
|
bool use_amiga_resampler;
|
|
foo_openmpt_settings() {
|
|
|
|
/*
|
|
samplerate = 48000;
|
|
channels = 2;
|
|
mastergain_millibel = 0;
|
|
stereoseparation = 100;
|
|
repeatcount = 0;
|
|
interpolationfilterlength = 8;
|
|
use_amiga_resampler = false;
|
|
ramping = -1;
|
|
*/
|
|
|
|
pfc::string8 tmp;
|
|
|
|
samplerate = static_cast<int>( cfg_samplerate.get() );
|
|
|
|
channels = static_cast<int>( cfg_channels.get() );
|
|
if ( channels == 3 ) {
|
|
channels = 2;
|
|
}
|
|
|
|
cfg_gain.get( tmp );
|
|
mastergain_millibel = static_cast<int>( pfc::string_to_float( tmp ) * 100.0 );
|
|
|
|
stereoseparation = static_cast<int>( cfg_stereo.get() );
|
|
|
|
cfg_repeat.get( tmp );
|
|
repeatcount = static_cast<int>( pfc::atoi64_ex( tmp, ~0 ) );
|
|
if ( repeatcount < -1 ) {
|
|
repeatcount = 0;
|
|
}
|
|
|
|
interpolationfilterlength = static_cast<int>( cfg_filter.get() );
|
|
|
|
use_amiga_resampler = cfg_amiga.get();
|
|
|
|
cfg_ramping.get( tmp );
|
|
ramping = static_cast<int>( pfc::atoi64_ex( tmp, ~0 ) );
|
|
if ( ramping < -1 ) {
|
|
ramping = -1;
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// Sample initquit implementation. See also: initquit class documentation in relevant header.
|
|
|
|
class myinitquit : public initquit {
|
|
public:
|
|
void on_init() {
|
|
// console::print("Sample component: on_init()");
|
|
}
|
|
void on_quit() {
|
|
// console::print("Sample component: on_quit()");
|
|
}
|
|
};
|
|
|
|
static initquit_factory_t<myinitquit> g_myinitquit_factory;
|
|
|
|
|
|
|
|
// No inheritance. Our methods get called over input framework templates. See input_singletrack_impl for descriptions of what each method does.
|
|
class input_openmpt {
|
|
public:
|
|
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
|
|
if ( p_reason == input_open_info_write ) {
|
|
throw exception_io_unsupported_format(); // our input does not support retagging.
|
|
}
|
|
m_file = p_filehint; // p_filehint may be null, hence next line
|
|
input_open_file_helper(m_file,p_path,p_reason,p_abort); // if m_file is null, opens file with appropriate privileges for our operation (read/write for writing tags, read-only otherwise).
|
|
if ( m_file->get_size( p_abort ) >= (std::numeric_limits<std::size_t>::max)() ) {
|
|
throw exception_io_unsupported_format();
|
|
}
|
|
std::vector<char> data( static_cast<std::size_t>( m_file->get_size( p_abort ) ) );
|
|
m_file->read( data.data(), data.size(), p_abort );
|
|
try {
|
|
std::map< std::string, std::string > ctls;
|
|
ctls["seek.sync_samples"] = "1";
|
|
mod = new openmpt::module( data, std::clog, ctls );
|
|
} catch ( std::exception & /*e*/ ) {
|
|
throw exception_io_data();
|
|
}
|
|
settings = foo_openmpt_settings();
|
|
mod->set_repeat_count( settings.repeatcount );
|
|
mod->set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, settings.mastergain_millibel );
|
|
mod->set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, settings.stereoseparation );
|
|
mod->set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, settings.interpolationfilterlength );
|
|
mod->set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, settings.ramping );
|
|
mod->ctl_set( "render.resampler.emulate_amiga", settings.use_amiga_resampler ? "1" : "0" );
|
|
}
|
|
void get_info(file_info & p_info,abort_callback & p_abort) {
|
|
p_info.set_length( mod->get_duration_seconds() );
|
|
p_info.info_set_int( "samplerate", settings.samplerate );
|
|
p_info.info_set_int( "channels", settings.channels );
|
|
p_info.info_set_int( "bitspersample", 32 );
|
|
std::vector<std::string> keys = mod->get_metadata_keys();
|
|
for ( std::vector<std::string>::iterator key = keys.begin(); key != keys.end(); ++key ) {
|
|
if ( *key == "message_raw" ) {
|
|
continue;
|
|
}
|
|
p_info.meta_set( (*key).c_str(), mod->get_metadata( *key ).c_str() );
|
|
}
|
|
}
|
|
t_filestats get_file_stats(abort_callback & p_abort) {
|
|
return m_file->get_stats(p_abort);
|
|
}
|
|
void decode_initialize(unsigned p_flags,abort_callback & p_abort) {
|
|
m_file->reopen(p_abort); // equivalent to seek to zero, except it also works on nonseekable streams
|
|
}
|
|
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
|
|
if ( settings.channels == 1 ) {
|
|
|
|
std::size_t count = mod->read( settings.samplerate, buffersize, left.data() );
|
|
if ( count == 0 ) {
|
|
return false;
|
|
}
|
|
for ( std::size_t frame = 0; frame < count; frame++ ) {
|
|
buffer[frame*1+0] = left[frame];
|
|
}
|
|
p_chunk.set_data_32( buffer.data(), count, settings.channels, settings.samplerate );
|
|
return true;
|
|
|
|
} else if ( settings.channels == 2 ) {
|
|
|
|
std::size_t count = mod->read( settings.samplerate, buffersize, left.data(), right.data() );
|
|
if ( count == 0 ) {
|
|
return false;
|
|
}
|
|
for ( std::size_t frame = 0; frame < count; frame++ ) {
|
|
buffer[frame*2+0] = left[frame];
|
|
buffer[frame*2+1] = right[frame];
|
|
}
|
|
p_chunk.set_data_32( buffer.data(), count, settings.channels, settings.samplerate );
|
|
return true;
|
|
|
|
} else if ( settings.channels == 4 ) {
|
|
|
|
std::size_t count = mod->read( settings.samplerate, buffersize, left.data(), right.data(), rear_left.data(), rear_right.data() );
|
|
if ( count == 0 ) {
|
|
return false;
|
|
}
|
|
for ( std::size_t frame = 0; frame < count; frame++ ) {
|
|
buffer[frame*4+0] = left[frame];
|
|
buffer[frame*4+1] = right[frame];
|
|
buffer[frame*4+2] = rear_left[frame];
|
|
buffer[frame*4+3] = rear_right[frame];
|
|
}
|
|
p_chunk.set_data_32( buffer.data(), count, settings.channels, settings.samplerate );
|
|
return true;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
void decode_seek(double p_seconds,abort_callback & p_abort) {
|
|
mod->set_position_seconds( p_seconds );
|
|
}
|
|
bool decode_can_seek() {
|
|
return true;
|
|
}
|
|
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { // deals with dynamic information such as VBR bitrates
|
|
return false;
|
|
}
|
|
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { // deals with dynamic information such as track changes in live streams
|
|
return false;
|
|
}
|
|
void decode_on_idle(abort_callback & p_abort) {
|
|
m_file->on_idle( p_abort );
|
|
}
|
|
void retag(const file_info & p_info,abort_callback & p_abort) {
|
|
throw exception_io_unsupported_format();
|
|
}
|
|
static bool g_is_our_content_type(const char * p_content_type) { // match against supported mime types here
|
|
return false;
|
|
}
|
|
static bool g_is_our_path(const char * p_path,const char * p_extension) {
|
|
if ( !p_extension ) {
|
|
return false;
|
|
}
|
|
std::vector<std::string> extensions = openmpt::get_supported_extensions();
|
|
std::string ext = p_extension;
|
|
std::transform( ext.begin(), ext.end(), ext.begin(), tolower );
|
|
return std::find( extensions.begin(), extensions.end(), ext ) != extensions.end();
|
|
}
|
|
public:
|
|
service_ptr_t<file> m_file;
|
|
static const std::size_t buffersize = 1024;
|
|
foo_openmpt_settings settings;
|
|
openmpt::module * mod;
|
|
std::vector<float> left;
|
|
std::vector<float> right;
|
|
std::vector<float> rear_left;
|
|
std::vector<float> rear_right;
|
|
std::vector<float> buffer;
|
|
input_openmpt() : mod(0), left(buffersize), right(buffersize), rear_left(buffersize), rear_right(buffersize), buffer(4*buffersize) {}
|
|
~input_openmpt() { delete mod; mod = 0; }
|
|
};
|
|
|
|
static input_singletrack_factory_t<input_openmpt> g_input_openmpt_factory;
|
|
|
|
|
|
|
|
// copied table from soundlib/Tables.cpp
|
|
// the foobar2000 interface is stupid demanding to declare those statically
|
|
|
|
DECLARE_FILE_TYPE("OpenMPT compatible module files",
|
|
"*.mod" ";"
|
|
"*.s3m" ";"
|
|
"*.xm" ";"
|
|
"*.it" ";"
|
|
"*.mptm" ";"
|
|
"*.stm" ";"
|
|
"*.nst" ";"
|
|
"*.m15" ";"
|
|
"*.stk" ";"
|
|
"*.st26" ";"
|
|
"*.pt36" ";"
|
|
"*.ice" ";"
|
|
"*.wow" ";"
|
|
"*.ult" ";"
|
|
"*.669" ";"
|
|
"*.mtm" ";"
|
|
"*.med" ";"
|
|
"*.far" ";"
|
|
"*.mdl" ";"
|
|
"*.ams" ";"
|
|
"*.ams" ";"
|
|
"*.dsm" ";"
|
|
"*.dtm" ";"
|
|
"*.amf" ";"
|
|
"*.amf" ";"
|
|
"*.okt" ";"
|
|
"*.dmf" ";"
|
|
"*.ptm" ";"
|
|
"*.psm" ";"
|
|
"*.mt2" ";"
|
|
"*.dbm" ";"
|
|
"*.digi" ";"
|
|
"*.imf" ";"
|
|
"*.j2b" ";"
|
|
"*.plm" ";"
|
|
"*.stp" ";"
|
|
"*.sfx" ";"
|
|
"*.sfx2" ";"
|
|
"*.mms" ";"
|
|
"*.gdm" ";"
|
|
"*.umx" ";"
|
|
"*.mo3" ";"
|
|
"*.xpk" ";"
|
|
"*.ppm" ";"
|
|
"*.mmcmp" );
|