1 /*
2  * libopenmpt_impl.cpp
3  * -------------------
4  * Purpose: libopenmpt private interface implementation
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 #include "common/stdafx.h"
11 
12 #include "libopenmpt_internal.h"
13 #include "libopenmpt.hpp"
14 
15 #include "libopenmpt_impl.hpp"
16 
17 #include <algorithm>
18 #include <iostream>
19 #include <istream>
20 #include <iterator>
21 #include <limits>
22 #include <ostream>
23 
24 #include <cmath>
25 #include <cstdlib>
26 #include <cstring>
27 
28 #include "mpt/audio/span.hpp"
29 #include "mpt/base/algorithm.hpp"
30 #include "mpt/base/saturate_cast.hpp"
31 #include "mpt/base/saturate_round.hpp"
32 #include "mpt/format/default_integer.hpp"
33 #include "mpt/format/default_floatingpoint.hpp"
34 #include "mpt/format/default_string.hpp"
35 #include "mpt/io_read/callbackstream.hpp"
36 #include "mpt/io_read/filecursor_callbackstream.hpp"
37 #include "mpt/io_read/filecursor_memory.hpp"
38 #include "mpt/io_read/filecursor_stdstream.hpp"
39 #include "mpt/mutex/mutex.hpp"
40 #include "mpt/parse/parse.hpp"
41 #include "mpt/string/types.hpp"
42 #include "mpt/string/utility.hpp"
43 #include "mpt/string_transcode/transcode.hpp"
44 
45 #include "common/version.h"
46 #include "common/misc_util.h"
47 #include "common/Dither.h"
48 #include "common/FileReader.h"
49 #include "common/Logging.h"
50 #include "soundlib/Sndfile.h"
51 #include "soundlib/mod_specifications.h"
52 #include "soundlib/AudioReadTarget.h"
53 
54 OPENMPT_NAMESPACE_BEGIN
55 
56 #if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS)
57 
58 #if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
59 #if defined(_WIN32_WINNT)
60 #if (_WIN32_WINNT < 0x0602)
61 MPT_WARNING("Warning: libopenmpt for WinRT is built with reduced functionality. Please #define _WIN32_WINNT 0x0602.")
62 #endif // _WIN32_WINNT
63 #endif // _WIN32_WINNT
64 #endif // MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
65 
66 #if defined(MPT_BUILD_MSVC) || defined(MPT_BUILD_VCPKG)
67 #if MPT_OS_WINDOWS_WINRT
68 #pragma comment(lib, "ole32.lib")
69 #else
70 #pragma comment(lib, "rpcrt4.lib")
71 #endif
72 #ifndef NO_DMO
73 #pragma comment(lib, "dmoguids.lib")
74 #pragma comment(lib, "strmiids.lib")
75 #endif // !NO_DMO
76 #endif // MPT_BUILD_MSVC
77 
78 #if MPT_PLATFORM_MULTITHREADED && MPT_MUTEX_NONE
79 MPT_WARNING("Warning: libopenmpt built in non thread-safe mode because mutexes are not supported by the C++ standard library available.")
80 #endif // MPT_MUTEX_NONE
81 
82 #if (defined(__MINGW32__) || defined(__MINGW64__)) && !defined(_GLIBCXX_HAS_GTHREADS) && !defined(MPT_WITH_MINGWSTDTHREADS)
83 MPT_WARNING("Warning: Building libopenmpt with MinGW-w64 without std::thread support is not recommended and is deprecated. Please use MinGW-w64 with posix threading model (as opposed to win32 threading model), or build with mingw-std-threads.")
84 #endif // MINGW
85 
86 #if MPT_CLANG_AT_LEAST(5,0,0) && MPT_CLANG_BEFORE(11,0,0) && defined(__powerpc__) && !defined(__powerpc64__)
87 MPT_WARNING("Warning: libopenmpt is known to trigger bad code generation with Clang 5..10 on powerpc (32bit) when using -O3. See <https://bugs.llvm.org/show_bug.cgi?id=46683>.")
88 #endif
89 
90 #endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS
91 
92 #if defined(MPT_ASSERT_HANDLER_NEEDED) && !defined(ENABLE_TESTS)
93 
AssertHandler(const mpt::source_location & loc,const char * expr,const char * msg)94 MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
95 {
96 	if(msg) {
97 		mpt::log::GlobalLogger().SendLogMessage(loc, LogError, "ASSERT",
98 			MPT_USTRING("ASSERTION FAILED: ") + mpt::ToUnicode(mpt::CharsetSource, msg) + MPT_USTRING(" (") + mpt::ToUnicode(mpt::CharsetSource, expr) + MPT_USTRING(")")
99 			);
100 	} else {
101 		mpt::log::GlobalLogger().SendLogMessage(loc, LogError, "ASSERT",
102 			MPT_USTRING("ASSERTION FAILED: ") + mpt::ToUnicode(mpt::CharsetSource, expr)
103 			);
104 	}
105 	#if defined(MPT_BUILD_FATAL_ASSERTS)
106 		std::abort();
107 	#endif // MPT_BUILD_FATAL_ASSERTS
108 }
109 
110 #endif // MPT_ASSERT_HANDLER_NEEDED && !ENABLE_TESTS
111 
112 OPENMPT_NAMESPACE_END
113 
114 // assume OPENMPT_NAMESPACE is OpenMPT
115 
116 namespace openmpt {
117 
118 namespace version {
119 
get_library_version()120 std::uint32_t get_library_version() {
121 	return OPENMPT_API_VERSION;
122 }
123 
get_core_version()124 std::uint32_t get_core_version() {
125 	return OpenMPT::Version::Current().GetRawVersion();
126 }
127 
get_library_version_string()128 static std::string get_library_version_string() {
129 	std::string str;
130 	const OpenMPT::SourceInfo sourceInfo = OpenMPT::SourceInfo::Current();
131 	str += mpt::format_value_default<std::string>(OPENMPT_API_VERSION_MAJOR);
132 	str += ".";
133 	str += mpt::format_value_default<std::string>(OPENMPT_API_VERSION_MINOR);
134 	str += ".";
135 	str += mpt::format_value_default<std::string>(OPENMPT_API_VERSION_PATCH);
136 	if ( std::string(OPENMPT_API_VERSION_PREREL).length() > 0 ) {
137 		str += OPENMPT_API_VERSION_PREREL;
138 	}
139 	std::vector<std::string> fields;
140 	if ( sourceInfo.Revision() ) {
141 		fields.push_back( "r" + mpt::format_value_default<std::string>( sourceInfo.Revision() ) );
142 	}
143 	if ( sourceInfo.IsDirty() ) {
144 		fields.push_back( "modified" );
145 	} else if ( sourceInfo.HasMixedRevisions() ) {
146 		fields.push_back( "mixed" );
147 	}
148 	if ( sourceInfo.IsPackage() ) {
149 		fields.push_back( "pkg" );
150 	}
151 	if ( !fields.empty() ) {
152 		str += "+";
153 		str += OpenMPT::mpt::String::Combine( fields, std::string(".") );
154 	}
155 	return str;
156 }
157 
get_library_features_string()158 static std::string get_library_features_string() {
159 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, mpt::trim(OpenMPT::Build::GetBuildFeaturesString()));
160 }
161 
get_core_version_string()162 static std::string get_core_version_string() {
163 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetVersionStringExtended());
164 }
165 
get_source_url_string()166 static std::string get_source_url_string() {
167 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::SourceInfo::Current().GetUrlWithRevision());
168 }
169 
get_source_date_string()170 static std::string get_source_date_string() {
171 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::SourceInfo::Current().Date());
172 }
173 
get_source_revision_string()174 static std::string get_source_revision_string() {
175 	const OpenMPT::SourceInfo sourceInfo = OpenMPT::SourceInfo::Current();
176 	return sourceInfo.Revision() ? mpt::format_value_default<std::string>(sourceInfo.Revision()) : std::string();
177 }
178 
get_build_string()179 static std::string get_build_string() {
180 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetBuildDateString());
181 }
182 
get_build_compiler_string()183 static std::string get_build_compiler_string() {
184 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetBuildCompilerString());
185 }
186 
get_credits_string()187 static std::string get_credits_string() {
188 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetFullCreditsString());
189 }
190 
get_contact_string()191 static std::string get_contact_string() {
192 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, MPT_USTRING("Forum: ") + OpenMPT::Build::GetURL(OpenMPT::Build::Url::Forum));
193 }
194 
get_license_string()195 static std::string get_license_string() {
196 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetLicenseString());
197 }
198 
get_url_string()199 static std::string get_url_string() {
200 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetURL(OpenMPT::Build::Url::Website));
201 }
202 
get_support_forum_url_string()203 static std::string get_support_forum_url_string() {
204 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetURL(OpenMPT::Build::Url::Forum));
205 }
206 
get_bugtracker_url_string()207 static std::string get_bugtracker_url_string() {
208 	return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::Build::GetURL(OpenMPT::Build::Url::Bugtracker));
209 }
210 
get_string(const std::string & key)211 std::string get_string( const std::string & key ) {
212 	if ( key == "" ) {
213 		return std::string();
214 	} else if ( key == "library_version" ) {
215 		return get_library_version_string();
216 	} else if ( key == "library_version_major" ) {
217 		return mpt::format_value_default<std::string>(OPENMPT_API_VERSION_MAJOR);
218 	} else if ( key == "library_version_minor" ) {
219 		return mpt::format_value_default<std::string>(OPENMPT_API_VERSION_MINOR);
220 	} else if ( key == "library_version_patch" ) {
221 		return mpt::format_value_default<std::string>(OPENMPT_API_VERSION_PATCH);
222 	} else if ( key == "library_version_prerel" ) {
223 		return mpt::format_value_default<std::string>(OPENMPT_API_VERSION_PREREL);
224 	} else if ( key == "library_version_is_release" ) {
225 		return ( std::string(OPENMPT_API_VERSION_PREREL).length() == 0 ) ? "1" : "0";
226 	} else if ( key == "library_features" ) {
227 		return get_library_features_string();
228 	} else if ( key == "core_version" ) {
229 		return get_core_version_string();
230 	} else if ( key == "source_url" ) {
231 		return get_source_url_string();
232 	} else if ( key == "source_date" ) {
233 		return get_source_date_string();
234 	} else if ( key == "source_revision" ) {
235 		return get_source_revision_string();
236 	} else if ( key == "source_is_modified" ) {
237 		return OpenMPT::SourceInfo::Current().IsDirty() ? "1" : "0";
238 	} else if ( key == "source_has_mixed_revision" ) {
239 		return OpenMPT::SourceInfo::Current().HasMixedRevisions() ? "1" : "0";
240 	} else if ( key == "source_is_package" ) {
241 		return OpenMPT::SourceInfo::Current().IsPackage() ? "1" : "0";
242 	} else if ( key == "build" ) {
243 		return get_build_string();
244 	} else if ( key == "build_compiler" ) {
245 		return get_build_compiler_string();
246 	} else if ( key == "credits" ) {
247 		return get_credits_string();
248 	} else if ( key == "contact" ) {
249 		return get_contact_string();
250 	} else if ( key == "license" ) {
251 		return get_license_string();
252 	} else if ( key == "url" ) {
253 		return get_url_string();
254 	} else if ( key == "support_forum_url" ) {
255 		return get_support_forum_url_string();
256 	} else if ( key == "bugtracker_url" ) {
257 		return get_bugtracker_url_string();
258 	} else {
259 		return std::string();
260 	}
261 }
262 
263 } // namespace version
264 
log_interface()265 log_interface::log_interface() {
266 	return;
267 }
~log_interface()268 log_interface::~log_interface() {
269 	return;
270 }
271 
std_ostream_log(std::ostream & dst)272 std_ostream_log::std_ostream_log( std::ostream & dst ) : destination(dst) {
273 	return;
274 }
~std_ostream_log()275 std_ostream_log::~std_ostream_log() {
276 	return;
277 }
log(const std::string & message) const278 void std_ostream_log::log( const std::string & message ) const {
279 	destination.flush();
280 	destination << message << std::endl;
281 	destination.flush();
282 }
283 
284 class log_forwarder : public OpenMPT::ILog {
285 private:
286 	log_interface & destination;
287 public:
log_forwarder(log_interface & dest)288 	log_forwarder( log_interface & dest ) : destination(dest) {
289 		return;
290 	}
291 private:
AddToLog(OpenMPT::LogLevel level,const mpt::ustring & text) const292 	void AddToLog( OpenMPT::LogLevel level, const mpt::ustring & text ) const override {
293 		destination.log( mpt::transcode<std::string>( mpt::common_encoding::utf8, LogLevelToString( level ) + MPT_USTRING(": ") + text ) );
294 	}
295 }; // class log_forwarder
296 
297 class loader_log : public OpenMPT::ILog {
298 private:
299 	mutable std::vector<std::pair<OpenMPT::LogLevel,std::string> > m_Messages;
300 public:
301 	std::vector<std::pair<OpenMPT::LogLevel,std::string> > GetMessages() const;
302 private:
303 	void AddToLog( OpenMPT::LogLevel level, const mpt::ustring & text ) const override;
304 }; // class loader_log
305 
GetMessages() const306 std::vector<std::pair<OpenMPT::LogLevel,std::string> > loader_log::GetMessages() const {
307 	return m_Messages;
308 }
AddToLog(OpenMPT::LogLevel level,const mpt::ustring & text) const309 void loader_log::AddToLog( OpenMPT::LogLevel level, const mpt::ustring & text ) const {
310 	m_Messages.push_back( std::make_pair( level, mpt::transcode<std::string>( mpt::common_encoding::utf8, text ) ) );
311 }
312 
PushToCSoundFileLog(const std::string & text) const313 void module_impl::PushToCSoundFileLog( const std::string & text ) const {
314 	m_sndFile->AddToLog( OpenMPT::LogError, mpt::transcode<mpt::ustring>( mpt::common_encoding::utf8, text ) );
315 }
PushToCSoundFileLog(int loglevel,const std::string & text) const316 void module_impl::PushToCSoundFileLog( int loglevel, const std::string & text ) const {
317 	m_sndFile->AddToLog( static_cast<OpenMPT::LogLevel>( loglevel ), mpt::transcode<mpt::ustring>( mpt::common_encoding::utf8, text ) );
318 }
319 
subsong_data(double duration,std::int32_t start_row,std::int32_t start_order,std::int32_t sequence)320 module_impl::subsong_data::subsong_data( double duration, std::int32_t start_row, std::int32_t start_order, std::int32_t sequence )
321 	: duration(duration)
322 	, start_row(start_row)
323 	, start_order(start_order)
324 	, sequence(sequence)
325 {
326 	return;
327 }
328 
filterlength_to_resamplingmode(std::int32_t length)329 static OpenMPT::ResamplingMode filterlength_to_resamplingmode(std::int32_t length) {
330 	OpenMPT::ResamplingMode result = OpenMPT::SRCMODE_SINC8LP;
331 	if ( length == 0 ) {
332 		result = OpenMPT::SRCMODE_SINC8LP;
333 	} else if ( length >= 8 ) {
334 		result = OpenMPT::SRCMODE_SINC8LP;
335 	} else if ( length >= 3 ) {
336 		result = OpenMPT::SRCMODE_CUBIC;
337 	} else if ( length >= 2 ) {
338 		result = OpenMPT::SRCMODE_LINEAR;
339 	} else if ( length >= 1 ) {
340 		result = OpenMPT::SRCMODE_NEAREST;
341 	} else {
342 		throw openmpt::exception("negative filter length");
343 	}
344 	return result;
345 }
resamplingmode_to_filterlength(OpenMPT::ResamplingMode mode)346 static std::int32_t resamplingmode_to_filterlength(OpenMPT::ResamplingMode mode) {
347 	switch ( mode ) {
348 	case OpenMPT::SRCMODE_NEAREST:
349 		return 1;
350 		break;
351 	case OpenMPT::SRCMODE_LINEAR:
352 		return 2;
353 		break;
354 	case OpenMPT::SRCMODE_CUBIC:
355 		return 4;
356 		break;
357 	case OpenMPT::SRCMODE_SINC8:
358 	case OpenMPT::SRCMODE_SINC8LP:
359 	case OpenMPT::SRCMODE_DEFAULT:
360 		return 8;
361 	default:
362 		throw openmpt::exception("unknown interpolation filter length set internally");
363 		break;
364 	}
365 }
366 
367 template < typename sample_type >
valid_channels(sample_type * const * buffers,std::size_t max_channels)368 static inline std::size_t valid_channels( sample_type * const * buffers, std::size_t max_channels ) {
369 	std::size_t channel;
370 	for ( channel = 0; channel < max_channels; ++channel ) {
371 		if ( !buffers[ channel ] ) {
372 			break;
373 		}
374 	}
375 	return channel;
376 }
377 
translate_amiga_filter_type(module_impl::amiga_filter_type amiga_type)378 static OpenMPT::Resampling::AmigaFilter translate_amiga_filter_type( module_impl::amiga_filter_type amiga_type ) {
379 	switch (amiga_type ) {
380 		case module_impl::amiga_filter_type::a500:
381 			return OpenMPT::Resampling::AmigaFilter::A500;
382 		case module_impl::amiga_filter_type::a1200:
383 		case module_impl::amiga_filter_type::auto_filter:
384 		default:
385 			return OpenMPT::Resampling::AmigaFilter::A1200;
386 		case module_impl::amiga_filter_type::unfiltered:
387 			return OpenMPT::Resampling::AmigaFilter::Unfiltered;
388 	}
389 }
390 
ramping_to_mixersettings(OpenMPT::MixerSettings & settings,int ramping)391 static void ramping_to_mixersettings( OpenMPT::MixerSettings & settings, int ramping ) {
392 	if ( ramping == -1 ) {
393 		settings.SetVolumeRampUpMicroseconds( OpenMPT::MixerSettings().GetVolumeRampUpMicroseconds() );
394 		settings.SetVolumeRampDownMicroseconds( OpenMPT::MixerSettings().GetVolumeRampDownMicroseconds() );
395 	} else if ( ramping <= 0 ) {
396 		settings.SetVolumeRampUpMicroseconds( 0 );
397 		settings.SetVolumeRampDownMicroseconds( 0 );
398 	} else {
399 		settings.SetVolumeRampUpMicroseconds( ramping * 1000 );
400 		settings.SetVolumeRampDownMicroseconds( ramping * 1000 );
401 	}
402 }
mixersettings_to_ramping(int & ramping,const OpenMPT::MixerSettings & settings)403 static void mixersettings_to_ramping( int & ramping, const OpenMPT::MixerSettings & settings ) {
404 	std::int32_t ramp_us = std::max( settings.GetVolumeRampUpMicroseconds(), settings.GetVolumeRampDownMicroseconds() );
405 	if ( ( settings.GetVolumeRampUpMicroseconds() == OpenMPT::MixerSettings().GetVolumeRampUpMicroseconds() ) && ( settings.GetVolumeRampDownMicroseconds() == OpenMPT::MixerSettings().GetVolumeRampDownMicroseconds() ) ) {
406 		ramping = -1;
407 	} else if ( ramp_us <= 0 ) {
408 		ramping = 0;
409 	} else {
410 		ramping = ( ramp_us + 500 ) / 1000;
411 	}
412 }
413 
mod_string_to_utf8(const std::string & encoded) const414 std::string module_impl::mod_string_to_utf8( const std::string & encoded ) const {
415 	return OpenMPT::mpt::ToCharset( OpenMPT::mpt::Charset::UTF8, m_sndFile->GetCharsetInternal(), encoded );
416 }
apply_mixer_settings(std::int32_t samplerate,int channels)417 void module_impl::apply_mixer_settings( std::int32_t samplerate, int channels ) {
418 	bool samplerate_changed = static_cast<std::int32_t>( m_sndFile->m_MixerSettings.gdwMixingFreq ) != samplerate;
419 	bool channels_changed = static_cast<int>( m_sndFile->m_MixerSettings.gnChannels ) != channels;
420 	if ( samplerate_changed || channels_changed ) {
421 		OpenMPT::MixerSettings mixersettings = m_sndFile->m_MixerSettings;
422 		std::int32_t volrampin_us = mixersettings.GetVolumeRampUpMicroseconds();
423 		std::int32_t volrampout_us = mixersettings.GetVolumeRampDownMicroseconds();
424 		mixersettings.gdwMixingFreq = samplerate;
425 		mixersettings.gnChannels = channels;
426 		mixersettings.SetVolumeRampUpMicroseconds( volrampin_us );
427 		mixersettings.SetVolumeRampDownMicroseconds( volrampout_us );
428 		m_sndFile->SetMixerSettings( mixersettings );
429 	} else if ( !m_mixer_initialized ) {
430 		m_sndFile->InitPlayer( true );
431 	}
432 	if ( samplerate_changed ) {
433 		m_sndFile->SuspendPlugins();
434 		m_sndFile->ResumePlugins();
435 	}
436 	m_mixer_initialized = true;
437 }
apply_libopenmpt_defaults()438 void module_impl::apply_libopenmpt_defaults() {
439 	set_render_param( module::RENDER_STEREOSEPARATION_PERCENT, 100 );
440 	m_sndFile->Order.SetSequence( 0 );
441 }
get_subsongs() const442 module_impl::subsongs_type module_impl::get_subsongs() const {
443 	std::vector<subsong_data> subsongs;
444 	if ( m_sndFile->Order.GetNumSequences() == 0 ) {
445 		throw openmpt::exception("module contains no songs");
446 	}
447 	for ( OpenMPT::SEQUENCEINDEX seq = 0; seq < m_sndFile->Order.GetNumSequences(); ++seq ) {
448 		const std::vector<OpenMPT::GetLengthType> lengths = m_sndFile->GetLength( OpenMPT::eNoAdjust, OpenMPT::GetLengthTarget( true ).StartPos( seq, 0, 0 ) );
449 		for ( const auto & l : lengths ) {
450 			subsongs.push_back( subsong_data( l.duration, l.startRow, l.startOrder, seq ) );
451 		}
452 	}
453 	return subsongs;
454 }
init_subsongs(subsongs_type & subsongs) const455 void module_impl::init_subsongs( subsongs_type & subsongs ) const {
456 	subsongs = get_subsongs();
457 }
has_subsongs_inited() const458 bool module_impl::has_subsongs_inited() const {
459 	return !m_subsongs.empty();
460 }
ctor(const std::map<std::string,std::string> & ctls)461 void module_impl::ctor( const std::map< std::string, std::string > & ctls ) {
462 	m_sndFile = std::make_unique<OpenMPT::CSoundFile>();
463 	m_loaded = false;
464 	m_mixer_initialized = false;
465 	m_Dithers = std::make_unique<OpenMPT::DithersWrapperOpenMPT>( OpenMPT::mpt::global_prng(), OpenMPT::DithersWrapperOpenMPT::DefaultDither, 4 );
466 	m_LogForwarder = std::make_unique<log_forwarder>( *m_Log );
467 	m_sndFile->SetCustomLog( m_LogForwarder.get() );
468 	m_current_subsong = 0;
469 	m_currentPositionSeconds = 0.0;
470 	m_Gain = 1.0f;
471 	m_ctl_play_at_end = song_end_action::fadeout_song;
472 	m_ctl_load_skip_samples = false;
473 	m_ctl_load_skip_patterns = false;
474 	m_ctl_load_skip_plugins = false;
475 	m_ctl_load_skip_subsongs_init = false;
476 	m_ctl_seek_sync_samples = false;
477 	// init member variables that correspond to ctls
478 	for ( const auto & ctl : ctls ) {
479 		ctl_set( ctl.first, ctl.second, false );
480 	}
481 }
load(const OpenMPT::FileCursor & file,const std::map<std::string,std::string> & ctls)482 void module_impl::load( const OpenMPT::FileCursor & file, const std::map< std::string, std::string > & ctls ) {
483 	loader_log loaderlog;
484 	m_sndFile->SetCustomLog( &loaderlog );
485 	{
486 		int load_flags = OpenMPT::CSoundFile::loadCompleteModule;
487 		if ( m_ctl_load_skip_samples ) {
488 			load_flags &= ~OpenMPT::CSoundFile::loadSampleData;
489 		}
490 		if ( m_ctl_load_skip_patterns ) {
491 			load_flags &= ~OpenMPT::CSoundFile::loadPatternData;
492 		}
493 		if ( m_ctl_load_skip_plugins ) {
494 			load_flags &= ~(OpenMPT::CSoundFile::loadPluginData | OpenMPT::CSoundFile::loadPluginInstance);
495 		}
496 		if ( !m_sndFile->Create( file, static_cast<OpenMPT::CSoundFile::ModLoadingFlags>( load_flags ) ) ) {
497 			throw openmpt::exception("error loading file");
498 		}
499 		if ( !m_ctl_load_skip_subsongs_init ) {
500 			init_subsongs( m_subsongs );
501 		}
502 		m_loaded = true;
503 	}
504 	m_sndFile->SetCustomLog( m_LogForwarder.get() );
505 	std::vector<std::pair<OpenMPT::LogLevel,std::string> > loaderMessages = loaderlog.GetMessages();
506 	for ( const auto & msg : loaderMessages ) {
507 		PushToCSoundFileLog( msg.first, msg.second );
508 		m_loaderMessages.push_back( mpt::transcode<std::string>( mpt::common_encoding::utf8, LogLevelToString( msg.first ) ) + std::string(": ") + msg.second );
509 	}
510 	// init CSoundFile state that corresponds to ctls
511 	for ( const auto & ctl : ctls ) {
512 		ctl_set( ctl.first, ctl.second, false );
513 	}
514 }
is_loaded() const515 bool module_impl::is_loaded() const {
516 	return m_loaded;
517 }
read_wrapper(std::size_t count,std::int16_t * left,std::int16_t * right,std::int16_t * rear_left,std::int16_t * rear_right)518 std::size_t module_impl::read_wrapper( std::size_t count, std::int16_t * left, std::int16_t * right, std::int16_t * rear_left, std::int16_t * rear_right ) {
519 	m_sndFile->ResetMixStat();
520 	m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song );
521 	std::size_t count_read = 0;
522 	std::int16_t * const buffers[4] = { left, right, rear_left, rear_right };
523 	OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<std::int16_t>> target( mpt::audio_span_planar<std::int16_t>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain );
524 	while ( count > 0 ) {
525 		std::size_t count_chunk = m_sndFile->Read(
526 			static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels
527 			target
528 			);
529 		if ( count_chunk == 0 ) {
530 			break;
531 		}
532 		count -= count_chunk;
533 		count_read += count_chunk;
534 	}
535 	if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) {
536 		// This is the song end, but allow the song or loop to restart on the next call
537 		m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED);
538 	}
539 	return count_read;
540 }
read_wrapper(std::size_t count,float * left,float * right,float * rear_left,float * rear_right)541 std::size_t module_impl::read_wrapper( std::size_t count, float * left, float * right, float * rear_left, float * rear_right ) {
542 	m_sndFile->ResetMixStat();
543 	m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song );
544 	std::size_t count_read = 0;
545 	float * const buffers[4] = { left, right, rear_left, rear_right };
546 	OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<float>> target( mpt::audio_span_planar<float>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain );
547 	while ( count > 0 ) {
548 		std::size_t count_chunk = m_sndFile->Read(
549 			static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels
550 			target
551 			);
552 		if ( count_chunk == 0 ) {
553 			break;
554 		}
555 		count -= count_chunk;
556 		count_read += count_chunk;
557 	}
558 	if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) {
559 		// This is the song end, but allow the song or loop to restart on the next call
560 		m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED);
561 	}
562 	return count_read;
563 }
read_interleaved_wrapper(std::size_t count,std::size_t channels,std::int16_t * interleaved)564 std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_t channels, std::int16_t * interleaved ) {
565 	m_sndFile->ResetMixStat();
566 	m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song );
567 	std::size_t count_read = 0;
568 	OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<std::int16_t>> target( mpt::audio_span_interleaved<std::int16_t>( interleaved, channels, count ), *m_Dithers, m_Gain );
569 	while ( count > 0 ) {
570 		std::size_t count_chunk = m_sndFile->Read(
571 			static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels
572 			target
573 			);
574 		if ( count_chunk == 0 ) {
575 			break;
576 		}
577 		count -= count_chunk;
578 		count_read += count_chunk;
579 	}
580 	if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) {
581 		// This is the song end, but allow the song or loop to restart on the next call
582 		m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED);
583 	}
584 	return count_read;
585 }
read_interleaved_wrapper(std::size_t count,std::size_t channels,float * interleaved)586 std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_t channels, float * interleaved ) {
587 	m_sndFile->ResetMixStat();
588 	m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song );
589 	std::size_t count_read = 0;
590 	OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<float>> target( mpt::audio_span_interleaved<float>( interleaved, channels, count ), *m_Dithers, m_Gain );
591 	while ( count > 0 ) {
592 		std::size_t count_chunk = m_sndFile->Read(
593 			static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels
594 			target
595 			);
596 		if ( count_chunk == 0 ) {
597 			break;
598 		}
599 		count -= count_chunk;
600 		count_read += count_chunk;
601 	}
602 	if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) {
603 		// This is the song end, but allow the song or loop to restart on the next call
604 		m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED);
605 	}
606 	return count_read;
607 }
608 
get_supported_extensions()609 std::vector<std::string> module_impl::get_supported_extensions() {
610 	std::vector<std::string> retval;
611 	std::vector<const char *> extensions = OpenMPT::CSoundFile::GetSupportedExtensions( false );
612 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
613 	return retval;
614 }
is_extension_supported(std::string_view extension)615 bool module_impl::is_extension_supported( std::string_view extension ) {
616 	return OpenMPT::CSoundFile::IsExtensionSupported( extension );
617 }
could_open_probability(const OpenMPT::FileCursor & file,double effort,std::unique_ptr<log_interface> log)618 double module_impl::could_open_probability( const OpenMPT::FileCursor & file, double effort, std::unique_ptr<log_interface> log ) {
619 	try {
620 		if ( effort >= 0.8 ) {
621 			std::unique_ptr<OpenMPT::CSoundFile> sndFile = std::make_unique<OpenMPT::CSoundFile>();
622 			std::unique_ptr<log_forwarder> logForwarder = std::make_unique<log_forwarder>( *log );
623 			sndFile->SetCustomLog( logForwarder.get() );
624 			if ( !sndFile->Create( file, OpenMPT::CSoundFile::loadCompleteModule ) ) {
625 				return 0.0;
626 			}
627 			sndFile->Destroy();
628 			return 1.0;
629 		} else if ( effort >= 0.6 ) {
630 			std::unique_ptr<OpenMPT::CSoundFile> sndFile = std::make_unique<OpenMPT::CSoundFile>();
631 			std::unique_ptr<log_forwarder> logForwarder = std::make_unique<log_forwarder>( *log );
632 			sndFile->SetCustomLog( logForwarder.get() );
633 			if ( !sndFile->Create( file, OpenMPT::CSoundFile::loadNoPatternOrPluginData ) ) {
634 				return 0.0;
635 			}
636 			sndFile->Destroy();
637 			return 0.8;
638 		} else if ( effort >= 0.2 ) {
639 			std::unique_ptr<OpenMPT::CSoundFile> sndFile = std::make_unique<OpenMPT::CSoundFile>();
640 			std::unique_ptr<log_forwarder> logForwarder = std::make_unique<log_forwarder>( *log );
641 			sndFile->SetCustomLog( logForwarder.get() );
642 			if ( !sndFile->Create( file, OpenMPT::CSoundFile::onlyVerifyHeader ) ) {
643 				return 0.0;
644 			}
645 			sndFile->Destroy();
646 			return 0.6;
647 		} else if ( effort >= 0.1 ) {
648 			OpenMPT::FileCursor::PinnedView view = file.GetPinnedView( probe_file_header_get_recommended_size() );
649 			int probe_file_header_result = probe_file_header( probe_file_header_flags_default2, view.data(), view.size(), file.GetLength() );
650 			double result = 0.0;
651 			switch ( probe_file_header_result ) {
652 				case probe_file_header_result_success:
653 					result = 0.6;
654 					break;
655 				case probe_file_header_result_failure:
656 					result = 0.0;
657 					break;
658 				case probe_file_header_result_wantmoredata:
659 					result = 0.3;
660 					break;
661 				default:
662 					throw openmpt::exception("");
663 					break;
664 			}
665 			return result;
666 		} else {
667 			return 0.2;
668 		}
669 	} catch ( ... ) {
670 		return 0.0;
671 	}
672 }
could_open_probability(callback_stream_wrapper stream,double effort,std::unique_ptr<log_interface> log)673 double module_impl::could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log ) {
674 	mpt::IO::CallbackStream fstream;
675 	fstream.stream = stream.stream;
676 	fstream.read = stream.read;
677 	fstream.seek = stream.seek;
678 	fstream.tell = stream.tell;
679 	return could_open_probability( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( fstream ), effort, std::move(log) );
680 }
could_open_probability(std::istream & stream,double effort,std::unique_ptr<log_interface> log)681 double module_impl::could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log ) {
682 	return could_open_probability(mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( stream ), effort, std::move(log) );
683 }
684 
probe_file_header_get_recommended_size()685 std::size_t module_impl::probe_file_header_get_recommended_size() {
686 	return OpenMPT::CSoundFile::ProbeRecommendedSize;
687 }
probe_file_header(std::uint64_t flags,const std::byte * data,std::size_t size,std::uint64_t filesize)688 int module_impl::probe_file_header( std::uint64_t flags, const std::byte * data, std::size_t size, std::uint64_t filesize ) {
689 	int result = 0;
690 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( data, size ), &filesize ) ) {
691 		case OpenMPT::CSoundFile::ProbeSuccess:
692 			result = probe_file_header_result_success;
693 			break;
694 		case OpenMPT::CSoundFile::ProbeFailure:
695 			result = probe_file_header_result_failure;
696 			break;
697 		case OpenMPT::CSoundFile::ProbeWantMoreData:
698 			result = probe_file_header_result_wantmoredata;
699 			break;
700 		default:
701 			throw exception("internal error");
702 			break;
703 	}
704 	return result;
705 }
probe_file_header(std::uint64_t flags,const std::uint8_t * data,std::size_t size,std::uint64_t filesize)706 int module_impl::probe_file_header( std::uint64_t flags, const std::uint8_t * data, std::size_t size, std::uint64_t filesize ) {
707 	int result = 0;
708 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::byte_cast<const std::byte*>( data ), size ), &filesize ) ) {
709 		case OpenMPT::CSoundFile::ProbeSuccess:
710 			result = probe_file_header_result_success;
711 			break;
712 		case OpenMPT::CSoundFile::ProbeFailure:
713 			result = probe_file_header_result_failure;
714 			break;
715 		case OpenMPT::CSoundFile::ProbeWantMoreData:
716 			result = probe_file_header_result_wantmoredata;
717 			break;
718 		default:
719 			throw exception("internal error");
720 			break;
721 	}
722 	return result;
723 }
probe_file_header(std::uint64_t flags,const void * data,std::size_t size,std::uint64_t filesize)724 int module_impl::probe_file_header( std::uint64_t flags, const void * data, std::size_t size, std::uint64_t filesize ) {
725 	int result = 0;
726 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::void_cast<const std::byte*>( data ), size ), &filesize ) ) {
727 		case OpenMPT::CSoundFile::ProbeSuccess:
728 			result = probe_file_header_result_success;
729 			break;
730 		case OpenMPT::CSoundFile::ProbeFailure:
731 			result = probe_file_header_result_failure;
732 			break;
733 		case OpenMPT::CSoundFile::ProbeWantMoreData:
734 			result = probe_file_header_result_wantmoredata;
735 			break;
736 		default:
737 			throw exception("internal error");
738 			break;
739 	}
740 	return result;
741 }
probe_file_header(std::uint64_t flags,const std::byte * data,std::size_t size)742 int module_impl::probe_file_header( std::uint64_t flags, const std::byte * data, std::size_t size ) {
743 	int result = 0;
744 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( data, size ), nullptr ) ) {
745 		case OpenMPT::CSoundFile::ProbeSuccess:
746 			result = probe_file_header_result_success;
747 			break;
748 		case OpenMPT::CSoundFile::ProbeFailure:
749 			result = probe_file_header_result_failure;
750 			break;
751 		case OpenMPT::CSoundFile::ProbeWantMoreData:
752 			result = probe_file_header_result_wantmoredata;
753 			break;
754 		default:
755 			throw exception("internal error");
756 			break;
757 	}
758 	return result;
759 }
probe_file_header(std::uint64_t flags,const std::uint8_t * data,std::size_t size)760 int module_impl::probe_file_header( std::uint64_t flags, const std::uint8_t * data, std::size_t size ) {
761 	int result = 0;
762 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::byte_cast<const std::byte*>( data ), size ), nullptr ) ) {
763 		case OpenMPT::CSoundFile::ProbeSuccess:
764 			result = probe_file_header_result_success;
765 			break;
766 		case OpenMPT::CSoundFile::ProbeFailure:
767 			result = probe_file_header_result_failure;
768 			break;
769 		case OpenMPT::CSoundFile::ProbeWantMoreData:
770 			result = probe_file_header_result_wantmoredata;
771 			break;
772 		default:
773 			throw exception("internal error");
774 			break;
775 	}
776 	return result;
777 }
probe_file_header(std::uint64_t flags,const void * data,std::size_t size)778 int module_impl::probe_file_header( std::uint64_t flags, const void * data, std::size_t size ) {
779 	int result = 0;
780 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::void_cast<const std::byte*>( data ), size ), nullptr ) ) {
781 		case OpenMPT::CSoundFile::ProbeSuccess:
782 			result = probe_file_header_result_success;
783 			break;
784 		case OpenMPT::CSoundFile::ProbeFailure:
785 			result = probe_file_header_result_failure;
786 			break;
787 		case OpenMPT::CSoundFile::ProbeWantMoreData:
788 			result = probe_file_header_result_wantmoredata;
789 			break;
790 		default:
791 			throw exception("internal error");
792 			break;
793 	}
794 	return result;
795 }
probe_file_header(std::uint64_t flags,std::istream & stream)796 int module_impl::probe_file_header( std::uint64_t flags, std::istream & stream ) {
797 	int result = 0;
798 	char buffer[ PROBE_RECOMMENDED_SIZE ];
799 	OpenMPT::MemsetZero( buffer );
800 	std::size_t size_read = 0;
801 	std::size_t size_toread = OpenMPT::CSoundFile::ProbeRecommendedSize;
802 	if ( stream.bad() ) {
803 		throw exception("error reading stream");
804 	}
805 	const bool seekable = mpt::IO::FileDataStdStream::IsSeekable( stream );
806 	const std::uint64_t filesize = ( seekable ? mpt::IO::FileDataStdStream::GetLength( stream ) : 0 );
807 	while ( ( size_toread > 0 ) && stream ) {
808 		stream.read( buffer + size_read, size_toread );
809 		if ( stream.bad() ) {
810 			throw exception("error reading stream");
811 		} else if ( stream.eof() ) {
812 			// normal
813 		} else if ( stream.fail() ) {
814 			throw exception("error reading stream");
815 		} else {
816 			// normal
817 		}
818 		std::size_t read_count = static_cast<std::size_t>( stream.gcount() );
819 		size_read += read_count;
820 		size_toread -= read_count;
821 	}
822 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::byte_cast<const std::byte*>( buffer ), size_read ), seekable ? &filesize : nullptr ) ) {
823 		case OpenMPT::CSoundFile::ProbeSuccess:
824 			result = probe_file_header_result_success;
825 			break;
826 		case OpenMPT::CSoundFile::ProbeFailure:
827 			result = probe_file_header_result_failure;
828 			break;
829 		case OpenMPT::CSoundFile::ProbeWantMoreData:
830 			result = probe_file_header_result_wantmoredata;
831 			break;
832 		default:
833 			throw exception("internal error");
834 			break;
835 	}
836 	return result;
837 }
probe_file_header(std::uint64_t flags,callback_stream_wrapper stream)838 int module_impl::probe_file_header( std::uint64_t flags, callback_stream_wrapper stream ) {
839 	int result = 0;
840 	char buffer[ PROBE_RECOMMENDED_SIZE ];
841 	OpenMPT::MemsetZero( buffer );
842 	std::size_t size_read = 0;
843 	std::size_t size_toread = OpenMPT::CSoundFile::ProbeRecommendedSize;
844 	if ( !stream.read ) {
845 		throw exception("error reading stream");
846 	}
847 	mpt::IO::CallbackStream fstream;
848 	fstream.stream = stream.stream;
849 	fstream.read = stream.read;
850 	fstream.seek = stream.seek;
851 	fstream.tell = stream.tell;
852 	const bool seekable = mpt::IO::FileDataCallbackStream::IsSeekable( fstream );
853 	const std::uint64_t filesize = ( seekable ? mpt::IO::FileDataCallbackStream::GetLength( fstream ) : 0 );
854 	while ( size_toread > 0 ) {
855 		std::size_t read_count = stream.read( stream.stream, buffer + size_read, size_toread );
856 		size_read += read_count;
857 		size_toread -= read_count;
858 		if ( read_count == 0 ) { // eof
859 			break;
860 		}
861 	}
862 	switch ( OpenMPT::CSoundFile::Probe( static_cast<OpenMPT::CSoundFile::ProbeFlags>( flags ), mpt::span<const std::byte>( mpt::byte_cast<const std::byte*>( buffer ), size_read ), seekable ? &filesize : nullptr ) ) {
863 		case OpenMPT::CSoundFile::ProbeSuccess:
864 			result = probe_file_header_result_success;
865 			break;
866 		case OpenMPT::CSoundFile::ProbeFailure:
867 			result = probe_file_header_result_failure;
868 			break;
869 		case OpenMPT::CSoundFile::ProbeWantMoreData:
870 			result = probe_file_header_result_wantmoredata;
871 			break;
872 		default:
873 			throw exception("internal error");
874 			break;
875 	}
876 	return result;
877 }
module_impl(callback_stream_wrapper stream,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)878 module_impl::module_impl( callback_stream_wrapper stream, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
879 	ctor( ctls );
880 	mpt::IO::CallbackStream fstream;
881 	fstream.stream = stream.stream;
882 	fstream.read = stream.read;
883 	fstream.seek = stream.seek;
884 	fstream.tell = stream.tell;
885 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( fstream ), ctls );
886 	apply_libopenmpt_defaults();
887 }
module_impl(std::istream & stream,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)888 module_impl::module_impl( std::istream & stream, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
889 	ctor( ctls );
890 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( stream ), ctls );
891 	apply_libopenmpt_defaults();
892 }
module_impl(const std::vector<std::byte> & data,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)893 module_impl::module_impl( const std::vector<std::byte> & data, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
894 	ctor( ctls );
895 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::as_span( data ) ), ctls );
896 	apply_libopenmpt_defaults();
897 }
module_impl(const std::vector<std::uint8_t> & data,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)898 module_impl::module_impl( const std::vector<std::uint8_t> & data, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
899 	ctor( ctls );
900 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::as_span( data ) ), ctls );
901 	apply_libopenmpt_defaults();
902 }
module_impl(const std::vector<char> & data,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)903 module_impl::module_impl( const std::vector<char> & data, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
904 	ctor( ctls );
905 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::byte_cast< mpt::span< const std::byte > >( mpt::as_span( data ) ) ), ctls );
906 	apply_libopenmpt_defaults();
907 }
module_impl(const std::byte * data,std::size_t size,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)908 module_impl::module_impl( const std::byte * data, std::size_t size, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
909 	ctor( ctls );
910 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::as_span( data, size ) ), ctls );
911 	apply_libopenmpt_defaults();
912 }
module_impl(const std::uint8_t * data,std::size_t size,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)913 module_impl::module_impl( const std::uint8_t * data, std::size_t size, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
914 	ctor( ctls );
915 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::as_span( data, size ) ), ctls );
916 	apply_libopenmpt_defaults();
917 }
module_impl(const char * data,std::size_t size,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)918 module_impl::module_impl( const char * data, std::size_t size, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
919 	ctor( ctls );
920 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::byte_cast< mpt::span< const std::byte > >( mpt::as_span( data, size ) ) ), ctls );
921 	apply_libopenmpt_defaults();
922 }
module_impl(const void * data,std::size_t size,std::unique_ptr<log_interface> log,const std::map<std::string,std::string> & ctls)923 module_impl::module_impl( const void * data, std::size_t size, std::unique_ptr<log_interface> log, const std::map< std::string, std::string > & ctls ) : m_Log(std::move(log)) {
924 	ctor( ctls );
925 	load( mpt::IO::make_FileCursor<OpenMPT::mpt::PathString>( mpt::as_span( mpt::void_cast< const std::byte * >( data ), size ) ), ctls );
926 	apply_libopenmpt_defaults();
927 }
~module_impl()928 module_impl::~module_impl() {
929 	m_sndFile->Destroy();
930 }
931 
get_render_param(int param) const932 std::int32_t module_impl::get_render_param( int param ) const {
933 	std::int32_t result = 0;
934 	switch ( param ) {
935 		case module::RENDER_MASTERGAIN_MILLIBEL: {
936 			result = static_cast<std::int32_t>( 1000.0f * 2.0f * std::log10( m_Gain ) );
937 		} break;
938 		case module::RENDER_STEREOSEPARATION_PERCENT: {
939 			result = m_sndFile->m_MixerSettings.m_nStereoSeparation * 100 / OpenMPT::MixerSettings::StereoSeparationScale;
940 		} break;
941 		case module::RENDER_INTERPOLATIONFILTER_LENGTH: {
942 			result = resamplingmode_to_filterlength( m_sndFile->m_Resampler.m_Settings.SrcMode );
943 		} break;
944 		case module::RENDER_VOLUMERAMPING_STRENGTH: {
945 			int ramping = 0;
946 			mixersettings_to_ramping( ramping, m_sndFile->m_MixerSettings );
947 			result = ramping;
948 		} break;
949 		default: throw openmpt::exception("unknown render param"); break;
950 	}
951 	return result;
952 }
set_render_param(int param,std::int32_t value)953 void module_impl::set_render_param( int param, std::int32_t value ) {
954 	switch ( param ) {
955 		case module::RENDER_MASTERGAIN_MILLIBEL: {
956 			m_Gain = static_cast<float>( std::pow( 10.0f, value * 0.001f * 0.5f ) );
957 		} break;
958 		case module::RENDER_STEREOSEPARATION_PERCENT: {
959 			std::int32_t newvalue = value * OpenMPT::MixerSettings::StereoSeparationScale / 100;
960 			if ( newvalue != static_cast<std::int32_t>( m_sndFile->m_MixerSettings.m_nStereoSeparation ) ) {
961 				OpenMPT::MixerSettings settings = m_sndFile->m_MixerSettings;
962 				settings.m_nStereoSeparation = newvalue;
963 				m_sndFile->SetMixerSettings( settings );
964 			}
965 		} break;
966 		case module::RENDER_INTERPOLATIONFILTER_LENGTH: {
967 			OpenMPT::CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
968 			newsettings.SrcMode = filterlength_to_resamplingmode( value );
969 			if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
970 				m_sndFile->SetResamplerSettings( newsettings );
971 			}
972 		} break;
973 		case module::RENDER_VOLUMERAMPING_STRENGTH: {
974 			OpenMPT::MixerSettings newsettings = m_sndFile->m_MixerSettings;
975 			ramping_to_mixersettings( newsettings, value );
976 			if ( m_sndFile->m_MixerSettings.VolumeRampUpMicroseconds != newsettings.VolumeRampUpMicroseconds || m_sndFile->m_MixerSettings.VolumeRampDownMicroseconds != newsettings.VolumeRampDownMicroseconds ) {
977 				m_sndFile->SetMixerSettings( newsettings );
978 			}
979 		} break;
980 		default: throw openmpt::exception("unknown render param"); break;
981 	}
982 }
983 
read(std::int32_t samplerate,std::size_t count,std::int16_t * mono)984 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, std::int16_t * mono ) {
985 	if ( !mono ) {
986 		throw openmpt::exception("null pointer");
987 	}
988 	apply_mixer_settings( samplerate, 1 );
989 	count = read_wrapper( count, mono, nullptr, nullptr, nullptr );
990 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
991 	return count;
992 }
read(std::int32_t samplerate,std::size_t count,std::int16_t * left,std::int16_t * right)993 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, std::int16_t * left, std::int16_t * right ) {
994 	if ( !left || !right ) {
995 		throw openmpt::exception("null pointer");
996 	}
997 	apply_mixer_settings( samplerate, 2 );
998 	count = read_wrapper( count, left, right, nullptr, nullptr );
999 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1000 	return count;
1001 }
read(std::int32_t samplerate,std::size_t count,std::int16_t * left,std::int16_t * right,std::int16_t * rear_left,std::int16_t * rear_right)1002 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, std::int16_t * left, std::int16_t * right, std::int16_t * rear_left, std::int16_t * rear_right ) {
1003 	if ( !left || !right || !rear_left || !rear_right ) {
1004 		throw openmpt::exception("null pointer");
1005 	}
1006 	apply_mixer_settings( samplerate, 4 );
1007 	count = read_wrapper( count, left, right, rear_left, rear_right );
1008 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1009 	return count;
1010 }
read(std::int32_t samplerate,std::size_t count,float * mono)1011 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, float * mono ) {
1012 	if ( !mono ) {
1013 		throw openmpt::exception("null pointer");
1014 	}
1015 	apply_mixer_settings( samplerate, 1 );
1016 	count = read_wrapper( count, mono, nullptr, nullptr, nullptr );
1017 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1018 	return count;
1019 }
read(std::int32_t samplerate,std::size_t count,float * left,float * right)1020 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, float * left, float * right ) {
1021 	if ( !left || !right ) {
1022 		throw openmpt::exception("null pointer");
1023 	}
1024 	apply_mixer_settings( samplerate, 2 );
1025 	count = read_wrapper( count, left, right, nullptr, nullptr );
1026 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1027 	return count;
1028 }
read(std::int32_t samplerate,std::size_t count,float * left,float * right,float * rear_left,float * rear_right)1029 std::size_t module_impl::read( std::int32_t samplerate, std::size_t count, float * left, float * right, float * rear_left, float * rear_right ) {
1030 	if ( !left || !right || !rear_left || !rear_right ) {
1031 		throw openmpt::exception("null pointer");
1032 	}
1033 	apply_mixer_settings( samplerate, 4 );
1034 	count = read_wrapper( count, left, right, rear_left, rear_right );
1035 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1036 	return count;
1037 }
read_interleaved_stereo(std::int32_t samplerate,std::size_t count,std::int16_t * interleaved_stereo)1038 std::size_t module_impl::read_interleaved_stereo( std::int32_t samplerate, std::size_t count, std::int16_t * interleaved_stereo ) {
1039 	if ( !interleaved_stereo ) {
1040 		throw openmpt::exception("null pointer");
1041 	}
1042 	apply_mixer_settings( samplerate, 2 );
1043 	count = read_interleaved_wrapper( count, 2, interleaved_stereo );
1044 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1045 	return count;
1046 }
read_interleaved_quad(std::int32_t samplerate,std::size_t count,std::int16_t * interleaved_quad)1047 std::size_t module_impl::read_interleaved_quad( std::int32_t samplerate, std::size_t count, std::int16_t * interleaved_quad ) {
1048 	if ( !interleaved_quad ) {
1049 		throw openmpt::exception("null pointer");
1050 	}
1051 	apply_mixer_settings( samplerate, 4 );
1052 	count = read_interleaved_wrapper( count, 4, interleaved_quad );
1053 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1054 	return count;
1055 }
read_interleaved_stereo(std::int32_t samplerate,std::size_t count,float * interleaved_stereo)1056 std::size_t module_impl::read_interleaved_stereo( std::int32_t samplerate, std::size_t count, float * interleaved_stereo ) {
1057 	if ( !interleaved_stereo ) {
1058 		throw openmpt::exception("null pointer");
1059 	}
1060 	apply_mixer_settings( samplerate, 2 );
1061 	count = read_interleaved_wrapper( count, 2, interleaved_stereo );
1062 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1063 	return count;
1064 }
read_interleaved_quad(std::int32_t samplerate,std::size_t count,float * interleaved_quad)1065 std::size_t module_impl::read_interleaved_quad( std::int32_t samplerate, std::size_t count, float * interleaved_quad ) {
1066 	if ( !interleaved_quad ) {
1067 		throw openmpt::exception("null pointer");
1068 	}
1069 	apply_mixer_settings( samplerate, 4 );
1070 	count = read_interleaved_wrapper( count, 4, interleaved_quad );
1071 	m_currentPositionSeconds += static_cast<double>( count ) / static_cast<double>( samplerate );
1072 	return count;
1073 }
1074 
1075 
get_duration_seconds() const1076 double module_impl::get_duration_seconds() const {
1077 	std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ?  std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
1078 	const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
1079 	if ( m_current_subsong == all_subsongs ) {
1080 		// Play all subsongs consecutively.
1081 		double total_duration = 0.0;
1082 		for ( const auto & subsong : subsongs ) {
1083 			total_duration += subsong.duration;
1084 		}
1085 		return total_duration;
1086 	}
1087 	return subsongs[m_current_subsong].duration;
1088 }
select_subsong(std::int32_t subsong)1089 void module_impl::select_subsong( std::int32_t subsong ) {
1090 	std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ?  std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
1091 	const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
1092 	if ( subsong != all_subsongs && ( subsong < 0 || subsong >= static_cast<std::int32_t>( subsongs.size() ) ) ) {
1093 		throw openmpt::exception("invalid subsong");
1094 	}
1095 	m_current_subsong = subsong;
1096 	m_sndFile->m_SongFlags.set( OpenMPT::SONG_PLAYALLSONGS, subsong == all_subsongs );
1097 	if ( subsong == all_subsongs ) {
1098 		subsong = 0;
1099 	}
1100 	m_sndFile->Order.SetSequence( static_cast<OpenMPT::SEQUENCEINDEX>( subsongs[subsong].sequence ) );
1101 	set_position_order_row( subsongs[subsong].start_order, subsongs[subsong].start_row );
1102 	m_currentPositionSeconds = 0.0;
1103 }
get_selected_subsong() const1104 std::int32_t module_impl::get_selected_subsong() const {
1105 	return m_current_subsong;
1106 }
set_repeat_count(std::int32_t repeat_count)1107 void module_impl::set_repeat_count( std::int32_t repeat_count ) {
1108 	m_sndFile->SetRepeatCount( repeat_count );
1109 }
get_repeat_count() const1110 std::int32_t module_impl::get_repeat_count() const {
1111 	return m_sndFile->GetRepeatCount();
1112 }
get_position_seconds() const1113 double module_impl::get_position_seconds() const {
1114 	return m_currentPositionSeconds;
1115 }
set_position_seconds(double seconds)1116 double module_impl::set_position_seconds( double seconds ) {
1117 	std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ?  std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
1118 	const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
1119 	const subsong_data * subsong = 0;
1120 	double base_seconds = 0.0;
1121 	if ( m_current_subsong == all_subsongs ) {
1122 		// When playing all subsongs, find out which subsong this time would belong to.
1123 		subsong = &subsongs.back();
1124 		for ( std::size_t i = 0; i < subsongs.size(); ++i ) {
1125 			if ( base_seconds + subsongs[i].duration > seconds ) {
1126 				subsong = &subsongs[i];
1127 				break;
1128 			}
1129 			base_seconds += subsong->duration;
1130 		}
1131 		seconds -= base_seconds;
1132 	} else {
1133 		subsong = &subsongs[m_current_subsong];
1134 	}
1135 	m_sndFile->SetCurrentOrder( static_cast<OpenMPT::ORDERINDEX>( subsong->start_order ) );
1136 	OpenMPT::GetLengthType t = m_sndFile->GetLength( m_ctl_seek_sync_samples ? OpenMPT::eAdjustSamplePositions : OpenMPT::eAdjust, OpenMPT::GetLengthTarget( seconds ).StartPos( static_cast<OpenMPT::SEQUENCEINDEX>( subsong->sequence ), static_cast<OpenMPT::ORDERINDEX>( subsong->start_order ), static_cast<OpenMPT::ROWINDEX>( subsong->start_row ) ) ).back();
1137 	m_sndFile->m_PlayState.m_nNextOrder = m_sndFile->m_PlayState.m_nCurrentOrder = t.targetReached ? t.lastOrder : t.endOrder;
1138 	m_sndFile->m_PlayState.m_nNextRow = t.targetReached ? t.lastRow : t.endRow;
1139 	m_sndFile->m_PlayState.m_nTickCount = OpenMPT::CSoundFile::TICKS_ROW_FINISHED;
1140 	m_currentPositionSeconds = base_seconds + t.duration;
1141 	return m_currentPositionSeconds;
1142 }
set_position_order_row(std::int32_t order,std::int32_t row)1143 double module_impl::set_position_order_row( std::int32_t order, std::int32_t row ) {
1144 	if ( order < 0 || order >= m_sndFile->Order().GetLengthTailTrimmed() ) {
1145 		return m_currentPositionSeconds;
1146 	}
1147 	OpenMPT::PATTERNINDEX pattern = m_sndFile->Order()[order];
1148 	if ( m_sndFile->Patterns.IsValidIndex( pattern ) ) {
1149 		if ( row < 0 || row >= static_cast<std::int32_t>( m_sndFile->Patterns[pattern].GetNumRows() ) ) {
1150 			return m_currentPositionSeconds;
1151 		}
1152 	} else {
1153 		row = 0;
1154 	}
1155 	m_sndFile->m_PlayState.m_nCurrentOrder = static_cast<OpenMPT::ORDERINDEX>( order );
1156 	m_sndFile->SetCurrentOrder( static_cast<OpenMPT::ORDERINDEX>( order ) );
1157 	m_sndFile->m_PlayState.m_nNextRow = static_cast<OpenMPT::ROWINDEX>( row );
1158 	m_sndFile->m_PlayState.m_nTickCount = OpenMPT::CSoundFile::TICKS_ROW_FINISHED;
1159 	m_currentPositionSeconds = m_sndFile->GetLength( m_ctl_seek_sync_samples ? OpenMPT::eAdjustSamplePositions : OpenMPT::eAdjust, OpenMPT::GetLengthTarget( static_cast<OpenMPT::ORDERINDEX>( order ), static_cast<OpenMPT::ROWINDEX>( row ) ) ).back().duration;
1160 	return m_currentPositionSeconds;
1161 }
get_metadata_keys() const1162 std::vector<std::string> module_impl::get_metadata_keys() const {
1163 	return
1164 	{
1165 		"type",
1166 		"type_long",
1167 		"originaltype",
1168 		"originaltype_long",
1169 		"container",
1170 		"container_long",
1171 		"tracker",
1172 		"artist",
1173 		"title",
1174 		"date",
1175 		"message",
1176 		"message_raw",
1177 		"warnings",
1178 	};
1179 }
get_message_instruments() const1180 std::string module_impl::get_message_instruments() const {
1181 	std::string retval;
1182 	std::string tmp;
1183 	bool valid = false;
1184 	for ( OpenMPT::INSTRUMENTINDEX i = 1; i <= m_sndFile->GetNumInstruments(); ++i ) {
1185 		std::string instname = m_sndFile->GetInstrumentName( i );
1186 		if ( !instname.empty() ) {
1187 			valid = true;
1188 		}
1189 		tmp += instname;
1190 		tmp += "\n";
1191 	}
1192 	if ( valid ) {
1193 		retval = tmp;
1194 	}
1195 	return retval;
1196 }
get_message_samples() const1197 std::string module_impl::get_message_samples() const {
1198 	std::string retval;
1199 	std::string tmp;
1200 	bool valid = false;
1201 	for ( OpenMPT::SAMPLEINDEX i = 1; i <= m_sndFile->GetNumSamples(); ++i ) {
1202 		std::string samplename = m_sndFile->GetSampleName( i );
1203 		if ( !samplename.empty() ) {
1204 			valid = true;
1205 		}
1206 		tmp += samplename;
1207 		tmp += "\n";
1208 	}
1209 	if ( valid ) {
1210 		retval = tmp;
1211 	}
1212 	return retval;
1213 }
get_metadata(const std::string & key) const1214 std::string module_impl::get_metadata( const std::string & key ) const {
1215 	if ( key == std::string("type") ) {
1216 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_modFormat.type );
1217 	} else if ( key == std::string("type_long") ) {
1218 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_modFormat.formatName );
1219 	} else if ( key == std::string("originaltype") ) {
1220 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_modFormat.originalType );
1221 	} else if ( key == std::string("originaltype_long") ) {
1222 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_modFormat.originalFormatName );
1223 	} else if ( key == std::string("container") ) {
1224 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::CSoundFile::ModContainerTypeToString( m_sndFile->GetContainerType() ) );
1225 	} else if ( key == std::string("container_long") ) {
1226 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, OpenMPT::CSoundFile::ModContainerTypeToTracker( m_sndFile->GetContainerType() ) );
1227 	} else if ( key == std::string("tracker") ) {
1228 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_modFormat.madeWithTracker );
1229 	} else if ( key == std::string("artist") ) {
1230 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->m_songArtist );
1231 	} else if ( key == std::string("title") ) {
1232 		return mod_string_to_utf8( m_sndFile->GetTitle() );
1233 	} else if ( key == std::string("date") ) {
1234 		if ( m_sndFile->GetFileHistory().empty() || !m_sndFile->GetFileHistory().back().HasValidDate() ) {
1235 			return std::string();
1236 		}
1237 		return mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->GetFileHistory().back().AsISO8601() );
1238 	} else if ( key == std::string("message") ) {
1239 		std::string retval = m_sndFile->m_songMessage.GetFormatted( OpenMPT::SongMessage::leLF );
1240 		if ( retval.empty() ) {
1241 			switch ( m_sndFile->GetMessageHeuristic() ) {
1242 				case OpenMPT::ModMessageHeuristicOrder::Instruments:
1243 					retval = get_message_instruments();
1244 					break;
1245 				case OpenMPT::ModMessageHeuristicOrder::Samples:
1246 					retval = get_message_samples();
1247 					break;
1248 				case OpenMPT::ModMessageHeuristicOrder::InstrumentsSamples:
1249 					if ( retval.empty() ) {
1250 						retval = get_message_instruments();
1251 					}
1252 					if ( retval.empty() ) {
1253 						retval = get_message_samples();
1254 					}
1255 					break;
1256 				case OpenMPT::ModMessageHeuristicOrder::SamplesInstruments:
1257 					if ( retval.empty() ) {
1258 						retval = get_message_samples();
1259 					}
1260 					if ( retval.empty() ) {
1261 						retval = get_message_instruments();
1262 					}
1263 					break;
1264 				case OpenMPT::ModMessageHeuristicOrder::BothInstrumentsSamples:
1265 					{
1266 						std::string message_instruments = get_message_instruments();
1267 						std::string message_samples = get_message_samples();
1268 						if ( !message_instruments.empty() ) {
1269 							retval += std::move( message_instruments );
1270 						}
1271 						if ( !message_samples.empty() ) {
1272 							retval += std::move( message_samples );
1273 						}
1274 					}
1275 					break;
1276 				case OpenMPT::ModMessageHeuristicOrder::BothSamplesInstruments:
1277 					{
1278 						std::string message_instruments = get_message_instruments();
1279 						std::string message_samples = get_message_samples();
1280 						if ( !message_samples.empty() ) {
1281 							retval += std::move( message_samples );
1282 						}
1283 						if ( !message_instruments.empty() ) {
1284 							retval += std::move( message_instruments );
1285 						}
1286 					}
1287 					break;
1288 			}
1289 		}
1290 		return mod_string_to_utf8( retval );
1291 	} else if ( key == std::string("message_raw") ) {
1292 		std::string retval = m_sndFile->m_songMessage.GetFormatted( OpenMPT::SongMessage::leLF );
1293 		return mod_string_to_utf8( retval );
1294 	} else if ( key == std::string("warnings") ) {
1295 		std::string retval;
1296 		bool first = true;
1297 		for ( const auto & msg : m_loaderMessages ) {
1298 			if ( !first ) {
1299 				retval += "\n";
1300 			} else {
1301 				first = false;
1302 			}
1303 			retval += msg;
1304 		}
1305 		return retval;
1306 	}
1307 	return "";
1308 }
1309 
get_current_estimated_bpm() const1310 double module_impl::get_current_estimated_bpm() const {
1311 	return m_sndFile->GetCurrentBPM();
1312 }
get_current_speed() const1313 std::int32_t module_impl::get_current_speed() const {
1314 	return m_sndFile->m_PlayState.m_nMusicSpeed;
1315 }
get_current_tempo() const1316 std::int32_t module_impl::get_current_tempo() const {
1317 	return static_cast<std::int32_t>( m_sndFile->m_PlayState.m_nMusicTempo.GetInt() );
1318 }
get_current_order() const1319 std::int32_t module_impl::get_current_order() const {
1320 	return m_sndFile->GetCurrentOrder();
1321 }
get_current_pattern() const1322 std::int32_t module_impl::get_current_pattern() const {
1323 	std::int32_t order = m_sndFile->GetCurrentOrder();
1324 	if ( order < 0 || order >= m_sndFile->Order().GetLengthTailTrimmed() ) {
1325 		return m_sndFile->GetCurrentPattern();
1326 	}
1327 	std::int32_t pattern = m_sndFile->Order()[order];
1328 	if ( !m_sndFile->Patterns.IsValidIndex( static_cast<OpenMPT::PATTERNINDEX>( pattern ) ) ) {
1329 		return -1;
1330 	}
1331 	return pattern;
1332 }
get_current_row() const1333 std::int32_t module_impl::get_current_row() const {
1334 	return m_sndFile->m_PlayState.m_nRow;
1335 }
get_current_playing_channels() const1336 std::int32_t module_impl::get_current_playing_channels() const {
1337 	return m_sndFile->GetMixStat();
1338 }
1339 
get_current_channel_vu_mono(std::int32_t channel) const1340 float module_impl::get_current_channel_vu_mono( std::int32_t channel ) const {
1341 	if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
1342 		return 0.0f;
1343 	}
1344 	const float left = m_sndFile->m_PlayState.Chn[channel].nLeftVU * (1.0f/128.0f);
1345 	const float right = m_sndFile->m_PlayState.Chn[channel].nRightVU * (1.0f/128.0f);
1346 	return std::sqrt(left*left + right*right);
1347 }
get_current_channel_vu_left(std::int32_t channel) const1348 float module_impl::get_current_channel_vu_left( std::int32_t channel ) const {
1349 	if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
1350 		return 0.0f;
1351 	}
1352 	return m_sndFile->m_PlayState.Chn[channel].dwFlags[OpenMPT::CHN_SURROUND] ? 0.0f : m_sndFile->m_PlayState.Chn[channel].nLeftVU * (1.0f/128.0f);
1353 }
get_current_channel_vu_right(std::int32_t channel) const1354 float module_impl::get_current_channel_vu_right( std::int32_t channel ) const {
1355 	if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
1356 		return 0.0f;
1357 	}
1358 	return m_sndFile->m_PlayState.Chn[channel].dwFlags[OpenMPT::CHN_SURROUND] ? 0.0f : m_sndFile->m_PlayState.Chn[channel].nRightVU * (1.0f/128.0f);
1359 }
get_current_channel_vu_rear_left(std::int32_t channel) const1360 float module_impl::get_current_channel_vu_rear_left( std::int32_t channel ) const {
1361 	if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
1362 		return 0.0f;
1363 	}
1364 	return m_sndFile->m_PlayState.Chn[channel].dwFlags[OpenMPT::CHN_SURROUND] ? m_sndFile->m_PlayState.Chn[channel].nLeftVU * (1.0f/128.0f) : 0.0f;
1365 }
get_current_channel_vu_rear_right(std::int32_t channel) const1366 float module_impl::get_current_channel_vu_rear_right( std::int32_t channel ) const {
1367 	if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
1368 		return 0.0f;
1369 	}
1370 	return m_sndFile->m_PlayState.Chn[channel].dwFlags[OpenMPT::CHN_SURROUND] ? m_sndFile->m_PlayState.Chn[channel].nRightVU * (1.0f/128.0f) : 0.0f;
1371 }
1372 
get_num_subsongs() const1373 std::int32_t module_impl::get_num_subsongs() const {
1374 	std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ?  std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
1375 	const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
1376 	return static_cast<std::int32_t>( subsongs.size() );
1377 }
get_num_channels() const1378 std::int32_t module_impl::get_num_channels() const {
1379 	return m_sndFile->GetNumChannels();
1380 }
get_num_orders() const1381 std::int32_t module_impl::get_num_orders() const {
1382 	return m_sndFile->Order().GetLengthTailTrimmed();
1383 }
get_num_patterns() const1384 std::int32_t module_impl::get_num_patterns() const {
1385 	return m_sndFile->Patterns.GetNumPatterns();
1386 }
get_num_instruments() const1387 std::int32_t module_impl::get_num_instruments() const {
1388 	return m_sndFile->GetNumInstruments();
1389 }
get_num_samples() const1390 std::int32_t module_impl::get_num_samples() const {
1391 	return m_sndFile->GetNumSamples();
1392 }
1393 
get_subsong_names() const1394 std::vector<std::string> module_impl::get_subsong_names() const {
1395 	std::vector<std::string> retval;
1396 	std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ?  std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
1397 	const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
1398 	retval.reserve( subsongs.size() );
1399 	for ( const auto & subsong : subsongs ) {
1400 		const auto & order = m_sndFile->Order( static_cast<OpenMPT::SEQUENCEINDEX>( subsong.sequence ) );
1401 		retval.push_back( mpt::transcode<std::string>( mpt::common_encoding::utf8, order.GetName() ) );
1402 		if ( retval.back().empty() ) {
1403 			// use first pattern name instead
1404 			if ( order.IsValidPat( static_cast<OpenMPT::SEQUENCEINDEX>( subsong.start_order ) ) )
1405 				retval.back() = OpenMPT::mpt::ToCharset( OpenMPT::mpt::Charset::UTF8, m_sndFile->GetCharsetInternal(), m_sndFile->Patterns[ order[ subsong.start_order ] ].GetName() );
1406 		}
1407 	}
1408 	return retval;
1409 }
get_channel_names() const1410 std::vector<std::string> module_impl::get_channel_names() const {
1411 	std::vector<std::string> retval;
1412 	for ( OpenMPT::CHANNELINDEX i = 0; i < m_sndFile->GetNumChannels(); ++i ) {
1413 		retval.push_back( mod_string_to_utf8( m_sndFile->ChnSettings[i].szName ) );
1414 	}
1415 	return retval;
1416 }
get_order_names() const1417 std::vector<std::string> module_impl::get_order_names() const {
1418 	std::vector<std::string> retval;
1419 	OpenMPT::ORDERINDEX num_orders = m_sndFile->Order().GetLengthTailTrimmed();
1420 	retval.reserve( num_orders );
1421 	for ( OpenMPT::ORDERINDEX i = 0; i < num_orders; ++i ) {
1422 		OpenMPT::PATTERNINDEX pat = m_sndFile->Order()[i];
1423 		if ( m_sndFile->Patterns.IsValidIndex( pat ) ) {
1424 			retval.push_back( mod_string_to_utf8( m_sndFile->Patterns[ m_sndFile->Order()[i] ].GetName() ) );
1425 		} else {
1426 			if ( pat == m_sndFile->Order.GetIgnoreIndex() ) {
1427 				retval.push_back( "+++ skip" );
1428 			} else if ( pat == m_sndFile->Order.GetInvalidPatIndex() ) {
1429 				retval.push_back( "--- stop" );
1430 			} else {
1431 				retval.push_back( "???" );
1432 			}
1433 		}
1434 	}
1435 	return retval;
1436 }
get_pattern_names() const1437 std::vector<std::string> module_impl::get_pattern_names() const {
1438 	std::vector<std::string> retval;
1439 	retval.reserve( m_sndFile->Patterns.GetNumPatterns() );
1440 	for ( OpenMPT::PATTERNINDEX i = 0; i < m_sndFile->Patterns.GetNumPatterns(); ++i ) {
1441 		retval.push_back( mod_string_to_utf8( m_sndFile->Patterns[i].GetName() ) );
1442 	}
1443 	return retval;
1444 }
get_instrument_names() const1445 std::vector<std::string> module_impl::get_instrument_names() const {
1446 	std::vector<std::string> retval;
1447 	retval.reserve( m_sndFile->GetNumInstruments() );
1448 	for ( OpenMPT::INSTRUMENTINDEX i = 1; i <= m_sndFile->GetNumInstruments(); ++i ) {
1449 		retval.push_back( mod_string_to_utf8( m_sndFile->GetInstrumentName( i ) ) );
1450 	}
1451 	return retval;
1452 }
get_sample_names() const1453 std::vector<std::string> module_impl::get_sample_names() const {
1454 	std::vector<std::string> retval;
1455 	retval.reserve( m_sndFile->GetNumSamples() );
1456 	for ( OpenMPT::SAMPLEINDEX i = 1; i <= m_sndFile->GetNumSamples(); ++i ) {
1457 		retval.push_back( mod_string_to_utf8( m_sndFile->GetSampleName( i ) ) );
1458 	}
1459 	return retval;
1460 }
1461 
get_order_pattern(std::int32_t o) const1462 std::int32_t module_impl::get_order_pattern( std::int32_t o ) const {
1463 	if ( o < 0 || o >= m_sndFile->Order().GetLengthTailTrimmed() ) {
1464 		return -1;
1465 	}
1466 	return m_sndFile->Order()[o];
1467 }
get_pattern_num_rows(std::int32_t p) const1468 std::int32_t module_impl::get_pattern_num_rows( std::int32_t p ) const {
1469 	if ( !mpt::is_in_range( p, std::numeric_limits<OpenMPT::PATTERNINDEX>::min(), std::numeric_limits<OpenMPT::PATTERNINDEX>::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast<OpenMPT::PATTERNINDEX>( p ) ) ) {
1470 		return 0;
1471 	}
1472 	return m_sndFile->Patterns[p].GetNumRows();
1473 }
1474 
get_pattern_row_channel_command(std::int32_t p,std::int32_t r,std::int32_t c,int cmd) const1475 std::uint8_t module_impl::get_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const {
1476 	if ( !mpt::is_in_range( p, std::numeric_limits<OpenMPT::PATTERNINDEX>::min(), std::numeric_limits<OpenMPT::PATTERNINDEX>::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast<OpenMPT::PATTERNINDEX>( p ) ) ) {
1477 		return 0;
1478 	}
1479 	const OpenMPT::CPattern & pattern = m_sndFile->Patterns[p];
1480 	if ( r < 0 || r >= static_cast<std::int32_t>( pattern.GetNumRows() ) ) {
1481 		return 0;
1482 	}
1483 	if ( c < 0 || c >= m_sndFile->GetNumChannels() ) {
1484 		return 0;
1485 	}
1486 	if ( cmd < module::command_note || cmd > module::command_parameter ) {
1487 		return 0;
1488 	}
1489 	const OpenMPT::ModCommand & cell = *pattern.GetpModCommand( static_cast<OpenMPT::ROWINDEX>( r ), static_cast<OpenMPT::CHANNELINDEX>( c ) );
1490 	switch ( cmd ) {
1491 		case module::command_note: return cell.note; break;
1492 		case module::command_instrument: return cell.instr; break;
1493 		case module::command_volumeffect: return cell.volcmd; break;
1494 		case module::command_effect: return cell.command; break;
1495 		case module::command_volume: return cell.vol; break;
1496 		case module::command_parameter: return cell.param; break;
1497 	}
1498 	return 0;
1499 }
1500 
1501 /*
1502 
1503 highlight chars explained:
1504 
1505   : empty/space
1506 . : empty/dot
1507 n : generic note
1508 m : special note
1509 i : generic instrument
1510 u : generic volume column effect
1511 v : generic volume column parameter
1512 e : generic effect column effect
1513 f : generic effect column parameter
1514 
1515 */
1516 
format_and_highlight_pattern_row_channel_command(std::int32_t p,std::int32_t r,std::int32_t c,int cmd) const1517 std::pair< std::string, std::string > module_impl::format_and_highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const {
1518 	if ( !mpt::is_in_range( p, std::numeric_limits<OpenMPT::PATTERNINDEX>::min(), std::numeric_limits<OpenMPT::PATTERNINDEX>::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast<OpenMPT::PATTERNINDEX>( p ) ) ) {
1519 		return std::make_pair( std::string(), std::string() );
1520 	}
1521 	const OpenMPT::CPattern & pattern = m_sndFile->Patterns[p];
1522 	if ( r < 0 || r >= static_cast<std::int32_t>( pattern.GetNumRows() ) ) {
1523 		return std::make_pair( std::string(), std::string() );
1524 	}
1525 	if ( c < 0 || c >= m_sndFile->GetNumChannels() ) {
1526 		return std::make_pair( std::string(), std::string() );
1527 	}
1528 	if ( cmd < module::command_note || cmd > module::command_parameter ) {
1529 		return std::make_pair( std::string(), std::string() );
1530 	}
1531 	const OpenMPT::ModCommand & cell = *pattern.GetpModCommand( static_cast<OpenMPT::ROWINDEX>( r ), static_cast<OpenMPT::CHANNELINDEX>( c ) );
1532 	// clang-format off
1533 	switch ( cmd ) {
1534 		case module::command_note:
1535 			return std::make_pair(
1536 					( cell.IsNote() || cell.IsSpecialNote() ) ? mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->GetNoteName( cell.note, cell.instr ) ) : std::string("...")
1537 				,
1538 					( cell.IsNote() ) ? std::string("nnn") : cell.IsSpecialNote() ? std::string("mmm") : std::string("...")
1539 				);
1540 			break;
1541 		case module::command_instrument:
1542 			return std::make_pair(
1543 					cell.instr ? OpenMPT::mpt::afmt::HEX0<2>( cell.instr ) : std::string("..")
1544 				,
1545 					cell.instr ? std::string("ii") : std::string("..")
1546 				);
1547 			break;
1548 		case module::command_volumeffect:
1549 			return std::make_pair(
1550 					cell.IsPcNote() ? std::string(" ") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetVolEffectLetter( cell.volcmd ) ) : std::string(" ")
1551 				,
1552 					cell.IsPcNote() ? std::string(" ") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string("u") : std::string(" ")
1553 				);
1554 			break;
1555 		case module::command_volume:
1556 			return std::make_pair(
1557 					cell.IsPcNote() ? OpenMPT::mpt::afmt::HEX0<2>( cell.GetValueVolCol() & 0xff ) : cell.volcmd != OpenMPT::VOLCMD_NONE ? OpenMPT::mpt::afmt::HEX0<2>( cell.vol ) : std::string("..")
1558 				,
1559 					cell.IsPcNote() ? std::string("vv") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string("vv") : std::string("..")
1560 				);
1561 			break;
1562 		case module::command_effect:
1563 			return std::make_pair(
1564 					cell.IsPcNote() ? OpenMPT::mpt::afmt::HEX0<1>( ( cell.GetValueEffectCol() & 0x0f00 ) > 16 ) : cell.command != OpenMPT::CMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetEffectLetter( cell.command ) ) : std::string(".")
1565 				,
1566 					cell.IsPcNote() ? std::string("e") : cell.command != OpenMPT::CMD_NONE ? std::string("e") : std::string(".")
1567 				);
1568 			break;
1569 		case module::command_parameter:
1570 			return std::make_pair(
1571 					cell.IsPcNote() ? OpenMPT::mpt::afmt::HEX0<2>( cell.GetValueEffectCol() & 0x00ff ) : cell.command != OpenMPT::CMD_NONE ? OpenMPT::mpt::afmt::HEX0<2>( cell.param ) : std::string("..")
1572 				,
1573 					cell.IsPcNote() ? std::string("ff") : cell.command != OpenMPT::CMD_NONE ? std::string("ff") : std::string("..")
1574 				);
1575 			break;
1576 	}
1577 	// clang-format on
1578 	return std::make_pair( std::string(), std::string() );
1579 }
format_pattern_row_channel_command(std::int32_t p,std::int32_t r,std::int32_t c,int cmd) const1580 std::string module_impl::format_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const {
1581 	return format_and_highlight_pattern_row_channel_command( p, r, c, cmd ).first;
1582 }
highlight_pattern_row_channel_command(std::int32_t p,std::int32_t r,std::int32_t c,int cmd) const1583 std::string module_impl::highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const {
1584 	return format_and_highlight_pattern_row_channel_command( p, r, c, cmd ).second;
1585 }
1586 
format_and_highlight_pattern_row_channel(std::int32_t p,std::int32_t r,std::int32_t c,std::size_t width,bool pad) const1587 std::pair< std::string, std::string > module_impl::format_and_highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const {
1588 	std::string text = pad ? std::string( width, ' ' ) : std::string();
1589 	std::string high = pad ? std::string( width, ' ' ) : std::string();
1590 	if ( !mpt::is_in_range( p, std::numeric_limits<OpenMPT::PATTERNINDEX>::min(), std::numeric_limits<OpenMPT::PATTERNINDEX>::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast<OpenMPT::PATTERNINDEX>( p ) ) ) {
1591 		return std::make_pair( text, high );
1592 	}
1593 	const OpenMPT::CPattern & pattern = m_sndFile->Patterns[p];
1594 	if ( r < 0 || r >= static_cast<std::int32_t>( pattern.GetNumRows() ) ) {
1595 		return std::make_pair( text, high );
1596 	}
1597 	if ( c < 0 || c >= m_sndFile->GetNumChannels() ) {
1598 		return std::make_pair( text, high );
1599 	}
1600 	//  0000000001111
1601 	//  1234567890123
1602 	// "NNN IIvVV EFF"
1603 	const OpenMPT::ModCommand & cell = *pattern.GetpModCommand( static_cast<OpenMPT::ROWINDEX>( r ), static_cast<OpenMPT::CHANNELINDEX>( c ) );
1604 	text.clear();
1605 	high.clear();
1606 	// clang-format off
1607 	text += ( cell.IsNote() || cell.IsSpecialNote() ) ? mpt::transcode<std::string>( mpt::common_encoding::utf8, m_sndFile->GetNoteName( cell.note, cell.instr ) ) : std::string("...");
1608 	high += ( cell.IsNote() ) ? std::string("nnn") : cell.IsSpecialNote() ? std::string("mmm") : std::string("...");
1609 	if ( ( width == 0 ) || ( width >= 6 ) ) {
1610 		text += std::string(" ");
1611 		high += std::string(" ");
1612 		text += cell.instr ? OpenMPT::mpt::afmt::HEX0<2>( cell.instr ) : std::string("..");
1613 		high += cell.instr ? std::string("ii") : std::string("..");
1614 	}
1615 	if ( ( width == 0 ) || ( width >= 9 ) ) {
1616 		text += cell.IsPcNote() ? std::string(" ") + OpenMPT::mpt::afmt::HEX0<2>( cell.GetValueVolCol() & 0xff ) : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetVolEffectLetter( cell.volcmd ) ) + OpenMPT::mpt::afmt::HEX0<2>( cell.vol ) : std::string(" ..");
1617 		high += cell.IsPcNote() ? std::string(" vv") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string("uvv") : std::string(" ..");
1618 	}
1619 	if ( ( width == 0 ) || ( width >= 13 ) ) {
1620 		text += std::string(" ");
1621 		high += std::string(" ");
1622 		text += cell.IsPcNote() ? OpenMPT::mpt::afmt::HEX0<3>( cell.GetValueEffectCol() & 0x0fff ) : cell.command != OpenMPT::CMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetEffectLetter( cell.command ) ) + OpenMPT::mpt::afmt::HEX0<2>( cell.param ) : std::string("...");
1623 		high += cell.IsPcNote() ? std::string("eff") : cell.command != OpenMPT::CMD_NONE ? std::string("eff") : std::string("...");
1624 	}
1625 	if ( ( width != 0 ) && ( text.length() > width ) ) {
1626 		text = text.substr( 0, width );
1627 	} else if ( ( width != 0 ) && pad ) {
1628 		text += std::string( width - text.length(), ' ' );
1629 	}
1630 	if ( ( width != 0 ) && ( high.length() > width ) ) {
1631 		high = high.substr( 0, width );
1632 	} else if ( ( width != 0 ) && pad ) {
1633 		high += std::string( width - high.length(), ' ' );
1634 	}
1635 	// clang-format on
1636 	return std::make_pair( text, high );
1637 }
format_pattern_row_channel(std::int32_t p,std::int32_t r,std::int32_t c,std::size_t width,bool pad) const1638 std::string module_impl::format_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const {
1639 	return format_and_highlight_pattern_row_channel( p, r, c, width, pad ).first;
1640 }
highlight_pattern_row_channel(std::int32_t p,std::int32_t r,std::int32_t c,std::size_t width,bool pad) const1641 std::string module_impl::highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const {
1642 	return format_and_highlight_pattern_row_channel( p, r, c, width, pad ).second;
1643 }
1644 
get_ctl_infos() const1645 std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> module_impl::get_ctl_infos() const {
1646 	static constexpr ctl_info ctl_infos[] = {
1647 		{ "load.skip_samples", ctl_type::boolean },
1648 		{ "load.skip_patterns", ctl_type::boolean },
1649 		{ "load.skip_plugins", ctl_type::boolean },
1650 		{ "load.skip_subsongs_init", ctl_type::boolean },
1651 		{ "seek.sync_samples", ctl_type::boolean },
1652 		{ "subsong", ctl_type::integer },
1653 		{ "play.tempo_factor", ctl_type::floatingpoint },
1654 		{ "play.pitch_factor", ctl_type::floatingpoint },
1655 		{ "play.at_end", ctl_type::text },
1656 		{ "render.resampler.emulate_amiga", ctl_type::boolean },
1657 		{ "render.resampler.emulate_amiga_type", ctl_type::text },
1658 		{ "render.opl.volume_factor", ctl_type::floatingpoint },
1659 		{ "dither", ctl_type::integer }
1660 	};
1661 	return std::make_pair(std::begin(ctl_infos), std::end(ctl_infos));
1662 }
1663 
get_ctls() const1664 std::vector<std::string> module_impl::get_ctls() const {
1665 	std::vector<std::string> result;
1666 	auto ctl_infos = get_ctl_infos();
1667 	result.reserve(std::distance(ctl_infos.first, ctl_infos.second));
1668 	for ( std::ptrdiff_t i = 0; i < std::distance(ctl_infos.first, ctl_infos.second); ++i ) {
1669 		result.push_back(ctl_infos.first[i].name);
1670 	}
1671 	return result;
1672 }
1673 
ctl_get(std::string ctl,bool throw_if_unknown) const1674 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
1675 	if ( !ctl.empty() ) {
1676 		// cppcheck false-positive
1677 		// cppcheck-suppress containerOutOfBounds
1678 		char rightmost = ctl.back();
1679 		if ( rightmost == '!' || rightmost == '?' ) {
1680 			if ( rightmost == '!' ) {
1681 				throw_if_unknown = true;
1682 			} else if ( rightmost == '?' ) {
1683 				throw_if_unknown = false;
1684 			}
1685 			ctl = ctl.substr( 0, ctl.length() - 1 );
1686 		}
1687 	}
1688 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1689 	if ( found_ctl == get_ctl_infos().second ) {
1690 		if ( ctl == "" ) {
1691 			throw openmpt::exception("empty ctl");
1692 		} else if ( throw_if_unknown ) {
1693 			throw openmpt::exception("unknown ctl: " + ctl);
1694 		} else {
1695 			return std::string();
1696 		}
1697 	}
1698 	std::string result;
1699 	switch ( found_ctl->type ) {
1700 		case ctl_type::boolean:
1701 			return mpt::format_value_default<std::string>( ctl_get_boolean( ctl, throw_if_unknown ) );
1702 			break;
1703 		case ctl_type::integer:
1704 			return mpt::format_value_default<std::string>( ctl_get_integer( ctl, throw_if_unknown ) );
1705 			break;
1706 		case ctl_type::floatingpoint:
1707 			return mpt::format_value_default<std::string>( ctl_get_floatingpoint( ctl, throw_if_unknown ) );
1708 			break;
1709 		case ctl_type::text:
1710 			return ctl_get_text( ctl, throw_if_unknown );
1711 			break;
1712 	}
1713 	return result;
1714 }
ctl_get_boolean(std::string_view ctl,bool throw_if_unknown) const1715 bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
1716 	if ( !ctl.empty() ) {
1717 		// cppcheck false-positive
1718 		// cppcheck-suppress containerOutOfBounds
1719 		char rightmost = ctl.back();
1720 		if ( rightmost == '!' || rightmost == '?' ) {
1721 			if ( rightmost == '!' ) {
1722 				throw_if_unknown = true;
1723 			} else if ( rightmost == '?' ) {
1724 				throw_if_unknown = false;
1725 			}
1726 			ctl = ctl.substr( 0, ctl.length() - 1 );
1727 		}
1728 	}
1729 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1730 	if ( found_ctl == get_ctl_infos().second ) {
1731 		if ( ctl == "" ) {
1732 			throw openmpt::exception("empty ctl");
1733 		} else if ( throw_if_unknown ) {
1734 			throw openmpt::exception("unknown ctl: " + std::string(ctl));
1735 		} else {
1736 			return false;
1737 		}
1738 	}
1739 	if ( found_ctl->type != ctl_type::boolean ) {
1740 		throw openmpt::exception("wrong ctl value type");
1741 	}
1742 	if ( ctl == "" ) {
1743 		throw openmpt::exception("empty ctl");
1744 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
1745 		return m_ctl_load_skip_samples;
1746 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
1747 		return m_ctl_load_skip_patterns;
1748 	} else if ( ctl == "load.skip_plugins" ) {
1749 		return m_ctl_load_skip_plugins;
1750 	} else if ( ctl == "load.skip_subsongs_init" ) {
1751 		return m_ctl_load_skip_subsongs_init;
1752 	} else if ( ctl == "seek.sync_samples" ) {
1753 		return m_ctl_seek_sync_samples;
1754 	} else if ( ctl == "render.resampler.emulate_amiga" ) {
1755 		return ( m_sndFile->m_Resampler.m_Settings.emulateAmiga != OpenMPT::Resampling::AmigaFilter::Off );
1756 	} else {
1757 		MPT_ASSERT_NOTREACHED();
1758 		return false;
1759 	}
1760 }
ctl_get_integer(std::string_view ctl,bool throw_if_unknown) const1761 std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
1762 	if ( !ctl.empty() ) {
1763 		// cppcheck false-positive
1764 		// cppcheck-suppress containerOutOfBounds
1765 		char rightmost = ctl.back();
1766 		if ( rightmost == '!' || rightmost == '?' ) {
1767 			if ( rightmost == '!' ) {
1768 				throw_if_unknown = true;
1769 			} else if ( rightmost == '?' ) {
1770 				throw_if_unknown = false;
1771 			}
1772 			ctl = ctl.substr( 0, ctl.length() - 1 );
1773 		}
1774 	}
1775 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1776 	if ( found_ctl == get_ctl_infos().second ) {
1777 		if ( ctl == "" ) {
1778 			throw openmpt::exception("empty ctl");
1779 		} else if ( throw_if_unknown ) {
1780 			throw openmpt::exception("unknown ctl: " + std::string(ctl));
1781 		} else {
1782 			return 0;
1783 		}
1784 	}
1785 	if ( found_ctl->type != ctl_type::integer ) {
1786 		throw openmpt::exception("wrong ctl value type");
1787 	}
1788 	if ( ctl == "" ) {
1789 		throw openmpt::exception("empty ctl");
1790 	} else if ( ctl == "subsong" ) {
1791 		return get_selected_subsong();
1792 	} else if ( ctl == "dither" ) {
1793 		return static_cast<std::int64_t>( m_Dithers->GetMode() );
1794 	} else {
1795 		MPT_ASSERT_NOTREACHED();
1796 		return 0;
1797 	}
1798 }
ctl_get_floatingpoint(std::string_view ctl,bool throw_if_unknown) const1799 double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
1800 	if ( !ctl.empty() ) {
1801 		// cppcheck false-positive
1802 		// cppcheck-suppress containerOutOfBounds
1803 		char rightmost = ctl.back();
1804 		if ( rightmost == '!' || rightmost == '?' ) {
1805 			if ( rightmost == '!' ) {
1806 				throw_if_unknown = true;
1807 			} else if ( rightmost == '?' ) {
1808 				throw_if_unknown = false;
1809 			}
1810 			ctl = ctl.substr( 0, ctl.length() - 1 );
1811 		}
1812 	}
1813 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1814 	if ( found_ctl == get_ctl_infos().second ) {
1815 		if ( ctl == "" ) {
1816 			throw openmpt::exception("empty ctl");
1817 		} else if ( throw_if_unknown ) {
1818 			throw openmpt::exception("unknown ctl: " + std::string(ctl));
1819 		} else {
1820 			return 0.0;
1821 		}
1822 	}
1823 	if ( found_ctl->type != ctl_type::floatingpoint ) {
1824 		throw openmpt::exception("wrong ctl value type");
1825 	}
1826 	if ( ctl == "" ) {
1827 		throw openmpt::exception("empty ctl");
1828 	} else if ( ctl == "play.tempo_factor" ) {
1829 		if ( !is_loaded() ) {
1830 			return 1.0;
1831 		}
1832 		return 65536.0 / m_sndFile->m_nTempoFactor;
1833 	} else if ( ctl == "play.pitch_factor" ) {
1834 		if ( !is_loaded() ) {
1835 			return 1.0;
1836 		}
1837 		return m_sndFile->m_nFreqFactor / 65536.0;
1838 	} else if ( ctl == "render.opl.volume_factor" ) {
1839 		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( OpenMPT::CSoundFile::m_OPLVolumeFactorScale );
1840 	} else {
1841 		MPT_ASSERT_NOTREACHED();
1842 		return 0.0;
1843 	}
1844 }
ctl_get_text(std::string_view ctl,bool throw_if_unknown) const1845 std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
1846 	if ( !ctl.empty() ) {
1847 		// cppcheck false-positive
1848 		// cppcheck-suppress containerOutOfBounds
1849 		char rightmost = ctl.back();
1850 		if ( rightmost == '!' || rightmost == '?' ) {
1851 			if ( rightmost == '!' ) {
1852 				throw_if_unknown = true;
1853 			} else if ( rightmost == '?' ) {
1854 				throw_if_unknown = false;
1855 			}
1856 			ctl = ctl.substr( 0, ctl.length() - 1 );
1857 		}
1858 	}
1859 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1860 	if ( found_ctl == get_ctl_infos().second ) {
1861 		if ( ctl == "" ) {
1862 			throw openmpt::exception("empty ctl");
1863 		} else if ( throw_if_unknown ) {
1864 			throw openmpt::exception("unknown ctl: " + std::string(ctl));
1865 		} else {
1866 			return std::string();
1867 		}
1868 	}
1869 	if ( ctl == "" ) {
1870 		throw openmpt::exception("empty ctl");
1871 	} else if ( ctl == "play.at_end" ) {
1872 		switch ( m_ctl_play_at_end )
1873 		{
1874 		case song_end_action::fadeout_song:
1875 			return "fadeout";
1876 		case song_end_action::continue_song:
1877 			return "continue";
1878 		case song_end_action::stop_song:
1879 			return "stop";
1880 		default:
1881 			return std::string();
1882 		}
1883 	} else if ( ctl == "render.resampler.emulate_amiga_type" ) {
1884 		switch ( m_ctl_render_resampler_emulate_amiga_type ) {
1885 			case amiga_filter_type::a500:
1886 				return "a500";
1887 			case amiga_filter_type::a1200:
1888 				return "a1200";
1889 			case amiga_filter_type::unfiltered:
1890 				return "unfiltered";
1891 			case amiga_filter_type::auto_filter:
1892 				return "auto";
1893 			default:
1894 				return std::string();
1895 		}
1896 	} else {
1897 		MPT_ASSERT_NOTREACHED();
1898 		return std::string();
1899 	}
1900 }
1901 
ctl_set(std::string ctl,const std::string & value,bool throw_if_unknown)1902 void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
1903 	if ( !ctl.empty() ) {
1904 		// cppcheck false-positive
1905 		// cppcheck-suppress containerOutOfBounds
1906 		char rightmost = ctl.back();
1907 		if ( rightmost == '!' || rightmost == '?' ) {
1908 			if ( rightmost == '!' ) {
1909 				throw_if_unknown = true;
1910 			} else if ( rightmost == '?' ) {
1911 				throw_if_unknown = false;
1912 			}
1913 			ctl = ctl.substr( 0, ctl.length() - 1 );
1914 		}
1915 	}
1916 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1917 	if ( found_ctl == get_ctl_infos().second ) {
1918 		if ( ctl == "" ) {
1919 			throw openmpt::exception("empty ctl: := " + value);
1920 		} else if ( throw_if_unknown ) {
1921 			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
1922 		} else {
1923 			return;
1924 		}
1925 	}
1926 	switch ( found_ctl->type ) {
1927 		case ctl_type::boolean:
1928 			ctl_set_boolean( ctl, mpt::ConvertStringTo<bool>( value ), throw_if_unknown );
1929 			break;
1930 		case ctl_type::integer:
1931 			ctl_set_integer( ctl, mpt::ConvertStringTo<std::int64_t>( value ), throw_if_unknown );
1932 			break;
1933 		case ctl_type::floatingpoint:
1934 			ctl_set_floatingpoint( ctl, mpt::ConvertStringTo<double>( value ), throw_if_unknown );
1935 			break;
1936 		case ctl_type::text:
1937 			ctl_set_text( ctl, value, throw_if_unknown );
1938 			break;
1939 	}
1940 }
ctl_set_boolean(std::string_view ctl,bool value,bool throw_if_unknown)1941 void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
1942 	if ( !ctl.empty() ) {
1943 		// cppcheck false-positive
1944 		// cppcheck-suppress containerOutOfBounds
1945 		char rightmost = ctl.back();
1946 		if ( rightmost == '!' || rightmost == '?' ) {
1947 			if ( rightmost == '!' ) {
1948 				throw_if_unknown = true;
1949 			} else if ( rightmost == '?' ) {
1950 				throw_if_unknown = false;
1951 			}
1952 			ctl = ctl.substr( 0, ctl.length() - 1 );
1953 		}
1954 	}
1955 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
1956 	if ( found_ctl == get_ctl_infos().second ) {
1957 		if ( ctl == "" ) {
1958 			throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
1959 		} else if ( throw_if_unknown ) {
1960 			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::format_value_default<std::string>(value));
1961 		} else {
1962 			return;
1963 		}
1964 	}
1965 	if ( ctl == "" ) {
1966 		throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
1967 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
1968 		m_ctl_load_skip_samples = value;
1969 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
1970 		m_ctl_load_skip_patterns = value;
1971 	} else if ( ctl == "load.skip_plugins" ) {
1972 		m_ctl_load_skip_plugins = value;
1973 	} else if ( ctl == "load.skip_subsongs_init" ) {
1974 		m_ctl_load_skip_subsongs_init = value;
1975 	} else if ( ctl == "seek.sync_samples" ) {
1976 		m_ctl_seek_sync_samples = value;
1977 	} else if ( ctl == "render.resampler.emulate_amiga" ) {
1978 		OpenMPT::CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
1979 		const bool enabled = value;
1980 		if ( enabled )
1981 			newsettings.emulateAmiga = translate_amiga_filter_type( m_ctl_render_resampler_emulate_amiga_type );
1982 		else
1983 			newsettings.emulateAmiga = OpenMPT::Resampling::AmigaFilter::Off;
1984 		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
1985 			m_sndFile->SetResamplerSettings( newsettings );
1986 		}
1987 	} else {
1988 		MPT_ASSERT_NOTREACHED();
1989 	}
1990 }
ctl_set_integer(std::string_view ctl,std::int64_t value,bool throw_if_unknown)1991 void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
1992 	if ( !ctl.empty() ) {
1993 		// cppcheck false-positive
1994 		// cppcheck-suppress containerOutOfBounds
1995 		char rightmost = ctl.back();
1996 		if ( rightmost == '!' || rightmost == '?' ) {
1997 			if ( rightmost == '!' ) {
1998 				throw_if_unknown = true;
1999 			} else if ( rightmost == '?' ) {
2000 				throw_if_unknown = false;
2001 			}
2002 			ctl = ctl.substr( 0, ctl.length() - 1 );
2003 		}
2004 	}
2005 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
2006 	if ( found_ctl == get_ctl_infos().second ) {
2007 		if ( ctl == "" ) {
2008 			throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
2009 		} else if ( throw_if_unknown ) {
2010 			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::format_value_default<std::string>(value));
2011 		} else {
2012 			return;
2013 		}
2014 	}
2015 
2016 	if ( ctl == "" ) {
2017 		throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
2018 	} else if ( ctl == "subsong" ) {
2019 		select_subsong( mpt::saturate_cast<std::int32_t>( value ) );
2020 	} else if ( ctl == "dither" ) {
2021 		std::size_t dither = mpt::saturate_cast<std::size_t>( value );
2022 		if ( dither >= OpenMPT::DithersOpenMPT::GetNumDithers() ) {
2023 			dither = OpenMPT::DithersOpenMPT::GetDefaultDither();
2024 		}
2025 		m_Dithers->SetMode( dither );
2026 	} else {
2027 		MPT_ASSERT_NOTREACHED();
2028 	}
2029 }
ctl_set_floatingpoint(std::string_view ctl,double value,bool throw_if_unknown)2030 void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
2031 	if ( !ctl.empty() ) {
2032 		// cppcheck false-positive
2033 		// cppcheck-suppress containerOutOfBounds
2034 		char rightmost = ctl.back();
2035 		if ( rightmost == '!' || rightmost == '?' ) {
2036 			if ( rightmost == '!' ) {
2037 				throw_if_unknown = true;
2038 			} else if ( rightmost == '?' ) {
2039 				throw_if_unknown = false;
2040 			}
2041 			ctl = ctl.substr( 0, ctl.length() - 1 );
2042 		}
2043 	}
2044 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
2045 	if ( found_ctl == get_ctl_infos().second ) {
2046 		if ( ctl == "" ) {
2047 			throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
2048 		} else if ( throw_if_unknown ) {
2049 			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::format_value_default<std::string>(value));
2050 		} else {
2051 			return;
2052 		}
2053 	}
2054 
2055 	if ( ctl == "" ) {
2056 		throw openmpt::exception("empty ctl: := " + mpt::format_value_default<std::string>( value ) );
2057 	} else if ( ctl == "play.tempo_factor" ) {
2058 		if ( !is_loaded() ) {
2059 			return;
2060 		}
2061 		double factor = value;
2062 		if ( factor <= 0.0 || factor > 4.0 ) {
2063 			throw openmpt::exception("invalid tempo factor");
2064 		}
2065 		m_sndFile->m_nTempoFactor = mpt::saturate_round<uint32_t>( 65536.0 / factor );
2066 		m_sndFile->RecalculateSamplesPerTick();
2067 	} else if ( ctl == "play.pitch_factor" ) {
2068 		if ( !is_loaded() ) {
2069 			return;
2070 		}
2071 		double factor = value;
2072 		if ( factor <= 0.0 || factor > 4.0 ) {
2073 			throw openmpt::exception("invalid pitch factor");
2074 		}
2075 		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
2076 		m_sndFile->RecalculateSamplesPerTick();
2077 	} else if ( ctl == "render.opl.volume_factor" ) {
2078 		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<std::int32_t>( value * static_cast<double>( OpenMPT::CSoundFile::m_OPLVolumeFactorScale ) );
2079 	} else {
2080 		MPT_ASSERT_NOTREACHED();
2081 	}
2082 }
ctl_set_text(std::string_view ctl,std::string_view value,bool throw_if_unknown)2083 void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
2084 	if ( !ctl.empty() ) {
2085 		// cppcheck false-positive
2086 		// cppcheck-suppress containerOutOfBounds
2087 		char rightmost = ctl.back();
2088 		if ( rightmost == '!' || rightmost == '?' ) {
2089 			if ( rightmost == '!' ) {
2090 				throw_if_unknown = true;
2091 			} else if ( rightmost == '?' ) {
2092 				throw_if_unknown = false;
2093 			}
2094 			ctl = ctl.substr( 0, ctl.length() - 1 );
2095 		}
2096 	}
2097 	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
2098 	if ( found_ctl == get_ctl_infos().second ) {
2099 		if ( ctl == "" ) {
2100 			throw openmpt::exception("empty ctl: := " + std::string( value ) );
2101 		} else if ( throw_if_unknown ) {
2102 			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
2103 		} else {
2104 			return;
2105 		}
2106 	}
2107 
2108 	if ( ctl == "" ) {
2109 		throw openmpt::exception("empty ctl: := " + std::string( value ) );
2110 	} else if ( ctl == "play.at_end" ) {
2111 		if ( value == "fadeout" ) {
2112 			m_ctl_play_at_end = song_end_action::fadeout_song;
2113 		} else if(value == "continue") {
2114 			m_ctl_play_at_end = song_end_action::continue_song;
2115 		} else if(value == "stop") {
2116 			m_ctl_play_at_end = song_end_action::stop_song;
2117 		} else {
2118 			throw openmpt::exception("unknown song end action:" + std::string(value));
2119 		}
2120 	} else if ( ctl == "render.resampler.emulate_amiga_type" ) {
2121 		if ( value == "a500" ) {
2122 			m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::a500;
2123 		} else if ( value == "a1200" ) {
2124 			m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::a1200;
2125 		} else if ( value == "unfiltered" ) {
2126 			m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::unfiltered;
2127 		} else if ( value == "auto" ) {
2128 			m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::auto_filter;
2129 		} else {
2130 			throw openmpt::exception( "invalid amiga filter type" );
2131 		}
2132 		if ( m_sndFile->m_Resampler.m_Settings.emulateAmiga != OpenMPT::Resampling::AmigaFilter::Off ) {
2133 			OpenMPT::CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
2134 			newsettings.emulateAmiga = translate_amiga_filter_type( m_ctl_render_resampler_emulate_amiga_type );
2135 			if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
2136 				m_sndFile->SetResamplerSettings( newsettings );
2137 			}
2138 		}
2139 	} else {
2140 		MPT_ASSERT_NOTREACHED();
2141 	}
2142 }
2143 
2144 } // namespace openmpt
2145