winamp/Src/external_dependencies/openmpt-trunk/libopenmpt/in_openmpt.cpp
2024-09-24 14:54:57 +02:00

588 lines
17 KiB
C++

/*
* in_openmpt.cpp
* --------------
* Purpose: libopenmpt winamp input plugin implementation
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#ifndef NO_WINAMP
#if defined(_MFC_VER) || 1
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(WINVER) && !defined(_WIN32_WINDOWS)
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // _WIN32_WINNT_WINXP
#endif
#endif
#if !defined(MPT_BUILD_RETRO)
#if defined(_MSC_VER)
#define MPT_WITH_MFC
#endif
#else
#if defined(_WIN32_WINNT)
#if (_WIN32_WINNT >= 0x0501)
#if defined(_MSC_VER)
#define MPT_WITH_MFC
#endif
#endif
#endif
#endif
#if defined(MPT_WITH_MFC)
#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS // Avoid binary bloat from linking unused MFC controls
#endif // MPT_WITH_MFC
#ifndef NOMINMAX
#define NOMINMAX
#endif
#if defined(MPT_WITH_MFC)
#include <afxwin.h>
#include <afxcmn.h>
#endif // MPT_WITH_MFC
#include <windows.h>
#endif // _MFC_VER
#ifdef LIBOPENMPT_BUILD_DLL
#undef LIBOPENMPT_BUILD_DLL
#endif
#ifdef _MSC_VER
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifndef _SCL_SECURE_NO_WARNINGS
#define _SCL_SECURE_NO_WARNINGS
#endif
#endif // _MSC_VER
#include "libopenmpt.hpp"
#include "libopenmpt_plugin_settings.hpp"
#include "libopenmpt_plugin_gui.hpp"
#include "svn_version.h"
#if defined(OPENMPT_VERSION_REVISION)
static const char * in_openmpt_string = "in_openmpt " OPENMPT_API_VERSION_STRING "." OPENMPT_API_VERSION_STRINGIZE(OPENMPT_VERSION_REVISION);
#else
static const char * in_openmpt_string = "in_openmpt " OPENMPT_API_VERSION_STRING;
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#ifdef UNICODE
#define UNICODE_INPUT_PLUGIN
#endif
#ifndef _MSC_VER
#define _MSC_VER 1300
#endif
#include "winamp/Winamp/IN2.H"
#include "winamp/Winamp/wa_ipc.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <cstring>
#include <tchar.h>
#define BPS 16
#define WINAMP_DSP_HEADROOM_FACTOR 2
#define WINAMP_BUFFER_SIZE_FRAMES 576
#define WM_OPENMPT_SEEK (WM_USER+3)
#define SHORT_TITLE "in_openmpt"
static void apply_options();
static std::string StringEncode( const std::wstring &src, UINT codepage )
{
int required_size = WideCharToMultiByte( codepage, 0, src.c_str(), -1, NULL, 0, NULL, NULL );
if(required_size <= 0)
{
return std::string();
}
std::vector<CHAR> encoded_string( required_size );
WideCharToMultiByte( codepage, 0, src.c_str(), -1, &encoded_string[0], encoded_string.size(), NULL, NULL );
return &encoded_string[0];
}
static std::wstring StringDecode( const std::string & src, UINT codepage )
{
int required_size = MultiByteToWideChar( codepage, 0, src.c_str(), -1, NULL, 0 );
if(required_size <= 0)
{
return std::wstring();
}
std::vector<WCHAR> decoded_string( required_size );
MultiByteToWideChar( codepage, 0, src.c_str(), -1, &decoded_string[0], decoded_string.size() );
return &decoded_string[0];
}
#if defined(UNICODE)
static std::wstring StringToWINAPI( const std::wstring & src )
{
return src;
}
#else
static std::string StringToWINAPI( const std::wstring & src )
{
return StringEncode( src, CP_ACP );
}
#endif
template <typename Tstring, typename Tstring2, typename Tstring3>
static inline Tstring StringReplace( Tstring str, const Tstring2 & oldStr_, const Tstring3 & newStr_ ) {
std::size_t pos = 0;
const Tstring oldStr = oldStr_;
const Tstring newStr = newStr_;
while ( ( pos = str.find( oldStr, pos ) ) != Tstring::npos ) {
str.replace( pos, oldStr.length(), newStr );
pos += newStr.length();
}
return str;
}
struct self_winamp_t {
std::vector<char> filetypes_string;
libopenmpt::plugin::settings settings;
int samplerate;
int channels;
std::basic_string<TCHAR> cached_filename;
std::basic_string<TCHAR> cached_title;
int cached_length;
std::basic_string<TCHAR> cached_infotext;
std::int64_t decode_position_frames;
openmpt::module * mod;
HANDLE PlayThread;
DWORD PlayThreadID;
bool paused;
std::vector<std::int16_t> buffer;
std::vector<std::int16_t> interleaved_buffer;
self_winamp_t() : settings(TEXT(SHORT_TITLE), true) {
filetypes_string.clear();
settings.changed = apply_options;
settings.load();
std::vector<std::string> extensions = openmpt::get_supported_extensions();
for ( std::vector<std::string>::iterator ext = extensions.begin(); ext != extensions.end(); ++ext ) {
std::copy( (*ext).begin(), (*ext).end(), std::back_inserter( filetypes_string ) );
filetypes_string.push_back('\0');
std::copy( SHORT_TITLE, SHORT_TITLE + std::strlen(SHORT_TITLE), std::back_inserter( filetypes_string ) );
filetypes_string.push_back('\0');
}
filetypes_string.push_back('\0');
samplerate = settings.samplerate;
channels = settings.channels;
cached_filename = std::basic_string<TCHAR>();
cached_title = std::basic_string<TCHAR>();
cached_length = 0;
cached_infotext = std::basic_string<TCHAR>();
decode_position_frames = 0;
mod = 0;
PlayThread = 0;
PlayThreadID = 0;
paused = false;
buffer.resize( WINAMP_BUFFER_SIZE_FRAMES * channels );
interleaved_buffer.resize( WINAMP_BUFFER_SIZE_FRAMES * channels * WINAMP_DSP_HEADROOM_FACTOR );
}
~self_winamp_t() {
return;
}
};
static self_winamp_t * self = 0;
static void apply_options() {
if ( self->mod ) {
self->mod->set_repeat_count( self->settings.repeatcount );
self->mod->set_render_param( openmpt::module::RENDER_MASTERGAIN_MILLIBEL, self->settings.mastergain_millibel );
self->mod->set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, self->settings.stereoseparation );
self->mod->set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, self->settings.interpolationfilterlength );
self->mod->set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, self->settings.ramping );
self->mod->ctl_set_boolean( "render.resampler.emulate_amiga", self->settings.use_amiga_resampler ? true : false );
switch ( self->settings.amiga_filter_type ) {
case 0:
self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "auto" );
break;
case 1:
self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "unfiltered" );
break;
case 0xA500:
self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a500" );
break;
case 0xA1200:
self->mod->ctl_set_text( "render.resampler.emulate_amiga_type", "a1200" );
break;
}
}
self->settings.save();
}
extern In_Module inmod;
static DWORD WINAPI DecodeThread( LPVOID );
static std::basic_string<TCHAR> generate_infotext( const std::basic_string<TCHAR> & filename, const openmpt::module & mod ) {
std::basic_ostringstream<TCHAR> str;
str << TEXT("filename: ") << filename << std::endl;
str << TEXT("duration: ") << mod.get_duration_seconds() << TEXT("seconds") << std::endl;
std::vector<std::string> metadatakeys = mod.get_metadata_keys();
for ( std::vector<std::string>::iterator key = metadatakeys.begin(); key != metadatakeys.end(); ++key ) {
if ( *key == "message_raw" ) {
continue;
}
str << StringToWINAPI( StringDecode( *key, CP_UTF8 ) ) << TEXT(": ") << StringToWINAPI( StringDecode( mod.get_metadata(*key), CP_UTF8 ) ) << std::endl;
}
return str.str();
}
static void config( HWND hwndParent ) {
#if 1
libopenmpt::plugin::gui_edit_settings( &self->settings, hwndParent, TEXT(SHORT_TITLE) );
#else
static_cast<void>(hwndParent);
#endif
apply_options();
}
static void about( HWND hwndParent ) {
std::ostringstream about;
about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl;
about << " Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl;
about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl;
about << std::endl;
about << openmpt::string::get( "contact" ) << std::endl;
about << std::endl;
about << "Show full credits?" << std::endl;
if ( MessageBox( hwndParent, StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ).c_str(), TEXT(SHORT_TITLE), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) {
return;
}
std::ostringstream credits;
credits << openmpt::string::get( "credits" );
#if 1
libopenmpt::plugin::gui_show_file_info( hwndParent, TEXT(SHORT_TITLE), StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ) );
#else
MessageBox( hwndParent, StringToWINAPI( StringReplace(StringDecode(credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ).c_str(), TEXT(SHORT_TITLE), MB_OK );
#endif
}
static void init() {
if ( !self ) {
self = new self_winamp_t();
inmod.FileExtensions = &(self->filetypes_string[0]);
}
}
static void quit() {
if ( self ) {
inmod.FileExtensions = NULL;
delete self;
self = 0;
}
}
static int isourfile( const in_char * /* fn */ ) {
return 0;
}
static int play( const in_char * fn ) {
if ( !fn ) {
return -1;
}
try {
std::ifstream s( fn, std::ios::binary );
std::map< std::string, std::string > ctls;
ctls["seek.sync_samples"] = "1";
self->mod = new openmpt::module( s, std::clog, ctls );
self->cached_filename = fn;
self->cached_title = StringToWINAPI( StringDecode( self->mod->get_metadata( "title" ), CP_UTF8 ) );
self->cached_length = static_cast<int>( self->mod->get_duration_seconds() * 1000.0 );
self->cached_infotext = generate_infotext( self->cached_filename, *self->mod );
apply_options();
self->samplerate = self->settings.samplerate;
self->channels = self->settings.channels;
int maxlatency = inmod.outMod->Open( self->samplerate, self->channels, BPS, -1, -1 );
std::ostringstream str;
str << maxlatency;
inmod.SetInfo( self->mod->get_num_channels(), self->samplerate/1000, self->channels, 1 );
inmod.SAVSAInit( maxlatency, self->samplerate );
inmod.VSASetInfo( self->channels, self->samplerate );
inmod.outMod->SetVolume( -666 );
inmod.outMod->SetPan( 0 );
self->paused = false;
self->decode_position_frames = 0;
self->PlayThread = CreateThread( NULL, 0, DecodeThread, NULL, 0, &self->PlayThreadID );
return 0;
} catch ( ... ) {
if ( self->mod ) {
delete self->mod;
self->mod = 0;
}
return -1;
}
}
static void pause() {
self->paused = true;
inmod.outMod->Pause( 1 );
}
static void unpause() {
self->paused = false;
inmod.outMod->Pause( 0 );
}
static int ispaused() {
return self->paused ? 1 : 0;
}
static void stop() {
PostThreadMessage( self->PlayThreadID, WM_QUIT, 0, 0 );
WaitForSingleObject( self->PlayThread, INFINITE );
CloseHandle( self->PlayThread );
self->PlayThread = 0;
self->PlayThreadID = 0;
delete self->mod;
self->mod = 0;
inmod.outMod->Close();
inmod.SAVSADeInit();
}
static int getlength() {
return self->cached_length;
}
static int getoutputtime() {
//return (int)( self->decode_position_frames * 1000 / self->mod->get_render_param( openmpt::module::RENDER_SAMPLERATE_HZ ) /* + ( inmod.outMod->GetOutputTime() - inmod.outMod->GetWrittenTime() ) */ );
return inmod.outMod->GetOutputTime();
}
static void setoutputtime( int time_in_ms ) {
PostThreadMessage( self->PlayThreadID, WM_OPENMPT_SEEK, 0, time_in_ms );
}
static void setvolume( int volume ) {
inmod.outMod->SetVolume( volume );
}
static void setpan( int pan ) {
inmod.outMod->SetPan( pan );
}
static int infobox( const in_char * fn, HWND hWndParent ) {
if ( fn && fn[0] != '\0' && self->cached_filename != std::basic_string<TCHAR>(fn) ) {
try {
std::ifstream s( fn, std::ios::binary );
openmpt::module mod( s );
#if 1
libopenmpt::plugin::gui_show_file_info( hWndParent, TEXT(SHORT_TITLE), StringReplace( generate_infotext( fn, mod ), TEXT("\n"), TEXT("\r\n") ) );
#else
MessageBox( hWndParent, StringReplace( generate_infotext( fn, mod ), TEXT("\n"), TEXT("\r\n") ).c_str(), TEXT(SHORT_TITLE), MB_OK );
#endif
} catch ( ... ) {
}
} else {
#if 1
libopenmpt::plugin::gui_show_file_info( hWndParent, TEXT(SHORT_TITLE), StringReplace( self->cached_infotext, TEXT("\n"), TEXT("\r\n") ) );
#else
MessageBox( hWndParent, StringReplace( self->cached_infotext, TEXT("\n"), TEXT("\r\n") ).c_str(), TEXT(SHORT_TITLE), MB_OK );
#endif
}
return INFOBOX_UNCHANGED;
}
static void getfileinfo( const in_char * filename, in_char * title, int * length_in_ms ) {
if ( !filename || *filename == '\0' ) {
if ( length_in_ms ) {
*length_in_ms = self->cached_length;
}
if ( title ) {
std::basic_string<TCHAR> truncated_title = self->cached_title;
if ( truncated_title.length() >= GETFILEINFO_TITLE_LENGTH ) {
truncated_title.resize( GETFILEINFO_TITLE_LENGTH - 1 );
}
_tcscpy( title, truncated_title.c_str() );
}
} else {
try {
std::ifstream s( filename, std::ios::binary );
openmpt::module mod( s );
if ( length_in_ms ) {
*length_in_ms = static_cast<int>( mod.get_duration_seconds() * 1000.0 );
}
if ( title ) {
std::basic_string<TCHAR> truncated_title = StringToWINAPI( StringDecode( mod.get_metadata("title"), CP_UTF8 ) );
if ( truncated_title.length() >= GETFILEINFO_TITLE_LENGTH ) {
truncated_title.resize( GETFILEINFO_TITLE_LENGTH - 1 );
}
_tcscpy( title, truncated_title.c_str() );
}
} catch ( ... ) {
}
}
}
static void eq_set( int /* on */ , char /* data */ [10], int /* preamp */ ) {
return;
}
static DWORD WINAPI DecodeThread( LPVOID ) {
MSG msg;
PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );
bool eof = false;
while ( true ) {
bool quit = false;
while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
if ( msg.message == WM_QUIT ) {
quit = true;
} else if ( msg.message == WM_OPENMPT_SEEK ) {
double pos_seconds = self->mod->set_position_seconds( msg.lParam * 0.001 );
self->decode_position_frames = (std::int64_t)( pos_seconds * (double)self->samplerate);
eof = false;
inmod.outMod->Flush( (int)( pos_seconds * 1000.0 ) );
}
}
if ( quit ) {
break;
}
if ( eof ) {
inmod.outMod->CanWrite(); // update output plugin state
if ( !inmod.outMod->IsPlaying() ) {
PostMessage( inmod.hMainWindow, WM_WA_MPEG_EOF, 0, 0 );
return 0;
}
Sleep( 10 );
} else {
bool dsp_active = inmod.dsp_isactive() ? true : false;
if ( inmod.outMod->CanWrite() >= (int)( WINAMP_BUFFER_SIZE_FRAMES * self->channels * sizeof( signed short ) ) * ( dsp_active ? WINAMP_DSP_HEADROOM_FACTOR : 1 ) ) {
int frames = 0;
switch ( self->channels ) {
case 1:
frames = self->mod->read( self->samplerate, WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+0*WINAMP_BUFFER_SIZE_FRAMES );
for ( int frame = 0; frame < frames; frame++ ) {
self->interleaved_buffer[frame*1+0] = self->buffer[0*WINAMP_BUFFER_SIZE_FRAMES+frame];
}
break;
case 2:
frames = self->mod->read( self->samplerate, WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+0*WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+1*WINAMP_BUFFER_SIZE_FRAMES );
for ( int frame = 0; frame < frames; frame++ ) {
self->interleaved_buffer[frame*2+0] = self->buffer[0*WINAMP_BUFFER_SIZE_FRAMES+frame];
self->interleaved_buffer[frame*2+1] = self->buffer[1*WINAMP_BUFFER_SIZE_FRAMES+frame];
}
break;
case 4:
frames = self->mod->read( self->samplerate, WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+0*WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+1*WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+2*WINAMP_BUFFER_SIZE_FRAMES, (&(self->buffer[0]))+3*WINAMP_BUFFER_SIZE_FRAMES );
for ( int frame = 0; frame < frames; frame++ ) {
self->interleaved_buffer[frame*4+0] = self->buffer[0*WINAMP_BUFFER_SIZE_FRAMES+frame];
self->interleaved_buffer[frame*4+1] = self->buffer[1*WINAMP_BUFFER_SIZE_FRAMES+frame];
self->interleaved_buffer[frame*4+2] = self->buffer[2*WINAMP_BUFFER_SIZE_FRAMES+frame];
self->interleaved_buffer[frame*4+3] = self->buffer[3*WINAMP_BUFFER_SIZE_FRAMES+frame];
}
break;
}
if ( frames == 0 ) {
eof = true;
} else {
self->decode_position_frames += frames;
std::int64_t decode_pos_ms = (self->decode_position_frames * 1000 / self->samplerate );
inmod.SAAddPCMData( &( self->interleaved_buffer[0] ), self->channels, BPS, (int)decode_pos_ms );
inmod.VSAAddPCMData( &( self->interleaved_buffer[0] ), self->channels, BPS, (int)decode_pos_ms );
if ( dsp_active ) {
frames = inmod.dsp_dosamples( &( self->interleaved_buffer[0] ), frames, BPS, self->channels, self->samplerate );
}
int bytes = frames * self->channels * sizeof( signed short );
inmod.outMod->Write( (char*)&( self->interleaved_buffer[0] ), bytes );
}
} else {
Sleep( 10 );
}
}
}
return 0;
}
#if defined(__GNUC__)
extern In_Module inmod;
#endif
In_Module inmod = {
IN_VER,
const_cast< char * >( in_openmpt_string ), // SHORT_TITLE,
0, // hMainWindow
0, // hDllInstance
NULL, // filled later in Init() "mptm\0ModPlug Tracker Module (*.mptm)\0",
1, // is_seekable
1, // uses output
config,
about,
init,
quit,
getfileinfo,
infobox,
isourfile,
play,
pause,
unpause,
ispaused,
stop,
getlength,
getoutputtime,
setoutputtime,
setvolume,
setpan,
0,0,0,0,0,0,0,0,0, // vis
0,0, // dsp
eq_set,
NULL, // setinfo
0 // out_mod
};
extern "C" __declspec(dllexport) In_Module * winampGetInModule2();
extern "C" __declspec(dllexport) In_Module * winampGetInModule2() {
return &inmod;
}
#if defined(MPT_WITH_MFC)
#ifdef _MFC_VER
namespace libopenmpt {
namespace plugin {
void DllMainAttach() {
// nothing
}
void DllMainDetach() {
// nothing
}
} // namespace plugin
} // namespace libopenmpt
#else
// nothing
#endif
#endif // MPT_WITH_MFC
#endif // NO_WINAMP