View Issue Details

IDProjectCategoryView StatusLast Update
0001241OpenMPTlibopenmptpublic2019-12-19 09:09
Reportermanx Assigned Tomanx  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
Product VersionOpenMPT 1.29.00.* (old testing) 
Target VersionOpenMPT 1.29.01.00 / libopenmpt 0.5.0 (upgrade first)Fixed in VersionOpenMPT 1.29.01.00 / libopenmpt 0.5.0 (upgrade first) 
Summary0001241: std::string_view for ctl key, and non-string overloads for ctl_get and ctl_set
Description

Providing overloads for std::int64_t and double in addition to std::string avoids to/from string conversions for ctls that actually want a number type.

We should also consider using std::string_view for the ctl key.

TagsNo tags attached.
Attached Files
libopenmpt-api-cpp17-v3.patch (51,847 bytes)   
Index: common/mptString.cpp
===================================================================
--- common/mptString.cpp	(revision 12197)
+++ common/mptString.cpp	(working copy)
@@ -1793,6 +1793,28 @@
 	return a.length() < b.length() ? -1 : 1;
 }
 
+int CompareNoCaseAscii(std::string_view a, std::string_view b)
+{
+	for(std::size_t i = 0; i < std::min(a.length(), b.length()); ++i)
+	{
+		unsigned char ac = static_cast<unsigned char>(mpt::ToLowerCaseAscii(a[i]));
+		unsigned char bc = static_cast<unsigned char>(mpt::ToLowerCaseAscii(b[i]));
+		if(ac != bc)
+		{
+			return ac < bc ? -1 : 1;
+		} else if(!ac && !bc)
+		{
+			return 0;
+		}
+	}
+	if(a.length() == b.length())
+	{
+		return 0;
+	}
+	return a.length() < b.length() ? -1 : 1;
+}
+
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s)
Index: common/mptString.h
===================================================================
--- common/mptString.h	(revision 12197)
+++ common/mptString.h	(working copy)
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include <cstring>
 
@@ -525,6 +526,7 @@
 
 int CompareNoCaseAscii(const char *a, const char *b, std::size_t n);
 int CompareNoCaseAscii(const std::string &a, const std::string &b);
+int CompareNoCaseAscii(std::string_view a, std::string_view b);
 
 #if defined(MODPLUG_TRACKER)
 
Index: common/stdafx.h
===================================================================
--- common/stdafx.h	(revision 12197)
+++ common/stdafx.h	(working copy)
@@ -115,6 +115,7 @@
 // <algorithm>
 // <limits>
 // <string>
+// <string_view>
 // <type_traits>
 // <cstring>
 
Index: libopenmpt/dox/changelog.md
===================================================================
--- libopenmpt/dox/changelog.md	(revision 12197)
+++ libopenmpt/dox/changelog.md	(working copy)
@@ -15,9 +15,9 @@
  *  [**Regression**] foo_openmpt: foo_openmpt is discontinued. Please use
     Kode54's fork foo_openmpt54:
     <https://www.foobar2000.org/components/view/foo_openmpt54>.
- *  [**Regression**] Support for client code using C++11 has been removed. C++14
-    is now required to build libopenmpt client applications.
- *  [**Regression**] Support for building with C++11 and C++14 has been removed.
+ *  [**Regression**] Support for client code using C++11 or C++ 14 has been
+    removed. C++17 is now required to build libopenmpt client applications.
+ *  [**Regression**] Support for building with C++11 or C++14 has been removed.
     C++17 is now required to build libopenmpt.
  *  [**Regression**] Support for Visual Studio 2015 has been removed.
  *  [**Regression**] Support for GCC 4.8, 4.9, 5, 6 has been removed.
Index: libopenmpt/dox/dependencies.md
===================================================================
--- libopenmpt/dox/dependencies.md	(revision 12197)
+++ libopenmpt/dox/dependencies.md	(working copy)
@@ -48,8 +48,7 @@
  *  Required compilers to use libopenmpt:
      *  Any **C89** / **C99** / **C11** compatible compiler should work with
         the C API as long as a **C99** compatible **stdint.h** is available.
-     *  Any **C++14** / **C++17** compatible compiler should work with the C++
-        API.
+     *  Any **C++17** compatible compiler should work with the C++ API.
  *  **J2B** support requires an inflate (deflate decompression) implementation:
      *  **zlib**
      *  **miniz** can be used internally if no zlib is available.
Index: libopenmpt/libopenmpt.h
===================================================================
--- libopenmpt/libopenmpt.h	(revision 12197)
+++ libopenmpt/libopenmpt.h	(working copy)
@@ -1396,21 +1396,21 @@
  * \param mod The module handle to work on.
  * \return A semicolon-separated list containing all supported ctl keys.
  * \remarks Currently supported ctl values are:
- *          - load.skip_samples: Set to "1" to avoid loading samples into memory
- *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
- *          - load.skip_plugins: Set to "1" to avoid loading plugins
- *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
- *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
- *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
- *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *          - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong (integer): The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end (text): Chooses the behaviour when the end of song is reached:
  *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
  *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
  *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
- *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
- *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
- *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
- *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
- *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *          - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
  *                    - 0: No dithering.
  *                    - 1: Default mode. Chosen by OpenMPT code, might change.
  *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1417,6 +1417,7 @@
  *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
  */
 LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+
 /*! \brief Get current ctl value
  *
  * \param mod The module handle to work on.
@@ -1423,8 +1424,46 @@
  * \param ctl The ctl key whose value should be retrieved.
  * \return The associated ctl value, or NULL on failure.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_get_boolean(), openmpt_module_ctl_get_integer(), openmpt_module_ctl_get_floatingpoint(), or openmpt_module_ctl_get_text().
  */
-LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl );
+
 /*! \brief Set ctl value
  *
  * \param mod The module handle to work on.
@@ -1432,8 +1471,49 @@
  * \param value The value that should be set.
  * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_set_boolean(), openmpt_module_ctl_set_integer(), openmpt_module_ctl_set_floatingpoint(), or openmpt_module_ctl_set_text().
  */
-LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+/*! \brief Set ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value );
+/*! \brief Set ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value );
+/*! \brief Set ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value );
+/*! \brief Set ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value );
 
 /* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
 
Index: libopenmpt/libopenmpt.hpp
===================================================================
--- libopenmpt/libopenmpt.hpp	(revision 12197)
+++ libopenmpt/libopenmpt.hpp	(working copy)
@@ -17,6 +17,7 @@
 #include <iostream>
 #include <map>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <cstdint>
@@ -231,8 +232,16 @@
 /*!
   \param extension file extension to query without a leading dot. The case is ignored.
   \return true if the extension is supported by libopenmpt, false otherwise.
+  \deprecated Please use openmpt::is_extension_supported2().
 */
-LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+  \since 0.5.0
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported2( std::string_view extension );
 
 //! Roughly scan the input stream to find out whether libopenmpt might be able to open it
 /*!
@@ -962,21 +971,21 @@
 	/*!
 	  \return A vector containing all supported ctl keys.
 	  \remarks Currently supported ctl values are:
-	           - load.skip_samples: Set to "1" to avoid loading samples into memory
-	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
-	           - load.skip_plugins: Set to "1" to avoid loading plugins
-	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
-	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
-	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
-	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	           - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong (integer): The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end (text): Chooses the behaviour when the end of song is reached:
 	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
 	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
 	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
-	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
-	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
-	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
-	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
-	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	           - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
 	                     - 0: No dithering.
 	                     - 1: Default mode. Chosen by OpenMPT code, might change.
 	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -991,8 +1000,42 @@
 	  \param ctl The ctl key whose value should be retrieved.
 	  \return The associated ctl value.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_get_boolean(), openmpt::module::ctl_get_integer(), openmpt::module::ctl_get_floatingpoint(), or openmpt::module::ctl_get_text().
 	*/
-	std::string ctl_get( const std::string & ctl ) const;
+	LIBOPENMPT_ATTR_DEPRECATED std::string ctl_get( const std::string & ctl ) const;
+	//! Get current ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	bool ctl_get_boolean( std::string_view ctl ) const;
+	//! Get current ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::int64_t ctl_get_integer( std::string_view ctl ) const;
+	//! Get current ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	double ctl_get_floatingpoint( std::string_view ctl ) const;
+	//! Get current ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::string ctl_get_text( std::string_view ctl ) const;
+
 	//! Set ctl value
 	/*!
 	  \param ctl The ctl key whose value should be set.
@@ -999,8 +1042,45 @@
 	  \param value The value that should be set.
 	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_set_bool(), openmpt::module::ctl_set_int(), openmpt::module::ctl_set_float(), or openmpt::module::ctl_set_string().
 	*/
-	void ctl_set( const std::string & ctl, const std::string & value );
+	LIBOPENMPT_ATTR_DEPRECATED void ctl_set( const std::string & ctl, const std::string & value );
+	//! Set ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_boolean( std::string_view ctl, bool value );
+	//! Set ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_integer( std::string_view ctl, std::int64_t value );
+	//! Set ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_floatingpoint( std::string_view ctl, double value );
+	//! Set ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_text( std::string_view ctl, std::string_view value );
 
 	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
 
Index: libopenmpt/libopenmpt_c.cpp
===================================================================
--- libopenmpt/libopenmpt_c.cpp	(revision 12197)
+++ libopenmpt/libopenmpt_c.cpp	(working copy)
@@ -1278,6 +1278,46 @@
 	}
 	return NULL;
 }
+int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_boolean( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_integer( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_floatingpoint( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0.0;
+}
+const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return openmpt::strdup( mod->impl->ctl_get_text( ctl ).c_str() );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return NULL;
+}
 
 int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value ) {
 	try {
@@ -1291,8 +1331,52 @@
 	}
 	return 0;
 }
+int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_boolean( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_integer( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_floatingpoint( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		openmpt::interface::check_pointer( value );
+		mod->impl->ctl_set_text( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
 
-
 openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls ) {
 	try {
 		openmpt_module_ext * mod_ext = (openmpt_module_ext*)std::calloc( 1, sizeof( openmpt_module_ext ) );
Index: libopenmpt/libopenmpt_cxx.cpp
===================================================================
--- libopenmpt/libopenmpt_cxx.cpp	(revision 12197)
+++ libopenmpt/libopenmpt_cxx.cpp	(working copy)
@@ -126,6 +126,9 @@
 bool is_extension_supported( const std::string & extension ) {
 	return openmpt::module_impl::is_extension_supported( extension );
 }
+bool is_extension_supported( std::string_view extension ) {
+	return openmpt::module_impl::is_extension_supported( extension );
+}
 
 double could_open_probability( std::istream & stream, double effort, std::ostream & log ) {
 	return openmpt::module_impl::could_open_probability( stream, effort, openmpt::helper::make_unique<std_ostream_log>( log ) );
@@ -385,12 +388,38 @@
 std::vector<std::string> module::get_ctls() const {
 	return impl->get_ctls();
 }
+
 std::string module::ctl_get( const std::string & ctl ) const {
 	return impl->ctl_get( ctl );
 }
+bool module::ctl_get_boolean( std::string_view ctl ) const {
+	return impl->ctl_get_boolean( ctl );
+}
+std::int64_t module::ctl_get_integer( std::string_view ctl ) const {
+	return impl->ctl_get_integer( ctl );
+}
+double module::ctl_get_floatingpoint( std::string_view ctl ) const {
+	return impl->ctl_get_floatingpoint( ctl );
+}
+std::string module::ctl_get_text( std::string_view ctl ) const {
+	return impl->ctl_get_text( ctl );
+}
+
 void module::ctl_set( const std::string & ctl, const std::string & value ) {
 	impl->ctl_set( ctl, value );
 }
+void module::ctl_set_boolean( std::string_view ctl, bool value ) {
+	impl->ctl_set_boolean( ctl, value );
+}
+void module::ctl_set_integer( std::string_view ctl, std::int64_t value ) {
+	impl->ctl_set_integer( ctl, value );
+}
+void module::ctl_set_floatingpoint( std::string_view ctl, double value ) {
+	impl->ctl_set_floatingpoint( ctl, value );
+}
+void module::ctl_set_text( std::string_view ctl, std::string_view value ) {
+	impl->ctl_set_text( ctl, value );
+}
 
 module_ext::module_ext( std::istream & stream, std::ostream & log, const std::map< std::string, std::string > & ctls ) : ext_impl(0) {
 	ext_impl = new module_ext_impl( stream, openmpt::helper::make_unique<std_ostream_log>( log ), ctls );
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 12197)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -593,12 +593,9 @@
 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
 	return retval;
 }
-bool module_impl::is_extension_supported( const char * extension ) {
+bool module_impl::is_extension_supported( std::string_view extension ) {
 	return CSoundFile::IsExtensionSupported( extension );
 }
-bool module_impl::is_extension_supported( const std::string & extension ) {
-	return CSoundFile::IsExtensionSupported( extension.c_str() );
-}
 double module_impl::could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ) {
 	try {
 		if ( effort >= 0.8 ) {
@@ -1527,6 +1524,7 @@
 		"dither",
 	};
 }
+
 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1591,6 +1589,215 @@
 		}
 	}
 }
+bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		return m_ctl_load_skip_samples;
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		return m_ctl_load_skip_patterns;
+	} else if ( ctl == "load.skip_plugins" ) {
+		return m_ctl_load_skip_plugins;
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		return m_ctl_load_skip_subsongs_init;
+	} else if ( ctl == "seek.sync_samples" ) {
+		return m_ctl_seek_sync_samples;
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		return m_sndFile->m_Resampler.m_Settings.emulateAmiga;
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return false;
+		}
+	}
+}
+std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		return get_selected_subsong();
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		return static_cast<int>( m_Dither->GetMode() );
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0;
+		}
+	}
+}
+double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return 65536.0 / m_sndFile->m_nTempoFactor;
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return m_sndFile->m_nFreqFactor / 65536.0;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale );
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0.0;
+		}
+	}
+}
+std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		switch ( m_ctl_play_at_end )
+		{
+		case song_end_action::fadeout_song:
+			return "fadeout";
+		case song_end_action::continue_song:
+			return "continue";
+		case song_end_action::stop_song:
+			return "stop";
+		default:
+			return std::string();
+		}
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return std::string();
+		}
+	}
+}
+
 void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1671,5 +1878,229 @@
 		}
 	}
 }
+void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		m_ctl_load_skip_samples = value;
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		m_ctl_load_skip_patterns = value;
+	} else if ( ctl == "load.skip_plugins" ) {
+		m_ctl_load_skip_plugins = value;
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		m_ctl_load_skip_subsongs_init = value;
+	} else if ( ctl == "seek.sync_samples" ) {
+		m_ctl_seek_sync_samples = value;
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
+		newsettings.emulateAmiga = value;
+		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
+			m_sndFile->SetResamplerSettings( newsettings );
+		}
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		select_subsong( mpt::saturate_cast<int32>( value ) );
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		int dither = mpt::saturate_cast<int>( value );
+		if ( dither < 0 || dither >= NumDitherModes ) {
+			dither = DitherDefault;
+		}
+		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return;
+		}
+		double factor = value;
+		if ( factor <= 0.0 || factor > 4.0 ) {
+			throw openmpt::exception("invalid tempo factor");
+		}
+		m_sndFile->m_nTempoFactor = mpt::saturate_round<uint32_t>( 65536.0 / factor );
+		m_sndFile->RecalculateSamplesPerTick();
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return;
+		}
+		double factor = value;
+		if ( factor <= 0.0 || factor > 4.0 ) {
+			throw openmpt::exception("invalid pitch factor");
+		}
+		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
+		m_sndFile->RecalculateSamplesPerTick();
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + std::string( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		if ( value == "fadeout" ) {
+			m_ctl_play_at_end = song_end_action::fadeout_song;
+		} else if(value == "continue") {
+			m_ctl_play_at_end = song_end_action::continue_song;
+		} else if(value == "stop") {
+			m_ctl_play_at_end = song_end_action::stop_song;
+		} else {
+			throw openmpt::exception("unknown song end action:" + std::string(value));
+		}
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
+		} else {
+			// ignore
+		}
+	}
+}
 
 } // namespace openmpt
Index: libopenmpt/libopenmpt_impl.hpp
===================================================================
--- libopenmpt/libopenmpt_impl.hpp	(revision 12197)
+++ libopenmpt/libopenmpt_impl.hpp	(working copy)
@@ -130,8 +130,7 @@
 	static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log );
 public:
 	static std::vector<std::string> get_supported_extensions();
-	static bool is_extension_supported( const char * extension );
-	static bool is_extension_supported( const std::string & extension );
+	static bool is_extension_supported( std::string_view extension );
 	static double could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log );
 	static double could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log );
 	static std::size_t probe_file_header_get_recommended_size();
@@ -204,7 +203,15 @@
 	std::string highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
 	std::vector<std::string> get_ctls() const;
 	std::string ctl_get( std::string ctl, bool throw_if_unknown = true ) const;
+	bool ctl_get_boolean( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::int64_t ctl_get_integer( std::string_view ctl, bool throw_if_unknown = true ) const;
+	double ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::string ctl_get_text( std::string_view ctl, bool throw_if_unknown = true ) const;
 	void ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown = true );
+	void ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown = true );
+	void ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown = true );
+	void ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown = true );
+	void ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown = true );
 }; // class module_impl
 
 namespace helper {
Index: openmpt123/openmpt123.cpp
===================================================================
--- openmpt123/openmpt123.cpp	(revision 12197)
+++ openmpt123/openmpt123.cpp	(working copy)
@@ -711,33 +711,6 @@
 }
 
 
-template < typename T, typename Tmod >
-T ctl_get( Tmod & mod, const std::string & ctl ) {
-	T result = T();
-	try {
-		std::istringstream str;
-		str.imbue( std::locale::classic() );
-		str.str( mod.ctl_get( ctl ) );
-		str >> std::fixed >> std::setprecision(16) >> result;
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return result;
-}
-
-template < typename T, typename Tmod >
-void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
-	try {
-		std::ostringstream str;
-		str.imbue( std::locale::classic() );
-		str << std::fixed << std::setprecision(16) << val;
-		mod.ctl_set( ctl, str.str() );
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return;
-}
-
 template < typename Tmod >
 static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
 	flags.separation = std::max( flags.separation, std::int32_t(   0 ) );
@@ -753,12 +726,17 @@
 	mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
 	mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
 	mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
-	ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
-	ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
-	std::ostringstream dither_str;
-	dither_str.imbue( std::locale::classic() );
-	dither_str << flags.dither;
-	mod.ctl_set( "dither", dither_str.str() );
+	try {
+		mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	try {
+		mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	mod.ctl_set_integer( "dither", flags.dither );
 }
 
 struct prev_file { int count; prev_file( int c ) : count(c) { } };
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 12197)
+++ soundlib/Sndfile.h	(working copy)
@@ -823,7 +823,7 @@
 	bool ReadWAV(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
-	static bool IsExtensionSupported(const char *ext); // UTF8, casing of ext is ignored
+	static bool IsExtensionSupported(std::string_view ext); // UTF8, casing of ext is ignored
 	static mpt::ustring ModContainerTypeToString(MODCONTAINERTYPE containertype);
 	static mpt::ustring ModContainerTypeToTracker(MODCONTAINERTYPE containertype);
 
Index: soundlib/Tables.cpp
===================================================================
--- soundlib/Tables.cpp	(revision 12197)
+++ soundlib/Tables.cpp	(working copy)
@@ -176,21 +176,19 @@
 }
 
 
-static bool IsEqualExtension(const char *a, const char *b)
+static bool IsEqualExtension(std::string_view a, std::string_view b)
 {
-	std::size_t lena = std::strlen(a);
-	std::size_t lenb = std::strlen(b);
-	if(lena != lenb)
+	if(a.length() != b.length())
 	{
 		return false;
 	}
-	return mpt::CompareNoCaseAscii(a, b, lena) == 0;
+	return mpt::CompareNoCaseAscii(a, b) == 0;
 }
 
 
-bool CSoundFile::IsExtensionSupported(const char *ext)
+bool CSoundFile::IsExtensionSupported(std::string_view ext)
 {
-	if(ext == nullptr || ext[0] == 0)
+	if(ext.length() == 0)
 	{
 		return false;
 	}
libopenmpt-api-cpp17-v3.patch (51,847 bytes)   
libopenmpt-api-cpp17-v4.patch (50,030 bytes)   
Index: common/mptString.cpp
===================================================================
--- common/mptString.cpp	(revision 12235)
+++ common/mptString.cpp	(working copy)
@@ -1772,7 +1772,7 @@
 	return 0;
 }
 
-int CompareNoCaseAscii(const std::string &a, const std::string &b)
+int CompareNoCaseAscii(std::string_view a, std::string_view b)
 {
 	for(std::size_t i = 0; i < std::min(a.length(), b.length()); ++i)
 	{
@@ -1793,6 +1793,12 @@
 	return a.length() < b.length() ? -1 : 1;
 }
 
+int CompareNoCaseAscii(const std::string &a, const std::string &b)
+{
+	return CompareNoCaseAscii(std::string_view(a), std::string_view(b));
+}
+
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s)
Index: common/mptString.h
===================================================================
--- common/mptString.h	(revision 12235)
+++ common/mptString.h	(working copy)
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include <cstring>
 
@@ -524,8 +525,10 @@
 std::string ToUpperCaseAscii(std::string s);
 
 int CompareNoCaseAscii(const char *a, const char *b, std::size_t n);
+int CompareNoCaseAscii(std::string_view a, std::string_view b);
 int CompareNoCaseAscii(const std::string &a, const std::string &b);
 
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s);
Index: common/stdafx.h
===================================================================
--- common/stdafx.h	(revision 12235)
+++ common/stdafx.h	(working copy)
@@ -115,6 +115,7 @@
 // <algorithm>
 // <limits>
 // <string>
+// <string_view>
 // <type_traits>
 // <cstring>
 
Index: libopenmpt/libopenmpt.h
===================================================================
--- libopenmpt/libopenmpt.h	(revision 12235)
+++ libopenmpt/libopenmpt.h	(working copy)
@@ -1396,21 +1396,21 @@
  * \param mod The module handle to work on.
  * \return A semicolon-separated list containing all supported ctl keys.
  * \remarks Currently supported ctl values are:
- *          - load.skip_samples: Set to "1" to avoid loading samples into memory
- *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
- *          - load.skip_plugins: Set to "1" to avoid loading plugins
- *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
- *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
- *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
- *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *          - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong (integer): The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end (text): Chooses the behaviour when the end of song is reached:
  *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
  *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
  *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
- *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
- *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
- *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
- *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
- *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *          - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
  *                    - 0: No dithering.
  *                    - 1: Default mode. Chosen by OpenMPT code, might change.
  *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1417,6 +1417,7 @@
  *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
  */
 LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+
 /*! \brief Get current ctl value
  *
  * \param mod The module handle to work on.
@@ -1423,8 +1424,46 @@
  * \param ctl The ctl key whose value should be retrieved.
  * \return The associated ctl value, or NULL on failure.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_get_boolean(), openmpt_module_ctl_get_integer(), openmpt_module_ctl_get_floatingpoint(), or openmpt_module_ctl_get_text().
  */
-LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl );
+
 /*! \brief Set ctl value
  *
  * \param mod The module handle to work on.
@@ -1432,8 +1471,49 @@
  * \param value The value that should be set.
  * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_set_boolean(), openmpt_module_ctl_set_integer(), openmpt_module_ctl_set_floatingpoint(), or openmpt_module_ctl_set_text().
  */
-LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+/*! \brief Set ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value );
+/*! \brief Set ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value );
+/*! \brief Set ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value );
+/*! \brief Set ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value );
 
 /* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
 
Index: libopenmpt/libopenmpt.hpp
===================================================================
--- libopenmpt/libopenmpt.hpp	(revision 12235)
+++ libopenmpt/libopenmpt.hpp	(working copy)
@@ -17,6 +17,7 @@
 #include <iostream>
 #include <map>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <cstdint>
@@ -231,8 +232,16 @@
 /*!
   \param extension file extension to query without a leading dot. The case is ignored.
   \return true if the extension is supported by libopenmpt, false otherwise.
+  \deprecated Please use openmpt::is_extension_supported2().
 */
-LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+  \since 0.5.0
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported2( std::string_view extension );
 
 //! Roughly scan the input stream to find out whether libopenmpt might be able to open it
 /*!
@@ -962,21 +971,21 @@
 	/*!
 	  \return A vector containing all supported ctl keys.
 	  \remarks Currently supported ctl values are:
-	           - load.skip_samples: Set to "1" to avoid loading samples into memory
-	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
-	           - load.skip_plugins: Set to "1" to avoid loading plugins
-	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
-	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
-	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
-	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	           - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong (integer): The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end (text): Chooses the behaviour when the end of song is reached:
 	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
 	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
 	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
-	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
-	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
-	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
-	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
-	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	           - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
 	                     - 0: No dithering.
 	                     - 1: Default mode. Chosen by OpenMPT code, might change.
 	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -991,8 +1000,42 @@
 	  \param ctl The ctl key whose value should be retrieved.
 	  \return The associated ctl value.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_get_boolean(), openmpt::module::ctl_get_integer(), openmpt::module::ctl_get_floatingpoint(), or openmpt::module::ctl_get_text().
 	*/
-	std::string ctl_get( const std::string & ctl ) const;
+	LIBOPENMPT_ATTR_DEPRECATED std::string ctl_get( const std::string & ctl ) const;
+	//! Get current ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	bool ctl_get_boolean( std::string_view ctl ) const;
+	//! Get current ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::int64_t ctl_get_integer( std::string_view ctl ) const;
+	//! Get current ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	double ctl_get_floatingpoint( std::string_view ctl ) const;
+	//! Get current ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::string ctl_get_text( std::string_view ctl ) const;
+
 	//! Set ctl value
 	/*!
 	  \param ctl The ctl key whose value should be set.
@@ -999,8 +1042,45 @@
 	  \param value The value that should be set.
 	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_set_bool(), openmpt::module::ctl_set_int(), openmpt::module::ctl_set_float(), or openmpt::module::ctl_set_string().
 	*/
-	void ctl_set( const std::string & ctl, const std::string & value );
+	LIBOPENMPT_ATTR_DEPRECATED void ctl_set( const std::string & ctl, const std::string & value );
+	//! Set ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_boolean( std::string_view ctl, bool value );
+	//! Set ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_integer( std::string_view ctl, std::int64_t value );
+	//! Set ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_floatingpoint( std::string_view ctl, double value );
+	//! Set ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_text( std::string_view ctl, std::string_view value );
 
 	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
 
Index: libopenmpt/libopenmpt_c.cpp
===================================================================
--- libopenmpt/libopenmpt_c.cpp	(revision 12235)
+++ libopenmpt/libopenmpt_c.cpp	(working copy)
@@ -1278,6 +1278,46 @@
 	}
 	return NULL;
 }
+int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_boolean( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_integer( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_floatingpoint( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0.0;
+}
+const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return openmpt::strdup( mod->impl->ctl_get_text( ctl ).c_str() );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return NULL;
+}
 
 int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value ) {
 	try {
@@ -1291,8 +1331,52 @@
 	}
 	return 0;
 }
+int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_boolean( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_integer( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_floatingpoint( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		openmpt::interface::check_pointer( value );
+		mod->impl->ctl_set_text( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
 
-
 openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls ) {
 	try {
 		openmpt_module_ext * mod_ext = (openmpt_module_ext*)std::calloc( 1, sizeof( openmpt_module_ext ) );
Index: libopenmpt/libopenmpt_cxx.cpp
===================================================================
--- libopenmpt/libopenmpt_cxx.cpp	(revision 12235)
+++ libopenmpt/libopenmpt_cxx.cpp	(working copy)
@@ -126,6 +126,9 @@
 bool is_extension_supported( const std::string & extension ) {
 	return openmpt::module_impl::is_extension_supported( extension );
 }
+bool is_extension_supported( std::string_view extension ) {
+	return openmpt::module_impl::is_extension_supported( extension );
+}
 
 double could_open_probability( std::istream & stream, double effort, std::ostream & log ) {
 	return openmpt::module_impl::could_open_probability( stream, effort, openmpt::helper::make_unique<std_ostream_log>( log ) );
@@ -385,12 +388,38 @@
 std::vector<std::string> module::get_ctls() const {
 	return impl->get_ctls();
 }
+
 std::string module::ctl_get( const std::string & ctl ) const {
 	return impl->ctl_get( ctl );
 }
+bool module::ctl_get_boolean( std::string_view ctl ) const {
+	return impl->ctl_get_boolean( ctl );
+}
+std::int64_t module::ctl_get_integer( std::string_view ctl ) const {
+	return impl->ctl_get_integer( ctl );
+}
+double module::ctl_get_floatingpoint( std::string_view ctl ) const {
+	return impl->ctl_get_floatingpoint( ctl );
+}
+std::string module::ctl_get_text( std::string_view ctl ) const {
+	return impl->ctl_get_text( ctl );
+}
+
 void module::ctl_set( const std::string & ctl, const std::string & value ) {
 	impl->ctl_set( ctl, value );
 }
+void module::ctl_set_boolean( std::string_view ctl, bool value ) {
+	impl->ctl_set_boolean( ctl, value );
+}
+void module::ctl_set_integer( std::string_view ctl, std::int64_t value ) {
+	impl->ctl_set_integer( ctl, value );
+}
+void module::ctl_set_floatingpoint( std::string_view ctl, double value ) {
+	impl->ctl_set_floatingpoint( ctl, value );
+}
+void module::ctl_set_text( std::string_view ctl, std::string_view value ) {
+	impl->ctl_set_text( ctl, value );
+}
 
 module_ext::module_ext( std::istream & stream, std::ostream & log, const std::map< std::string, std::string > & ctls ) : ext_impl(0) {
 	ext_impl = new module_ext_impl( stream, openmpt::helper::make_unique<std_ostream_log>( log ), ctls );
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 12235)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -593,12 +593,9 @@
 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
 	return retval;
 }
-bool module_impl::is_extension_supported( const char * extension ) {
+bool module_impl::is_extension_supported( std::string_view extension ) {
 	return CSoundFile::IsExtensionSupported( extension );
 }
-bool module_impl::is_extension_supported( const std::string & extension ) {
-	return CSoundFile::IsExtensionSupported( extension.c_str() );
-}
 double module_impl::could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ) {
 	try {
 		if ( effort >= 0.8 ) {
@@ -1527,6 +1524,7 @@
 		"dither",
 	};
 }
+
 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1591,6 +1589,215 @@
 		}
 	}
 }
+bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		return m_ctl_load_skip_samples;
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		return m_ctl_load_skip_patterns;
+	} else if ( ctl == "load.skip_plugins" ) {
+		return m_ctl_load_skip_plugins;
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		return m_ctl_load_skip_subsongs_init;
+	} else if ( ctl == "seek.sync_samples" ) {
+		return m_ctl_seek_sync_samples;
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		return m_sndFile->m_Resampler.m_Settings.emulateAmiga;
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return false;
+		}
+	}
+}
+std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		return get_selected_subsong();
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		return static_cast<int>( m_Dither->GetMode() );
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0;
+		}
+	}
+}
+double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return 65536.0 / m_sndFile->m_nTempoFactor;
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return m_sndFile->m_nFreqFactor / 65536.0;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale );
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0.0;
+		}
+	}
+}
+std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		switch ( m_ctl_play_at_end )
+		{
+		case song_end_action::fadeout_song:
+			return "fadeout";
+		case song_end_action::continue_song:
+			return "continue";
+		case song_end_action::stop_song:
+			return "stop";
+		default:
+			return std::string();
+		}
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return std::string();
+		}
+	}
+}
+
 void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1671,5 +1878,229 @@
 		}
 	}
 }
+void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		m_ctl_load_skip_samples = value;
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		m_ctl_load_skip_patterns = value;
+	} else if ( ctl == "load.skip_plugins" ) {
+		m_ctl_load_skip_plugins = value;
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		m_ctl_load_skip_subsongs_init = value;
+	} else if ( ctl == "seek.sync_samples" ) {
+		m_ctl_seek_sync_samples = value;
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
+		newsettings.emulateAmiga = value;
+		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
+			m_sndFile->SetResamplerSettings( newsettings );
+		}
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		select_subsong( mpt::saturate_cast<int32>( value ) );
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		int dither = mpt::saturate_cast<int>( value );
+		if ( dither < 0 || dither >= NumDitherModes ) {
+			dither = DitherDefault;
+		}
+		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return;
+		}
+		double factor = value;
+		if ( factor <= 0.0 || factor > 4.0 ) {
+			throw openmpt::exception("invalid tempo factor");
+		}
+		m_sndFile->m_nTempoFactor = mpt::saturate_round<uint32_t>( 65536.0 / factor );
+		m_sndFile->RecalculateSamplesPerTick();
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return;
+		}
+		double factor = value;
+		if ( factor <= 0.0 || factor > 4.0 ) {
+			throw openmpt::exception("invalid pitch factor");
+		}
+		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
+		m_sndFile->RecalculateSamplesPerTick();
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			// ignore
+		}
+	}
+}
+void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + std::string( value ) );
+	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_plugins" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "load.skip_subsongs_init" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "seek.sync_samples" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "subsong" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.at_end" ) {
+		if ( value == "fadeout" ) {
+			m_ctl_play_at_end = song_end_action::fadeout_song;
+		} else if(value == "continue") {
+			m_ctl_play_at_end = song_end_action::continue_song;
+		} else if(value == "stop") {
+			m_ctl_play_at_end = song_end_action::stop_song;
+		} else {
+			throw openmpt::exception("unknown song end action:" + std::string(value));
+		}
+	} else if ( ctl == "play.tempo_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "play.pitch_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else if ( ctl == "dither" ) {
+		throw openmpt::exception("wrong ctl value type");
+	} else {
+		if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
+		} else {
+			// ignore
+		}
+	}
+}
 
 } // namespace openmpt
Index: libopenmpt/libopenmpt_impl.hpp
===================================================================
--- libopenmpt/libopenmpt_impl.hpp	(revision 12235)
+++ libopenmpt/libopenmpt_impl.hpp	(working copy)
@@ -130,8 +130,7 @@
 	static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log );
 public:
 	static std::vector<std::string> get_supported_extensions();
-	static bool is_extension_supported( const char * extension );
-	static bool is_extension_supported( const std::string & extension );
+	static bool is_extension_supported( std::string_view extension );
 	static double could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log );
 	static double could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log );
 	static std::size_t probe_file_header_get_recommended_size();
@@ -204,7 +203,15 @@
 	std::string highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
 	std::vector<std::string> get_ctls() const;
 	std::string ctl_get( std::string ctl, bool throw_if_unknown = true ) const;
+	bool ctl_get_boolean( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::int64_t ctl_get_integer( std::string_view ctl, bool throw_if_unknown = true ) const;
+	double ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::string ctl_get_text( std::string_view ctl, bool throw_if_unknown = true ) const;
 	void ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown = true );
+	void ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown = true );
+	void ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown = true );
+	void ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown = true );
+	void ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown = true );
 }; // class module_impl
 
 namespace helper {
Index: openmpt123/openmpt123.cpp
===================================================================
--- openmpt123/openmpt123.cpp	(revision 12235)
+++ openmpt123/openmpt123.cpp	(working copy)
@@ -711,33 +711,6 @@
 }
 
 
-template < typename T, typename Tmod >
-T ctl_get( Tmod & mod, const std::string & ctl ) {
-	T result = T();
-	try {
-		std::istringstream str;
-		str.imbue( std::locale::classic() );
-		str.str( mod.ctl_get( ctl ) );
-		str >> std::fixed >> std::setprecision(16) >> result;
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return result;
-}
-
-template < typename T, typename Tmod >
-void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
-	try {
-		std::ostringstream str;
-		str.imbue( std::locale::classic() );
-		str << std::fixed << std::setprecision(16) << val;
-		mod.ctl_set( ctl, str.str() );
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return;
-}
-
 template < typename Tmod >
 static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
 	flags.separation = std::max( flags.separation, std::int32_t(   0 ) );
@@ -753,12 +726,17 @@
 	mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
 	mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
 	mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
-	ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
-	ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
-	std::ostringstream dither_str;
-	dither_str.imbue( std::locale::classic() );
-	dither_str << flags.dither;
-	mod.ctl_set( "dither", dither_str.str() );
+	try {
+		mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	try {
+		mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	mod.ctl_set_integer( "dither", flags.dither );
 }
 
 struct prev_file { int count; prev_file( int c ) : count(c) { } };
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 12235)
+++ soundlib/Sndfile.h	(working copy)
@@ -823,7 +823,7 @@
 	bool ReadWAV(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
-	static bool IsExtensionSupported(const char *ext); // UTF8, casing of ext is ignored
+	static bool IsExtensionSupported(std::string_view ext); // UTF8, casing of ext is ignored
 	static mpt::ustring ModContainerTypeToString(MODCONTAINERTYPE containertype);
 	static mpt::ustring ModContainerTypeToTracker(MODCONTAINERTYPE containertype);
 
Index: soundlib/Tables.cpp
===================================================================
--- soundlib/Tables.cpp	(revision 12235)
+++ soundlib/Tables.cpp	(working copy)
@@ -176,21 +176,19 @@
 }
 
 
-static bool IsEqualExtension(const char *a, const char *b)
+static bool IsEqualExtension(std::string_view a, std::string_view b)
 {
-	std::size_t lena = std::strlen(a);
-	std::size_t lenb = std::strlen(b);
-	if(lena != lenb)
+	if(a.length() != b.length())
 	{
 		return false;
 	}
-	return mpt::CompareNoCaseAscii(a, b, lena) == 0;
+	return mpt::CompareNoCaseAscii(a, b) == 0;
 }
 
 
-bool CSoundFile::IsExtensionSupported(const char *ext)
+bool CSoundFile::IsExtensionSupported(std::string_view ext)
 {
-	if(ext == nullptr || ext[0] == 0)
+	if(ext.length() == 0)
 	{
 		return false;
 	}
libopenmpt-api-cpp17-v4.patch (50,030 bytes)   
libopenmpt-api-cpp17-v5.patch (52,441 bytes)   
Index: common/mptString.cpp
===================================================================
--- common/mptString.cpp	(revision 12255)
+++ common/mptString.cpp	(working copy)
@@ -1772,7 +1772,7 @@
 	return 0;
 }
 
-int CompareNoCaseAscii(const std::string &a, const std::string &b)
+int CompareNoCaseAscii(std::string_view a, std::string_view b)
 {
 	for(std::size_t i = 0; i < std::min(a.length(), b.length()); ++i)
 	{
@@ -1793,6 +1793,12 @@
 	return a.length() < b.length() ? -1 : 1;
 }
 
+int CompareNoCaseAscii(const std::string &a, const std::string &b)
+{
+	return CompareNoCaseAscii(std::string_view(a), std::string_view(b));
+}
+
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s)
Index: common/mptString.h
===================================================================
--- common/mptString.h	(revision 12255)
+++ common/mptString.h	(working copy)
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include <cstring>
 
@@ -524,8 +525,10 @@
 std::string ToUpperCaseAscii(std::string s);
 
 int CompareNoCaseAscii(const char *a, const char *b, std::size_t n);
+int CompareNoCaseAscii(std::string_view a, std::string_view b);
 int CompareNoCaseAscii(const std::string &a, const std::string &b);
 
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s);
Index: common/stdafx.h
===================================================================
--- common/stdafx.h	(revision 12255)
+++ common/stdafx.h	(working copy)
@@ -115,6 +115,7 @@
 // <algorithm>
 // <limits>
 // <string>
+// <string_view>
 // <type_traits>
 // <cstring>
 
Index: libopenmpt/libopenmpt.h
===================================================================
--- libopenmpt/libopenmpt.h	(revision 12255)
+++ libopenmpt/libopenmpt.h	(working copy)
@@ -1396,21 +1396,21 @@
  * \param mod The module handle to work on.
  * \return A semicolon-separated list containing all supported ctl keys.
  * \remarks Currently supported ctl values are:
- *          - load.skip_samples: Set to "1" to avoid loading samples into memory
- *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
- *          - load.skip_plugins: Set to "1" to avoid loading plugins
- *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
- *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
- *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
- *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *          - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong (integer): The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end (text): Chooses the behaviour when the end of song is reached:
  *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
  *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
  *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
- *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
- *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
- *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
- *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
- *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *          - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
  *                    - 0: No dithering.
  *                    - 1: Default mode. Chosen by OpenMPT code, might change.
  *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1417,6 +1417,7 @@
  *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
  */
 LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+
 /*! \brief Get current ctl value
  *
  * \param mod The module handle to work on.
@@ -1423,8 +1424,46 @@
  * \param ctl The ctl key whose value should be retrieved.
  * \return The associated ctl value, or NULL on failure.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_get_boolean(), openmpt_module_ctl_get_integer(), openmpt_module_ctl_get_floatingpoint(), or openmpt_module_ctl_get_text().
  */
-LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl );
+
 /*! \brief Set ctl value
  *
  * \param mod The module handle to work on.
@@ -1432,8 +1471,49 @@
  * \param value The value that should be set.
  * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_set_boolean(), openmpt_module_ctl_set_integer(), openmpt_module_ctl_set_floatingpoint(), or openmpt_module_ctl_set_text().
  */
-LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+/*! \brief Set ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value );
+/*! \brief Set ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value );
+/*! \brief Set ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value );
+/*! \brief Set ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value );
 
 /* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
 
Index: libopenmpt/libopenmpt.hpp
===================================================================
--- libopenmpt/libopenmpt.hpp	(revision 12255)
+++ libopenmpt/libopenmpt.hpp	(working copy)
@@ -17,6 +17,7 @@
 #include <iostream>
 #include <map>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <cstdint>
@@ -231,8 +232,16 @@
 /*!
   \param extension file extension to query without a leading dot. The case is ignored.
   \return true if the extension is supported by libopenmpt, false otherwise.
+  \deprecated Please use openmpt::is_extension_supported2().
 */
-LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+  \since 0.5.0
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported2( std::string_view extension );
 
 //! Roughly scan the input stream to find out whether libopenmpt might be able to open it
 /*!
@@ -962,21 +971,21 @@
 	/*!
 	  \return A vector containing all supported ctl keys.
 	  \remarks Currently supported ctl values are:
-	           - load.skip_samples: Set to "1" to avoid loading samples into memory
-	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
-	           - load.skip_plugins: Set to "1" to avoid loading plugins
-	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
-	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
-	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
-	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	           - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong (integer): The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end (text): Chooses the behaviour when the end of song is reached:
 	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
 	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
 	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
-	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
-	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
-	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
-	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
-	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	           - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
 	                     - 0: No dithering.
 	                     - 1: Default mode. Chosen by OpenMPT code, might change.
 	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -991,8 +1000,42 @@
 	  \param ctl The ctl key whose value should be retrieved.
 	  \return The associated ctl value.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_get_boolean(), openmpt::module::ctl_get_integer(), openmpt::module::ctl_get_floatingpoint(), or openmpt::module::ctl_get_text().
 	*/
-	std::string ctl_get( const std::string & ctl ) const;
+	LIBOPENMPT_ATTR_DEPRECATED std::string ctl_get( const std::string & ctl ) const;
+	//! Get current ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	bool ctl_get_boolean( std::string_view ctl ) const;
+	//! Get current ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::int64_t ctl_get_integer( std::string_view ctl ) const;
+	//! Get current ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	double ctl_get_floatingpoint( std::string_view ctl ) const;
+	//! Get current ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::string ctl_get_text( std::string_view ctl ) const;
+
 	//! Set ctl value
 	/*!
 	  \param ctl The ctl key whose value should be set.
@@ -999,8 +1042,45 @@
 	  \param value The value that should be set.
 	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_set_bool(), openmpt::module::ctl_set_int(), openmpt::module::ctl_set_float(), or openmpt::module::ctl_set_string().
 	*/
-	void ctl_set( const std::string & ctl, const std::string & value );
+	LIBOPENMPT_ATTR_DEPRECATED void ctl_set( const std::string & ctl, const std::string & value );
+	//! Set ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_boolean( std::string_view ctl, bool value );
+	//! Set ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_integer( std::string_view ctl, std::int64_t value );
+	//! Set ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_floatingpoint( std::string_view ctl, double value );
+	//! Set ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_text( std::string_view ctl, std::string_view value );
 
 	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
 
Index: libopenmpt/libopenmpt_c.cpp
===================================================================
--- libopenmpt/libopenmpt_c.cpp	(revision 12255)
+++ libopenmpt/libopenmpt_c.cpp	(working copy)
@@ -1278,6 +1278,46 @@
 	}
 	return NULL;
 }
+int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_boolean( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_integer( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_floatingpoint( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0.0;
+}
+const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return openmpt::strdup( mod->impl->ctl_get_text( ctl ).c_str() );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return NULL;
+}
 
 int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value ) {
 	try {
@@ -1291,8 +1331,52 @@
 	}
 	return 0;
 }
+int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_boolean( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_integer( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_floatingpoint( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		openmpt::interface::check_pointer( value );
+		mod->impl->ctl_set_text( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
 
-
 openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls ) {
 	try {
 		openmpt_module_ext * mod_ext = (openmpt_module_ext*)std::calloc( 1, sizeof( openmpt_module_ext ) );
Index: libopenmpt/libopenmpt_cxx.cpp
===================================================================
--- libopenmpt/libopenmpt_cxx.cpp	(revision 12255)
+++ libopenmpt/libopenmpt_cxx.cpp	(working copy)
@@ -126,6 +126,9 @@
 bool is_extension_supported( const std::string & extension ) {
 	return openmpt::module_impl::is_extension_supported( extension );
 }
+bool is_extension_supported( std::string_view extension ) {
+	return openmpt::module_impl::is_extension_supported( extension );
+}
 
 double could_open_probability( std::istream & stream, double effort, std::ostream & log ) {
 	return openmpt::module_impl::could_open_probability( stream, effort, openmpt::helper::make_unique<std_ostream_log>( log ) );
@@ -385,12 +388,38 @@
 std::vector<std::string> module::get_ctls() const {
 	return impl->get_ctls();
 }
+
 std::string module::ctl_get( const std::string & ctl ) const {
 	return impl->ctl_get( ctl );
 }
+bool module::ctl_get_boolean( std::string_view ctl ) const {
+	return impl->ctl_get_boolean( ctl );
+}
+std::int64_t module::ctl_get_integer( std::string_view ctl ) const {
+	return impl->ctl_get_integer( ctl );
+}
+double module::ctl_get_floatingpoint( std::string_view ctl ) const {
+	return impl->ctl_get_floatingpoint( ctl );
+}
+std::string module::ctl_get_text( std::string_view ctl ) const {
+	return impl->ctl_get_text( ctl );
+}
+
 void module::ctl_set( const std::string & ctl, const std::string & value ) {
 	impl->ctl_set( ctl, value );
 }
+void module::ctl_set_boolean( std::string_view ctl, bool value ) {
+	impl->ctl_set_boolean( ctl, value );
+}
+void module::ctl_set_integer( std::string_view ctl, std::int64_t value ) {
+	impl->ctl_set_integer( ctl, value );
+}
+void module::ctl_set_floatingpoint( std::string_view ctl, double value ) {
+	impl->ctl_set_floatingpoint( ctl, value );
+}
+void module::ctl_set_text( std::string_view ctl, std::string_view value ) {
+	impl->ctl_set_text( ctl, value );
+}
 
 module_ext::module_ext( std::istream & stream, std::ostream & log, const std::map< std::string, std::string > & ctls ) : ext_impl(0) {
 	ext_impl = new module_ext_impl( stream, openmpt::helper::make_unique<std_ostream_log>( log ), ctls );
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 12255)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -593,12 +593,9 @@
 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
 	return retval;
 }
-bool module_impl::is_extension_supported( const char * extension ) {
+bool module_impl::is_extension_supported( std::string_view extension ) {
 	return CSoundFile::IsExtensionSupported( extension );
 }
-bool module_impl::is_extension_supported( const std::string & extension ) {
-	return CSoundFile::IsExtensionSupported( extension.c_str() );
-}
 double module_impl::could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ) {
 	try {
 		if ( effort >= 0.8 ) {
@@ -1510,23 +1507,34 @@
 	return format_and_highlight_pattern_row_channel( p, r, c, width, pad ).second;
 }
 
-std::vector<std::string> module_impl::get_ctls() const {
-	return
-	{
-		"load.skip_samples",
-		"load.skip_patterns",
-		"load.skip_plugins",
-		"load.skip_subsongs_init",
-		"seek.sync_samples",
-		"subsong",
-		"play.tempo_factor",
-		"play.pitch_factor",
-		"play.at_end",
-		"render.resampler.emulate_amiga",
-		"render.opl.volume_factor",
-		"dither",
+std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> module_impl::get_ctl_infos() const {
+	static constexpr ctl_info ctl_infos[] = {
+		{ "load.skip_samples", ctl_type::boolean },
+		{ "load.skip_patterns", ctl_type::boolean },
+		{ "load.skip_plugins", ctl_type::boolean },
+		{ "load.skip_subsongs_init", ctl_type::boolean },
+		{ "seek.sync_samples", ctl_type::boolean },
+		{ "subsong", ctl_type::integer },
+		{ "play.tempo_factor", ctl_type::floatingpoint },
+		{ "play.pitch_factor", ctl_type::floatingpoint },
+		{ "play.at_end", ctl_type::text },
+		{ "render.resampler.emulate_amiga", ctl_type::boolean },
+		{ "render.opl.volume_factor", ctl_type::floatingpoint },
+		{ "dither", ctl_type::integer }
 	};
+	return std::make_pair(std::begin(ctl_infos), std::end(ctl_infos));
 }
+
+std::vector<std::string> module_impl::get_ctls() const {
+	std::vector<std::string> result;
+	auto ctl_infos = get_ctl_infos();
+	result.reserve(std::distance(ctl_infos.first, ctl_infos.second));
+	for ( std::ptrdiff_t i = 0; i < std::distance(ctl_infos.first, ctl_infos.second); ++i ) {
+		result.push_back(ctl_infos.first[i].name);
+	}
+	return result;
+}
+
 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1541,20 +1549,189 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl);
+		} else {
+			return std::string();
+		}
+	}
+	std::string result;
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			return mpt::fmt::val( ctl_get_boolean( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::integer:
+			return mpt::fmt::val( ctl_get_integer( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::floatingpoint:
+			return mpt::fmt::val( ctl_get_floatingpoint( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::text:
+			return ctl_get_text( ctl, throw_if_unknown );
+			break;
+	}
+	return result;
+}
+bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return false;
+		}
+	}
+	if ( found_ctl->type != ctl_type::boolean ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
 	if ( ctl == "" ) {
 		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		return mpt::fmt::val( m_ctl_load_skip_samples );
+		return m_ctl_load_skip_samples;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		return mpt::fmt::val( m_ctl_load_skip_patterns );
+		return m_ctl_load_skip_patterns;
 	} else if ( ctl == "load.skip_plugins" ) {
-		return mpt::fmt::val( m_ctl_load_skip_plugins );
+		return m_ctl_load_skip_plugins;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		return mpt::fmt::val( m_ctl_load_skip_subsongs_init );
+		return m_ctl_load_skip_subsongs_init;
 	} else if ( ctl == "seek.sync_samples" ) {
-		return mpt::fmt::val( m_ctl_seek_sync_samples );
+		return m_ctl_seek_sync_samples;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		return m_sndFile->m_Resampler.m_Settings.emulateAmiga;
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return false;
+	}
+}
+std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::integer ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "subsong" ) {
-		return mpt::fmt::val( get_selected_subsong() );
+		return get_selected_subsong();
+	} else if ( ctl == "dither" ) {
+		return static_cast<int>( m_Dither->GetMode() );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0;
+	}
+}
+double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0.0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::floatingpoint ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return 65536.0 / m_sndFile->m_nTempoFactor;
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return m_sndFile->m_nFreqFactor / 65536.0;
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0.0;
+	}
+}
+std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return std::string();
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "play.at_end" ) {
 		switch ( m_ctl_play_at_end )
 		{
@@ -1567,31 +1744,52 @@
 		default:
 			return std::string();
 		}
-	} else if ( ctl == "play.tempo_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return std::string();
+	}
+}
+
+void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
-		return mpt::fmt::val( 65536.0 / m_sndFile->m_nTempoFactor );
-	} else if ( ctl == "play.pitch_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
-		}
-		return mpt::fmt::val( m_sndFile->m_nFreqFactor / 65536.0 );
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		return mpt::fmt::val( m_sndFile->m_Resampler.m_Settings.emulateAmiga );
-	} else if ( ctl == "render.opl.volume_factor" ) {
-		return mpt::fmt::val( static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		return mpt::fmt::val( static_cast<int>( m_Dither->GetMode() ) );
-	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl);
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + value);
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
 		} else {
-			return std::string();
+			return;
 		}
 	}
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			ctl_set_boolean( ctl, ConvertStrTo<bool>( value ), throw_if_unknown );
+			break;
+		case ctl_type::integer:
+			ctl_set_integer( ctl, ConvertStrTo<int64>( value ), throw_if_unknown );
+			break;
+		case ctl_type::floatingpoint:
+			ctl_set_floatingpoint( ctl, ConvertStrTo<double>( value ), throw_if_unknown );
+			break;
+		case ctl_type::text:
+			ctl_set_text( ctl, value, throw_if_unknown );
+			break;
+	}
 }
-void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
 		// cppcheck-suppress containerOutOfBounds
@@ -1605,35 +1803,109 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
 	if ( ctl == "" ) {
-		throw openmpt::exception("empty ctl: := " + value);
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		m_ctl_load_skip_samples = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_samples = value;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		m_ctl_load_skip_patterns = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_patterns = value;
 	} else if ( ctl == "load.skip_plugins" ) {
-		m_ctl_load_skip_plugins = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_plugins = value;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		m_ctl_load_skip_subsongs_init = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_subsongs_init = value;
 	} else if ( ctl == "seek.sync_samples" ) {
-		m_ctl_seek_sync_samples = ConvertStrTo<bool>( value );
+		m_ctl_seek_sync_samples = value;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
+		newsettings.emulateAmiga = value;
+		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
+			m_sndFile->SetResamplerSettings( newsettings );
+		}
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "subsong" ) {
-		select_subsong( ConvertStrTo<int32>( value ) );
-	} else if ( ctl == "play.at_end" ) {
-		if ( value == "fadeout" ) {
-			m_ctl_play_at_end = song_end_action::fadeout_song;
-		} else if(value == "continue") {
-			m_ctl_play_at_end = song_end_action::continue_song;
-		} else if(value == "stop") {
-			m_ctl_play_at_end = song_end_action::stop_song;
+		select_subsong( mpt::saturate_cast<int32>( value ) );
+	} else if ( ctl == "dither" ) {
+		int dither = mpt::saturate_cast<int>( value );
+		if ( dither < 0 || dither >= NumDitherModes ) {
+			dither = DitherDefault;
+		}
+		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
 		} else {
-			throw openmpt::exception("unknown song end action:" + value);
+			return;
 		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "play.tempo_factor" ) {
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid tempo factor");
 		}
@@ -1643,33 +1915,58 @@
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid pitch factor");
 		}
 		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
 		m_sndFile->RecalculateSamplesPerTick();
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
-		newsettings.emulateAmiga = ConvertStrTo<bool>( value );
-		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
-			m_sndFile->SetResamplerSettings( newsettings );
-		}
 	} else if ( ctl == "render.opl.volume_factor" ) {
-		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( ConvertStrTo<double>( value ) * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		int dither = ConvertStrTo<int>( value );
-		if ( dither < 0 || dither >= NumDitherModes ) {
-			dither = DitherDefault;
+		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
-		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
-	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + std::string( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
 		} else {
-			// ignore
+			return;
 		}
 	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + std::string( value ) );
+	} else if ( ctl == "play.at_end" ) {
+		if ( value == "fadeout" ) {
+			m_ctl_play_at_end = song_end_action::fadeout_song;
+		} else if(value == "continue") {
+			m_ctl_play_at_end = song_end_action::continue_song;
+		} else if(value == "stop") {
+			m_ctl_play_at_end = song_end_action::stop_song;
+		} else {
+			throw openmpt::exception("unknown song end action:" + std::string(value));
+		}
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
 }
 
 } // namespace openmpt
Index: libopenmpt/libopenmpt_impl.hpp
===================================================================
--- libopenmpt/libopenmpt_impl.hpp	(revision 12255)
+++ libopenmpt/libopenmpt_impl.hpp	(working copy)
@@ -15,6 +15,7 @@
 
 #include <iosfwd>
 #include <memory>
+#include <utility>
 
 #if defined(_MSC_VER)
 #pragma warning(push)
@@ -91,6 +92,17 @@
 
 	static const std::int32_t all_subsongs = -1;
 
+	enum class ctl_type {
+		boolean,
+		integer,
+		floatingpoint,
+		text,
+	};
+	struct ctl_info {
+		const char * name;
+		ctl_type type;
+	};
+
 	std::unique_ptr<log_interface> m_Log;
 	std::unique_ptr<log_forwarder> m_LogForwarder;
 	std::int32_t m_current_subsong;
@@ -130,8 +142,7 @@
 	static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log );
 public:
 	static std::vector<std::string> get_supported_extensions();
-	static bool is_extension_supported( const char * extension );
-	static bool is_extension_supported( const std::string & extension );
+	static bool is_extension_supported( std::string_view extension );
 	static double could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log );
 	static double could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log );
 	static std::size_t probe_file_header_get_recommended_size();
@@ -202,9 +213,18 @@
 	std::string highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const;
 	std::string format_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
 	std::string highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
+	std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> get_ctl_infos() const;
 	std::vector<std::string> get_ctls() const;
 	std::string ctl_get( std::string ctl, bool throw_if_unknown = true ) const;
+	bool ctl_get_boolean( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::int64_t ctl_get_integer( std::string_view ctl, bool throw_if_unknown = true ) const;
+	double ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::string ctl_get_text( std::string_view ctl, bool throw_if_unknown = true ) const;
 	void ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown = true );
+	void ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown = true );
+	void ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown = true );
+	void ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown = true );
+	void ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown = true );
 }; // class module_impl
 
 namespace helper {
Index: openmpt123/openmpt123.cpp
===================================================================
--- openmpt123/openmpt123.cpp	(revision 12255)
+++ openmpt123/openmpt123.cpp	(working copy)
@@ -711,33 +711,6 @@
 }
 
 
-template < typename T, typename Tmod >
-T ctl_get( Tmod & mod, const std::string & ctl ) {
-	T result = T();
-	try {
-		std::istringstream str;
-		str.imbue( std::locale::classic() );
-		str.str( mod.ctl_get( ctl ) );
-		str >> std::fixed >> std::setprecision(16) >> result;
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return result;
-}
-
-template < typename T, typename Tmod >
-void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
-	try {
-		std::ostringstream str;
-		str.imbue( std::locale::classic() );
-		str << std::fixed << std::setprecision(16) << val;
-		mod.ctl_set( ctl, str.str() );
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return;
-}
-
 template < typename Tmod >
 static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
 	flags.separation = std::max( flags.separation, std::int32_t(   0 ) );
@@ -753,12 +726,17 @@
 	mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
 	mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
 	mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
-	ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
-	ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
-	std::ostringstream dither_str;
-	dither_str.imbue( std::locale::classic() );
-	dither_str << flags.dither;
-	mod.ctl_set( "dither", dither_str.str() );
+	try {
+		mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	try {
+		mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	mod.ctl_set_integer( "dither", flags.dither );
 }
 
 struct prev_file { int count; prev_file( int c ) : count(c) { } };
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 12255)
+++ soundlib/Sndfile.h	(working copy)
@@ -824,7 +824,7 @@
 	bool ReadWAV(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
-	static bool IsExtensionSupported(const char *ext); // UTF8, casing of ext is ignored
+	static bool IsExtensionSupported(std::string_view ext); // UTF8, casing of ext is ignored
 	static mpt::ustring ModContainerTypeToString(MODCONTAINERTYPE containertype);
 	static mpt::ustring ModContainerTypeToTracker(MODCONTAINERTYPE containertype);
 
Index: soundlib/Tables.cpp
===================================================================
--- soundlib/Tables.cpp	(revision 12255)
+++ soundlib/Tables.cpp	(working copy)
@@ -176,21 +176,19 @@
 }
 
 
-static bool IsEqualExtension(const char *a, const char *b)
+static bool IsEqualExtension(std::string_view a, std::string_view b)
 {
-	std::size_t lena = std::strlen(a);
-	std::size_t lenb = std::strlen(b);
-	if(lena != lenb)
+	if(a.length() != b.length())
 	{
 		return false;
 	}
-	return mpt::CompareNoCaseAscii(a, b, lena) == 0;
+	return mpt::CompareNoCaseAscii(a, b) == 0;
 }
 
 
-bool CSoundFile::IsExtensionSupported(const char *ext)
+bool CSoundFile::IsExtensionSupported(std::string_view ext)
 {
-	if(ext == nullptr || ext[0] == 0)
+	if(ext.length() == 0)
 	{
 		return false;
 	}
libopenmpt-api-cpp17-v5.patch (52,441 bytes)   
libopenmpt-api-cpp17-v6.patch (52,445 bytes)   
Index: common/mptString.cpp
===================================================================
--- common/mptString.cpp	(revision 12302)
+++ common/mptString.cpp	(working copy)
@@ -1533,7 +1533,7 @@
 	return 0;
 }
 
-int CompareNoCaseAscii(const std::string &a, const std::string &b)
+int CompareNoCaseAscii(std::string_view a, std::string_view b)
 {
 	for(std::size_t i = 0; i < std::min(a.length(), b.length()); ++i)
 	{
@@ -1554,6 +1554,12 @@
 	return a.length() < b.length() ? -1 : 1;
 }
 
+int CompareNoCaseAscii(const std::string &a, const std::string &b)
+{
+	return CompareNoCaseAscii(std::string_view(a), std::string_view(b));
+}
+
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s)
Index: common/mptString.h
===================================================================
--- common/mptString.h	(revision 12302)
+++ common/mptString.h	(working copy)
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include <cstring>
 
@@ -545,8 +546,10 @@
 std::string ToUpperCaseAscii(std::string s);
 
 int CompareNoCaseAscii(const char *a, const char *b, std::size_t n);
+int CompareNoCaseAscii(std::string_view a, std::string_view b);
 int CompareNoCaseAscii(const std::string &a, const std::string &b);
 
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s);
Index: common/stdafx.h
===================================================================
--- common/stdafx.h	(revision 12302)
+++ common/stdafx.h	(working copy)
@@ -115,6 +115,7 @@
 // <algorithm>
 // <limits>
 // <string>
+// <string_view>
 // <type_traits>
 // <cstring>
 
Index: libopenmpt/libopenmpt.h
===================================================================
--- libopenmpt/libopenmpt.h	(revision 12302)
+++ libopenmpt/libopenmpt.h	(working copy)
@@ -1396,21 +1396,21 @@
  * \param mod The module handle to work on.
  * \return A semicolon-separated list containing all supported ctl keys.
  * \remarks Currently supported ctl values are:
- *          - load.skip_samples: Set to "1" to avoid loading samples into memory
- *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
- *          - load.skip_plugins: Set to "1" to avoid loading plugins
- *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
- *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
- *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
- *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *          - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong (integer): The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end (text): Chooses the behaviour when the end of song is reached:
  *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
  *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
  *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
- *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
- *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
- *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
- *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
- *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *          - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
  *                    - 0: No dithering.
  *                    - 1: Default mode. Chosen by OpenMPT code, might change.
  *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1417,6 +1417,7 @@
  *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
  */
 LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+
 /*! \brief Get current ctl value
  *
  * \param mod The module handle to work on.
@@ -1423,8 +1424,46 @@
  * \param ctl The ctl key whose value should be retrieved.
  * \return The associated ctl value, or NULL on failure.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_get_boolean(), openmpt_module_ctl_get_integer(), openmpt_module_ctl_get_floatingpoint(), or openmpt_module_ctl_get_text().
  */
-LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl );
+
 /*! \brief Set ctl value
  *
  * \param mod The module handle to work on.
@@ -1432,8 +1471,49 @@
  * \param value The value that should be set.
  * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_set_boolean(), openmpt_module_ctl_set_integer(), openmpt_module_ctl_set_floatingpoint(), or openmpt_module_ctl_set_text().
  */
-LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+/*! \brief Set ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value );
+/*! \brief Set ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value );
+/*! \brief Set ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value );
+/*! \brief Set ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value );
 
 /* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
 
Index: libopenmpt/libopenmpt.hpp
===================================================================
--- libopenmpt/libopenmpt.hpp	(revision 12302)
+++ libopenmpt/libopenmpt.hpp	(working copy)
@@ -17,6 +17,7 @@
 #include <iostream>
 #include <map>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <cstddef>
@@ -232,8 +233,16 @@
 /*!
   \param extension file extension to query without a leading dot. The case is ignored.
   \return true if the extension is supported by libopenmpt, false otherwise.
+  \deprecated Please use openmpt::is_extension_supported2().
 */
-LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+  \since 0.5.0
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported2( std::string_view extension );
 
 //! Roughly scan the input stream to find out whether libopenmpt might be able to open it
 /*!
@@ -1027,21 +1036,21 @@
 	/*!
 	  \return A vector containing all supported ctl keys.
 	  \remarks Currently supported ctl values are:
-	           - load.skip_samples: Set to "1" to avoid loading samples into memory
-	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
-	           - load.skip_plugins: Set to "1" to avoid loading plugins
-	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
-	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
-	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
-	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	           - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong (integer): The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end (text): Chooses the behaviour when the end of song is reached:
 	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
 	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
 	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
-	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
-	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
-	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
-	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
-	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	           - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
 	                     - 0: No dithering.
 	                     - 1: Default mode. Chosen by OpenMPT code, might change.
 	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1056,8 +1065,42 @@
 	  \param ctl The ctl key whose value should be retrieved.
 	  \return The associated ctl value.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_get_boolean(), openmpt::module::ctl_get_integer(), openmpt::module::ctl_get_floatingpoint(), or openmpt::module::ctl_get_text().
 	*/
-	std::string ctl_get( const std::string & ctl ) const;
+	LIBOPENMPT_ATTR_DEPRECATED std::string ctl_get( const std::string & ctl ) const;
+	//! Get current ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	bool ctl_get_boolean( std::string_view ctl ) const;
+	//! Get current ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::int64_t ctl_get_integer( std::string_view ctl ) const;
+	//! Get current ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	double ctl_get_floatingpoint( std::string_view ctl ) const;
+	//! Get current ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::string ctl_get_text( std::string_view ctl ) const;
+
 	//! Set ctl value
 	/*!
 	  \param ctl The ctl key whose value should be set.
@@ -1064,8 +1107,45 @@
 	  \param value The value that should be set.
 	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_set_bool(), openmpt::module::ctl_set_int(), openmpt::module::ctl_set_float(), or openmpt::module::ctl_set_string().
 	*/
-	void ctl_set( const std::string & ctl, const std::string & value );
+	LIBOPENMPT_ATTR_DEPRECATED void ctl_set( const std::string & ctl, const std::string & value );
+	//! Set ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_boolean( std::string_view ctl, bool value );
+	//! Set ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_integer( std::string_view ctl, std::int64_t value );
+	//! Set ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_floatingpoint( std::string_view ctl, double value );
+	//! Set ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_text( std::string_view ctl, std::string_view value );
 
 	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
 
Index: libopenmpt/libopenmpt_c.cpp
===================================================================
--- libopenmpt/libopenmpt_c.cpp	(revision 12302)
+++ libopenmpt/libopenmpt_c.cpp	(working copy)
@@ -1278,6 +1278,46 @@
 	}
 	return NULL;
 }
+int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_boolean( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_integer( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_floatingpoint( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0.0;
+}
+const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return openmpt::strdup( mod->impl->ctl_get_text( ctl ).c_str() );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return NULL;
+}
 
 int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value ) {
 	try {
@@ -1291,8 +1331,52 @@
 	}
 	return 0;
 }
+int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_boolean( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_integer( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_floatingpoint( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		openmpt::interface::check_pointer( value );
+		mod->impl->ctl_set_text( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
 
-
 openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls ) {
 	try {
 		openmpt_module_ext * mod_ext = (openmpt_module_ext*)std::calloc( 1, sizeof( openmpt_module_ext ) );
Index: libopenmpt/libopenmpt_cxx.cpp
===================================================================
--- libopenmpt/libopenmpt_cxx.cpp	(revision 12302)
+++ libopenmpt/libopenmpt_cxx.cpp	(working copy)
@@ -126,6 +126,9 @@
 bool is_extension_supported( const std::string & extension ) {
 	return openmpt::module_impl::is_extension_supported( extension );
 }
+bool is_extension_supported( std::string_view extension ) {
+	return openmpt::module_impl::is_extension_supported( extension );
+}
 
 double could_open_probability( std::istream & stream, double effort, std::ostream & log ) {
 	return openmpt::module_impl::could_open_probability( stream, effort, openmpt::helper::make_unique<std_ostream_log>( log ) );
@@ -403,12 +406,38 @@
 std::vector<std::string> module::get_ctls() const {
 	return impl->get_ctls();
 }
+
 std::string module::ctl_get( const std::string & ctl ) const {
 	return impl->ctl_get( ctl );
 }
+bool module::ctl_get_boolean( std::string_view ctl ) const {
+	return impl->ctl_get_boolean( ctl );
+}
+std::int64_t module::ctl_get_integer( std::string_view ctl ) const {
+	return impl->ctl_get_integer( ctl );
+}
+double module::ctl_get_floatingpoint( std::string_view ctl ) const {
+	return impl->ctl_get_floatingpoint( ctl );
+}
+std::string module::ctl_get_text( std::string_view ctl ) const {
+	return impl->ctl_get_text( ctl );
+}
+
 void module::ctl_set( const std::string & ctl, const std::string & value ) {
 	impl->ctl_set( ctl, value );
 }
+void module::ctl_set_boolean( std::string_view ctl, bool value ) {
+	impl->ctl_set_boolean( ctl, value );
+}
+void module::ctl_set_integer( std::string_view ctl, std::int64_t value ) {
+	impl->ctl_set_integer( ctl, value );
+}
+void module::ctl_set_floatingpoint( std::string_view ctl, double value ) {
+	impl->ctl_set_floatingpoint( ctl, value );
+}
+void module::ctl_set_text( std::string_view ctl, std::string_view value ) {
+	impl->ctl_set_text( ctl, value );
+}
 
 module_ext::module_ext( std::istream & stream, std::ostream & log, const std::map< std::string, std::string > & ctls ) : ext_impl(0) {
 	ext_impl = new module_ext_impl( stream, openmpt::helper::make_unique<std_ostream_log>( log ), ctls );
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 12302)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -593,12 +593,9 @@
 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
 	return retval;
 }
-bool module_impl::is_extension_supported( const char * extension ) {
+bool module_impl::is_extension_supported( std::string_view extension ) {
 	return CSoundFile::IsExtensionSupported( extension );
 }
-bool module_impl::is_extension_supported( const std::string & extension ) {
-	return CSoundFile::IsExtensionSupported( extension.c_str() );
-}
 double module_impl::could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ) {
 	try {
 		if ( effort >= 0.8 ) {
@@ -1556,23 +1553,34 @@
 	return format_and_highlight_pattern_row_channel( p, r, c, width, pad ).second;
 }
 
-std::vector<std::string> module_impl::get_ctls() const {
-	return
-	{
-		"load.skip_samples",
-		"load.skip_patterns",
-		"load.skip_plugins",
-		"load.skip_subsongs_init",
-		"seek.sync_samples",
-		"subsong",
-		"play.tempo_factor",
-		"play.pitch_factor",
-		"play.at_end",
-		"render.resampler.emulate_amiga",
-		"render.opl.volume_factor",
-		"dither",
+std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> module_impl::get_ctl_infos() const {
+	static constexpr ctl_info ctl_infos[] = {
+		{ "load.skip_samples", ctl_type::boolean },
+		{ "load.skip_patterns", ctl_type::boolean },
+		{ "load.skip_plugins", ctl_type::boolean },
+		{ "load.skip_subsongs_init", ctl_type::boolean },
+		{ "seek.sync_samples", ctl_type::boolean },
+		{ "subsong", ctl_type::integer },
+		{ "play.tempo_factor", ctl_type::floatingpoint },
+		{ "play.pitch_factor", ctl_type::floatingpoint },
+		{ "play.at_end", ctl_type::text },
+		{ "render.resampler.emulate_amiga", ctl_type::boolean },
+		{ "render.opl.volume_factor", ctl_type::floatingpoint },
+		{ "dither", ctl_type::integer }
 	};
+	return std::make_pair(std::begin(ctl_infos), std::end(ctl_infos));
 }
+
+std::vector<std::string> module_impl::get_ctls() const {
+	std::vector<std::string> result;
+	auto ctl_infos = get_ctl_infos();
+	result.reserve(std::distance(ctl_infos.first, ctl_infos.second));
+	for ( std::ptrdiff_t i = 0; i < std::distance(ctl_infos.first, ctl_infos.second); ++i ) {
+		result.push_back(ctl_infos.first[i].name);
+	}
+	return result;
+}
+
 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1587,20 +1595,189 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl);
+		} else {
+			return std::string();
+		}
+	}
+	std::string result;
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			return mpt::fmt::val( ctl_get_boolean( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::integer:
+			return mpt::fmt::val( ctl_get_integer( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::floatingpoint:
+			return mpt::fmt::val( ctl_get_floatingpoint( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::text:
+			return ctl_get_text( ctl, throw_if_unknown );
+			break;
+	}
+	return result;
+}
+bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return false;
+		}
+	}
+	if ( found_ctl->type != ctl_type::boolean ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
 	if ( ctl == "" ) {
 		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		return mpt::fmt::val( m_ctl_load_skip_samples );
+		return m_ctl_load_skip_samples;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		return mpt::fmt::val( m_ctl_load_skip_patterns );
+		return m_ctl_load_skip_patterns;
 	} else if ( ctl == "load.skip_plugins" ) {
-		return mpt::fmt::val( m_ctl_load_skip_plugins );
+		return m_ctl_load_skip_plugins;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		return mpt::fmt::val( m_ctl_load_skip_subsongs_init );
+		return m_ctl_load_skip_subsongs_init;
 	} else if ( ctl == "seek.sync_samples" ) {
-		return mpt::fmt::val( m_ctl_seek_sync_samples );
+		return m_ctl_seek_sync_samples;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		return m_sndFile->m_Resampler.m_Settings.emulateAmiga;
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return false;
+	}
+}
+std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::integer ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "subsong" ) {
-		return mpt::fmt::val( get_selected_subsong() );
+		return get_selected_subsong();
+	} else if ( ctl == "dither" ) {
+		return static_cast<int>( m_Dither->GetMode() );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0;
+	}
+}
+double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0.0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::floatingpoint ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return 65536.0 / m_sndFile->m_nTempoFactor;
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return m_sndFile->m_nFreqFactor / 65536.0;
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0.0;
+	}
+}
+std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return std::string();
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "play.at_end" ) {
 		switch ( m_ctl_play_at_end )
 		{
@@ -1613,31 +1790,52 @@
 		default:
 			return std::string();
 		}
-	} else if ( ctl == "play.tempo_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return std::string();
+	}
+}
+
+void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
-		return mpt::fmt::val( 65536.0 / m_sndFile->m_nTempoFactor );
-	} else if ( ctl == "play.pitch_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
-		}
-		return mpt::fmt::val( m_sndFile->m_nFreqFactor / 65536.0 );
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		return mpt::fmt::val( m_sndFile->m_Resampler.m_Settings.emulateAmiga );
-	} else if ( ctl == "render.opl.volume_factor" ) {
-		return mpt::fmt::val( static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		return mpt::fmt::val( static_cast<int>( m_Dither->GetMode() ) );
-	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl);
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + value);
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
 		} else {
-			return std::string();
+			return;
 		}
 	}
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			ctl_set_boolean( ctl, ConvertStrTo<bool>( value ), throw_if_unknown );
+			break;
+		case ctl_type::integer:
+			ctl_set_integer( ctl, ConvertStrTo<int64>( value ), throw_if_unknown );
+			break;
+		case ctl_type::floatingpoint:
+			ctl_set_floatingpoint( ctl, ConvertStrTo<double>( value ), throw_if_unknown );
+			break;
+		case ctl_type::text:
+			ctl_set_text( ctl, value, throw_if_unknown );
+			break;
+	}
 }
-void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
 		// cppcheck-suppress containerOutOfBounds
@@ -1651,35 +1849,109 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
 	if ( ctl == "" ) {
-		throw openmpt::exception("empty ctl: := " + value);
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		m_ctl_load_skip_samples = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_samples = value;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		m_ctl_load_skip_patterns = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_patterns = value;
 	} else if ( ctl == "load.skip_plugins" ) {
-		m_ctl_load_skip_plugins = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_plugins = value;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		m_ctl_load_skip_subsongs_init = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_subsongs_init = value;
 	} else if ( ctl == "seek.sync_samples" ) {
-		m_ctl_seek_sync_samples = ConvertStrTo<bool>( value );
+		m_ctl_seek_sync_samples = value;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
+		newsettings.emulateAmiga = value;
+		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
+			m_sndFile->SetResamplerSettings( newsettings );
+		}
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "subsong" ) {
-		select_subsong( ConvertStrTo<int32>( value ) );
-	} else if ( ctl == "play.at_end" ) {
-		if ( value == "fadeout" ) {
-			m_ctl_play_at_end = song_end_action::fadeout_song;
-		} else if(value == "continue") {
-			m_ctl_play_at_end = song_end_action::continue_song;
-		} else if(value == "stop") {
-			m_ctl_play_at_end = song_end_action::stop_song;
+		select_subsong( mpt::saturate_cast<int32>( value ) );
+	} else if ( ctl == "dither" ) {
+		int dither = mpt::saturate_cast<int>( value );
+		if ( dither < 0 || dither >= NumDitherModes ) {
+			dither = DitherDefault;
+		}
+		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
 		} else {
-			throw openmpt::exception("unknown song end action:" + value);
+			return;
 		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "play.tempo_factor" ) {
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid tempo factor");
 		}
@@ -1689,33 +1961,58 @@
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid pitch factor");
 		}
 		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
 		m_sndFile->RecalculateSamplesPerTick();
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
-		newsettings.emulateAmiga = ConvertStrTo<bool>( value );
-		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
-			m_sndFile->SetResamplerSettings( newsettings );
-		}
 	} else if ( ctl == "render.opl.volume_factor" ) {
-		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( ConvertStrTo<double>( value ) * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		int dither = ConvertStrTo<int>( value );
-		if ( dither < 0 || dither >= NumDitherModes ) {
-			dither = DitherDefault;
+		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
-		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
-	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + std::string( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
 		} else {
-			// ignore
+			return;
 		}
 	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + std::string( value ) );
+	} else if ( ctl == "play.at_end" ) {
+		if ( value == "fadeout" ) {
+			m_ctl_play_at_end = song_end_action::fadeout_song;
+		} else if(value == "continue") {
+			m_ctl_play_at_end = song_end_action::continue_song;
+		} else if(value == "stop") {
+			m_ctl_play_at_end = song_end_action::stop_song;
+		} else {
+			throw openmpt::exception("unknown song end action:" + std::string(value));
+		}
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
 }
 
 } // namespace openmpt
Index: libopenmpt/libopenmpt_impl.hpp
===================================================================
--- libopenmpt/libopenmpt_impl.hpp	(revision 12302)
+++ libopenmpt/libopenmpt_impl.hpp	(working copy)
@@ -15,6 +15,7 @@
 
 #include <iosfwd>
 #include <memory>
+#include <utility>
 
 #if defined(_MSC_VER)
 #pragma warning(push)
@@ -91,6 +92,17 @@
 
 	static const std::int32_t all_subsongs = -1;
 
+	enum class ctl_type {
+		boolean,
+		integer,
+		floatingpoint,
+		text,
+	};
+	struct ctl_info {
+		const char * name;
+		ctl_type type;
+	};
+
 	std::unique_ptr<log_interface> m_Log;
 	std::unique_ptr<log_forwarder> m_LogForwarder;
 	std::int32_t m_current_subsong;
@@ -130,8 +142,7 @@
 	static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log );
 public:
 	static std::vector<std::string> get_supported_extensions();
-	static bool is_extension_supported( const char * extension );
-	static bool is_extension_supported( const std::string & extension );
+	static bool is_extension_supported( std::string_view extension );
 	static double could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log );
 	static double could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log );
 	static std::size_t probe_file_header_get_recommended_size();
@@ -206,9 +217,18 @@
 	std::string highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const;
 	std::string format_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
 	std::string highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
+	std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> get_ctl_infos() const;
 	std::vector<std::string> get_ctls() const;
 	std::string ctl_get( std::string ctl, bool throw_if_unknown = true ) const;
+	bool ctl_get_boolean( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::int64_t ctl_get_integer( std::string_view ctl, bool throw_if_unknown = true ) const;
+	double ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::string ctl_get_text( std::string_view ctl, bool throw_if_unknown = true ) const;
 	void ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown = true );
+	void ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown = true );
+	void ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown = true );
+	void ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown = true );
+	void ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown = true );
 }; // class module_impl
 
 namespace helper {
Index: openmpt123/openmpt123.cpp
===================================================================
--- openmpt123/openmpt123.cpp	(revision 12302)
+++ openmpt123/openmpt123.cpp	(working copy)
@@ -736,33 +736,6 @@
 }
 
 
-template < typename T, typename Tmod >
-T ctl_get( Tmod & mod, const std::string & ctl ) {
-	T result = T();
-	try {
-		std::istringstream str;
-		str.imbue( std::locale::classic() );
-		str.str( mod.ctl_get( ctl ) );
-		str >> std::fixed >> std::setprecision(16) >> result;
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return result;
-}
-
-template < typename T, typename Tmod >
-void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
-	try {
-		std::ostringstream str;
-		str.imbue( std::locale::classic() );
-		str << std::fixed << std::setprecision(16) << val;
-		mod.ctl_set( ctl, str.str() );
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return;
-}
-
 template < typename Tmod >
 static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
 	flags.separation = std::max( flags.separation, std::int32_t(   0 ) );
@@ -778,12 +751,17 @@
 	mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
 	mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
 	mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
-	ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
-	ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
-	std::ostringstream dither_str;
-	dither_str.imbue( std::locale::classic() );
-	dither_str << flags.dither;
-	mod.ctl_set( "dither", dither_str.str() );
+	try {
+		mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	try {
+		mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	mod.ctl_set_integer( "dither", flags.dither );
 }
 
 struct prev_file { int count; prev_file( int c ) : count(c) { } };
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 12302)
+++ soundlib/Sndfile.h	(working copy)
@@ -824,7 +824,7 @@
 	bool ReadWAV(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
-	static bool IsExtensionSupported(const char *ext); // UTF8, casing of ext is ignored
+	static bool IsExtensionSupported(std::string_view ext); // UTF8, casing of ext is ignored
 	static mpt::ustring ModContainerTypeToString(MODCONTAINERTYPE containertype);
 	static mpt::ustring ModContainerTypeToTracker(MODCONTAINERTYPE containertype);
 
Index: soundlib/Tables.cpp
===================================================================
--- soundlib/Tables.cpp	(revision 12302)
+++ soundlib/Tables.cpp	(working copy)
@@ -176,21 +176,19 @@
 }
 
 
-static bool IsEqualExtension(const char *a, const char *b)
+static bool IsEqualExtension(std::string_view a, std::string_view b)
 {
-	std::size_t lena = std::strlen(a);
-	std::size_t lenb = std::strlen(b);
-	if(lena != lenb)
+	if(a.length() != b.length())
 	{
 		return false;
 	}
-	return mpt::CompareNoCaseAscii(a, b, lena) == 0;
+	return mpt::CompareNoCaseAscii(a, b) == 0;
 }
 
 
-bool CSoundFile::IsExtensionSupported(const char *ext)
+bool CSoundFile::IsExtensionSupported(std::string_view ext)
 {
-	if(ext == nullptr || ext[0] == 0)
+	if(ext.length() == 0)
 	{
 		return false;
 	}
libopenmpt-api-cpp17-v6.patch (52,445 bytes)   
Has the bug occurred in previous versions?
Tested code revision (in case you know it)

Relationships

related to 0001240 acknowledgedmanx Modernize C++ API 

Activities

manx

manx

2019-10-03 08:41

administrator   ~0004102

Proving overloads for existing string API functions for string_view can break existing code if it relies on implicit conversion because that would now be ambiguous. We cannot solve all possible cases without relying on template functions with std::enable_if here (which would be ugly), however we could additionally add overloads for the common case of string literals (and cstr). In any case, this would be a potentially breaking API change unless we use a new function name.

manx

manx

2019-10-14 18:27

administrator   ~0004109

v3 also adds string_view for is_extension_supported()

Saga Musix

Saga Musix

2019-10-27 17:03

administrator   ~0004122

I think the "wrong ctl value type" approach used in the current patch doesn't scale well when it comes to maintainability. Adding one new ctl results in having to edit several other getter and setter functions.

I would suggest to have the ctls, as returned by get_ctls, stored in a static array. Then the structure of every ctl setter and getter could be modified like this:

  1. check against all ctls supported by this function
  2. do a std::find on the list of supported ctls, and if the ctl matches one of them, throw "wrong ctl value type".
  3. throw "unknown ctl"
manx

manx

2019-10-28 10:41

administrator   ~0004126

(untested so far)

Saga Musix

Saga Musix

2019-11-10 17:52

administrator   ~0004150

Looks good in general now. However, maybe we should rename the "floatingpoint" type to simply "float". Less to type, and it's a common enough abbreviation used in many programming languages anyway.

manx

manx

2019-11-17 09:17

administrator   ~0004154

I would rather stick with "boolean", "integer", "floatingpoint", "text" in the API because it emphasizes the fact that this is not a 1:1 mapping to similarly or identically mapped native language types.

"float" would suggest that the type is actually "float" instead of "double", which would be confusing. Similar reasoning applies to "int" vs "int64_t", and "boolean" vs "bool" (in particular in the C API because of C89 compatibility which does not even provide bool or _Bool).

Saga Musix

Saga Musix

2019-11-17 11:43

administrator   ~0004155

Fair enough. Patch looks okay then.

manx

manx

2019-12-14 15:12

administrator   ~0004161

libopenmpt-api-cpp17-v8.patch (55,347 bytes)   
Index: common/mptString.cpp
===================================================================
--- common/mptString.cpp	(revision 12356)
+++ common/mptString.cpp	(working copy)
@@ -1533,7 +1533,7 @@
 	return 0;
 }
 
-int CompareNoCaseAscii(const std::string &a, const std::string &b)
+int CompareNoCaseAscii(std::string_view a, std::string_view b)
 {
 	for(std::size_t i = 0; i < std::min(a.length(), b.length()); ++i)
 	{
@@ -1554,6 +1554,12 @@
 	return a.length() < b.length() ? -1 : 1;
 }
 
+int CompareNoCaseAscii(const std::string &a, const std::string &b)
+{
+	return CompareNoCaseAscii(std::string_view(a), std::string_view(b));
+}
+
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s)
Index: common/mptString.h
===================================================================
--- common/mptString.h	(revision 12356)
+++ common/mptString.h	(working copy)
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include <cstring>
 
@@ -545,8 +546,10 @@
 std::string ToUpperCaseAscii(std::string s);
 
 int CompareNoCaseAscii(const char *a, const char *b, std::size_t n);
+int CompareNoCaseAscii(std::string_view a, std::string_view b);
 int CompareNoCaseAscii(const std::string &a, const std::string &b);
 
+
 #if defined(MODPLUG_TRACKER)
 
 mpt::ustring ToLowerCase(const mpt::ustring &s);
Index: common/stdafx.h
===================================================================
--- common/stdafx.h	(revision 12356)
+++ common/stdafx.h	(working copy)
@@ -115,6 +115,7 @@
 // <algorithm>
 // <limits>
 // <string>
+// <string_view>
 // <type_traits>
 // <cstring>
 
Index: libopenmpt/libopenmpt.h
===================================================================
--- libopenmpt/libopenmpt.h	(revision 12356)
+++ libopenmpt/libopenmpt.h	(working copy)
@@ -1396,26 +1396,26 @@
  * \param mod The module handle to work on.
  * \return A semicolon-separated list containing all supported ctl keys.
  * \remarks Currently supported ctl values are:
- *          - load.skip_samples: Set to "1" to avoid loading samples into memory
- *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
- *          - load.skip_plugins: Set to "1" to avoid loading plugins
- *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
- *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
- *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
- *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *          - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong (integer): The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end (text): Chooses the behaviour when the end of song is reached:
  *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
  *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
  *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
- *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
- *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
- *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
- *          - render.resampler.emulate_amiga_type: Configures the filter type to use for the Amiga resampler. Supported values are:
+ *          - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.resampler.emulate_amiga_type (string): Configures the filter type to use for the Amiga resampler. Supported values are:
  *                    - "auto": Filter type is chosen by the library and might change. This is the default.
  *                    - "a500": Amiga A500 filter.
  *                    - "a1200": Amiga A1200 filter.
  *                    - "unfiltered": BLEP synthesis without model-specific filters. The LED filter is ignored by this setting. This filter mode is considered to be experimental and might change in the future.
- *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
- *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *          - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
  *                    - 0: No dithering.
  *                    - 1: Default mode. Chosen by OpenMPT code, might change.
  *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1422,6 +1422,7 @@
  *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
  */
 LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+
 /*! \brief Get current ctl value
  *
  * \param mod The module handle to work on.
@@ -1428,8 +1429,46 @@
  * \param ctl The ctl key whose value should be retrieved.
  * \return The associated ctl value, or NULL on failure.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_get_boolean(), openmpt_module_ctl_get_integer(), openmpt_module_ctl_get_floatingpoint(), or openmpt_module_ctl_get_text().
  */
-LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl );
+/*! \brief Get current ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl );
+
 /*! \brief Set ctl value
  *
  * \param mod The module handle to work on.
@@ -1437,8 +1476,49 @@
  * \param value The value that should be set.
  * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
  * \sa openmpt_module_get_ctls
+ * \deprecated Please use openmpt_module_ctl_set_boolean(), openmpt_module_ctl_set_integer(), openmpt_module_ctl_set_floatingpoint(), or openmpt_module_ctl_set_text().
  */
-LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+/*! \brief Set ctl boolean value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value );
+/*! \brief Set ctl integer value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value );
+/*! \brief Set ctl floatingpoint value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value );
+/*! \brief Set ctl string value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ * \since 0.5.0
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value );
 
 /* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
 
Index: libopenmpt/libopenmpt.hpp
===================================================================
--- libopenmpt/libopenmpt.hpp	(revision 12356)
+++ libopenmpt/libopenmpt.hpp	(working copy)
@@ -17,6 +17,7 @@
 #include <iostream>
 #include <map>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <cstddef>
@@ -232,8 +233,16 @@
 /*!
   \param extension file extension to query without a leading dot. The case is ignored.
   \return true if the extension is supported by libopenmpt, false otherwise.
+  \deprecated Please use openmpt::is_extension_supported2().
 */
-LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+  \since 0.5.0
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported2( std::string_view extension );
 
 //! Roughly scan the input stream to find out whether libopenmpt might be able to open it
 /*!
@@ -1027,26 +1036,26 @@
 	/*!
 	  \return A vector containing all supported ctl keys.
 	  \remarks Currently supported ctl values are:
-	           - load.skip_samples: Set to "1" to avoid loading samples into memory
-	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
-	           - load.skip_plugins: Set to "1" to avoid loading plugins
-	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
-	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
-	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
-	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	           - load.skip_samples (boolean): Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns (boolean): Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins (boolean): Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init (boolean): Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples (boolean): Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong (integer): The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end (text): Chooses the behaviour when the end of song is reached:
 	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
 	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
 	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
-	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
-	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
-	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
-	           - render.resampler.emulate_amiga_type: Configures the filter type to use for the Amiga resampler. Supported values are:
+	           - play.tempo_factor (floatingpoint): Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor (floatingpoint): Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga (boolean): Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.resampler.emulate_amiga_type (string): Configures the filter type to use for the Amiga resampler. Supported values are:
 	                     - "auto": Filter type is chosen by the library and might change. This is the default.
 	                     - "a500": Amiga A500 filter.
 	                     - "a1200": Amiga A1200 filter.
 	                     - "unfiltered": BLEP synthesis without model-specific filters. The LED filter is ignored by this setting. This filter mode is considered to be experimental and might change in the future.
-	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
-	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	           - render.opl.volume_factor (floatingpoint): Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither (integer): Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
 	                     - 0: No dithering.
 	                     - 1: Default mode. Chosen by OpenMPT code, might change.
 	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
@@ -1061,8 +1070,42 @@
 	  \param ctl The ctl key whose value should be retrieved.
 	  \return The associated ctl value.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_get_boolean(), openmpt::module::ctl_get_integer(), openmpt::module::ctl_get_floatingpoint(), or openmpt::module::ctl_get_text().
 	*/
-	std::string ctl_get( const std::string & ctl ) const;
+	LIBOPENMPT_ATTR_DEPRECATED std::string ctl_get( const std::string & ctl ) const;
+	//! Get current ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	bool ctl_get_boolean( std::string_view ctl ) const;
+	//! Get current ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::int64_t ctl_get_integer( std::string_view ctl ) const;
+	//! Get current ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	double ctl_get_floatingpoint( std::string_view ctl ) const;
+	//! Get current ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	std::string ctl_get_text( std::string_view ctl ) const;
+
 	//! Set ctl value
 	/*!
 	  \param ctl The ctl key whose value should be set.
@@ -1069,8 +1112,45 @@
 	  \param value The value that should be set.
 	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
 	  \sa openmpt::module::get_ctls
+	  \deprecated Please use openmpt::module::ctl_set_bool(), openmpt::module::ctl_set_int(), openmpt::module::ctl_set_float(), or openmpt::module::ctl_set_string().
 	*/
-	void ctl_set( const std::string & ctl, const std::string & value );
+	LIBOPENMPT_ATTR_DEPRECATED void ctl_set( const std::string & ctl, const std::string & value );
+	//! Set ctl boolean value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_boolean( std::string_view ctl, bool value );
+	//! Set ctl integer value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_integer( std::string_view ctl, std::int64_t value );
+	//! Set ctl floatingpoint value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_floatingpoint( std::string_view ctl, double value );
+	//! Set ctl text value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	  \since 0.5.0
+	*/
+	void ctl_set_text( std::string_view ctl, std::string_view value );
 
 	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
 
Index: libopenmpt/libopenmpt_c.cpp
===================================================================
--- libopenmpt/libopenmpt_c.cpp	(revision 12356)
+++ libopenmpt/libopenmpt_c.cpp	(working copy)
@@ -1278,6 +1278,46 @@
 	}
 	return NULL;
 }
+int openmpt_module_ctl_get_boolean( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_boolean( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int64_t openmpt_module_ctl_get_integer( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_integer( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+double openmpt_module_ctl_get_floatingpoint( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return mod->impl->ctl_get_floatingpoint( ctl );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0.0;
+}
+const char * openmpt_module_ctl_get_text( openmpt_module * mod, const char * ctl ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		return openmpt::strdup( mod->impl->ctl_get_text( ctl ).c_str() );
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return NULL;
+}
 
 int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value ) {
 	try {
@@ -1291,8 +1331,52 @@
 	}
 	return 0;
 }
+int openmpt_module_ctl_set_boolean( openmpt_module * mod, const char * ctl, int value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_boolean( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_integer( openmpt_module * mod, const char * ctl, int64_t value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_integer( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_floatingpoint( openmpt_module * mod, const char * ctl, double value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		mod->impl->ctl_set_floatingpoint( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
+int openmpt_module_ctl_set_text( openmpt_module * mod, const char * ctl, const char * value ) {
+	try {
+		openmpt::interface::check_soundfile( mod );
+		openmpt::interface::check_pointer( ctl );
+		openmpt::interface::check_pointer( value );
+		mod->impl->ctl_set_text( ctl, value );
+		return 1;
+	} catch ( ... ) {
+		openmpt::report_exception( __func__, mod );
+	}
+	return 0;
+}
 
-
 openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls ) {
 	try {
 		openmpt_module_ext * mod_ext = (openmpt_module_ext*)std::calloc( 1, sizeof( openmpt_module_ext ) );
Index: libopenmpt/libopenmpt_cxx.cpp
===================================================================
--- libopenmpt/libopenmpt_cxx.cpp	(revision 12356)
+++ libopenmpt/libopenmpt_cxx.cpp	(working copy)
@@ -126,6 +126,9 @@
 bool is_extension_supported( const std::string & extension ) {
 	return openmpt::module_impl::is_extension_supported( extension );
 }
+bool is_extension_supported( std::string_view extension ) {
+	return openmpt::module_impl::is_extension_supported( extension );
+}
 
 double could_open_probability( std::istream & stream, double effort, std::ostream & log ) {
 	return openmpt::module_impl::could_open_probability( stream, effort, openmpt::helper::make_unique<std_ostream_log>( log ) );
@@ -403,12 +406,38 @@
 std::vector<std::string> module::get_ctls() const {
 	return impl->get_ctls();
 }
+
 std::string module::ctl_get( const std::string & ctl ) const {
 	return impl->ctl_get( ctl );
 }
+bool module::ctl_get_boolean( std::string_view ctl ) const {
+	return impl->ctl_get_boolean( ctl );
+}
+std::int64_t module::ctl_get_integer( std::string_view ctl ) const {
+	return impl->ctl_get_integer( ctl );
+}
+double module::ctl_get_floatingpoint( std::string_view ctl ) const {
+	return impl->ctl_get_floatingpoint( ctl );
+}
+std::string module::ctl_get_text( std::string_view ctl ) const {
+	return impl->ctl_get_text( ctl );
+}
+
 void module::ctl_set( const std::string & ctl, const std::string & value ) {
 	impl->ctl_set( ctl, value );
 }
+void module::ctl_set_boolean( std::string_view ctl, bool value ) {
+	impl->ctl_set_boolean( ctl, value );
+}
+void module::ctl_set_integer( std::string_view ctl, std::int64_t value ) {
+	impl->ctl_set_integer( ctl, value );
+}
+void module::ctl_set_floatingpoint( std::string_view ctl, double value ) {
+	impl->ctl_set_floatingpoint( ctl, value );
+}
+void module::ctl_set_text( std::string_view ctl, std::string_view value ) {
+	impl->ctl_set_text( ctl, value );
+}
 
 module_ext::module_ext( std::istream & stream, std::ostream & log, const std::map< std::string, std::string > & ctls ) : ext_impl(0) {
 	ext_impl = new module_ext_impl( stream, openmpt::helper::make_unique<std_ostream_log>( log ), ctls );
Index: libopenmpt/libopenmpt_impl.cpp
===================================================================
--- libopenmpt/libopenmpt_impl.cpp	(revision 12356)
+++ libopenmpt/libopenmpt_impl.cpp	(working copy)
@@ -606,12 +606,9 @@
 	std::copy( extensions.begin(), extensions.end(), std::back_insert_iterator<std::vector<std::string> >( retval ) );
 	return retval;
 }
-bool module_impl::is_extension_supported( const char * extension ) {
+bool module_impl::is_extension_supported( std::string_view extension ) {
 	return CSoundFile::IsExtensionSupported( extension );
 }
-bool module_impl::is_extension_supported( const std::string & extension ) {
-	return CSoundFile::IsExtensionSupported( extension.c_str() );
-}
 double module_impl::could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ) {
 	try {
 		if ( effort >= 0.8 ) {
@@ -1569,23 +1566,35 @@
 	return format_and_highlight_pattern_row_channel( p, r, c, width, pad ).second;
 }
 
-std::vector<std::string> module_impl::get_ctls() const {
-	return {
-	  "load.skip_samples",
-	  "load.skip_patterns",
-	  "load.skip_plugins",
-	  "load.skip_subsongs_init",
-	  "seek.sync_samples",
-	  "subsong",
-	  "play.tempo_factor",
-	  "play.pitch_factor",
-	  "play.at_end",
-	  "render.resampler.emulate_amiga",
-	  "render.resampler.emulate_amiga_type",
-	  "render.opl.volume_factor",
-	  "dither",
+std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> module_impl::get_ctl_infos() const {
+	static constexpr ctl_info ctl_infos[] = {
+		{ "load.skip_samples", ctl_type::boolean },
+		{ "load.skip_patterns", ctl_type::boolean },
+		{ "load.skip_plugins", ctl_type::boolean },
+		{ "load.skip_subsongs_init", ctl_type::boolean },
+		{ "seek.sync_samples", ctl_type::boolean },
+		{ "subsong", ctl_type::integer },
+		{ "play.tempo_factor", ctl_type::floatingpoint },
+		{ "play.pitch_factor", ctl_type::floatingpoint },
+		{ "play.at_end", ctl_type::text },
+		{ "render.resampler.emulate_amiga", ctl_type::boolean },
+		{ "render.resampler.emulate_amiga_type", ctl_type::text },
+		{ "render.opl.volume_factor", ctl_type::floatingpoint },
+		{ "dither", ctl_type::integer }
 	};
+	return std::make_pair(std::begin(ctl_infos), std::end(ctl_infos));
 }
+
+std::vector<std::string> module_impl::get_ctls() const {
+	std::vector<std::string> result;
+	auto ctl_infos = get_ctl_infos();
+	result.reserve(std::distance(ctl_infos.first, ctl_infos.second));
+	for ( std::ptrdiff_t i = 0; i < std::distance(ctl_infos.first, ctl_infos.second); ++i ) {
+		result.push_back(ctl_infos.first[i].name);
+	}
+	return result;
+}
+
 std::string module_impl::ctl_get( std::string ctl, bool throw_if_unknown ) const {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
@@ -1600,20 +1609,189 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl);
+		} else {
+			return std::string();
+		}
+	}
+	std::string result;
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			return mpt::fmt::val( ctl_get_boolean( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::integer:
+			return mpt::fmt::val( ctl_get_integer( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::floatingpoint:
+			return mpt::fmt::val( ctl_get_floatingpoint( ctl, throw_if_unknown ) );
+			break;
+		case ctl_type::text:
+			return ctl_get_text( ctl, throw_if_unknown );
+			break;
+	}
+	return result;
+}
+bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return false;
+		}
+	}
+	if ( found_ctl->type != ctl_type::boolean ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
 	if ( ctl == "" ) {
 		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		return mpt::fmt::val( m_ctl_load_skip_samples );
+		return m_ctl_load_skip_samples;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		return mpt::fmt::val( m_ctl_load_skip_patterns );
+		return m_ctl_load_skip_patterns;
 	} else if ( ctl == "load.skip_plugins" ) {
-		return mpt::fmt::val( m_ctl_load_skip_plugins );
+		return m_ctl_load_skip_plugins;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		return mpt::fmt::val( m_ctl_load_skip_subsongs_init );
+		return m_ctl_load_skip_subsongs_init;
 	} else if ( ctl == "seek.sync_samples" ) {
-		return mpt::fmt::val( m_ctl_seek_sync_samples );
+		return m_ctl_seek_sync_samples;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		return ( m_sndFile->m_Resampler.m_Settings.emulateAmiga != Resampling::AmigaFilter::Off );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return false;
+	}
+}
+std::int64_t module_impl::ctl_get_integer( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::integer ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "subsong" ) {
-		return mpt::fmt::val( get_selected_subsong() );
+		return get_selected_subsong();
+	} else if ( ctl == "dither" ) {
+		return static_cast<int>( m_Dither->GetMode() );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0;
+	}
+}
+double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return 0.0;
+		}
+	}
+	if ( found_ctl->type != ctl_type::floatingpoint ) {
+		throw openmpt::exception("wrong ctl value type");
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
+	} else if ( ctl == "play.tempo_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return 65536.0 / m_sndFile->m_nTempoFactor;
+	} else if ( ctl == "play.pitch_factor" ) {
+		if ( !is_loaded() ) {
+			return 1.0;
+		}
+		return m_sndFile->m_nFreqFactor / 65536.0;
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+		return 0.0;
+	}
+}
+std::string module_impl::ctl_get_text( std::string_view ctl, bool throw_if_unknown ) const {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl");
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl));
+		} else {
+			return std::string();
+		}
+	}
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl");
 	} else if ( ctl == "play.at_end" ) {
 		switch ( m_ctl_play_at_end )
 		{
@@ -1626,18 +1804,6 @@
 		default:
 			return std::string();
 		}
-	} else if ( ctl == "play.tempo_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
-		}
-		return mpt::fmt::val( 65536.0 / m_sndFile->m_nTempoFactor );
-	} else if ( ctl == "play.pitch_factor" ) {
-		if ( !is_loaded() ) {
-			return "1.0";
-		}
-		return mpt::fmt::val( m_sndFile->m_nFreqFactor / 65536.0 );
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		return mpt::fmt::val( m_sndFile->m_Resampler.m_Settings.emulateAmiga != Resampling::AmigaFilter::Off );
 	} else if ( ctl == "render.resampler.emulate_amiga_type" ) {
 		switch ( m_ctl_render_resampler_emulate_amiga_type ) {
 			case amiga_filter_type::a500:
@@ -1649,21 +1815,54 @@
 			case amiga_filter_type::auto_filter:
 				return "auto";
 			default:
-				return {};
+				return std::string();
 		}
-	} else if ( ctl == "render.opl.volume_factor" ) {
-		return mpt::fmt::val( static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		return mpt::fmt::val( static_cast<int>( m_Dither->GetMode() ) );
 	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl);
+		MPT_ASSERT_NOTREACHED();
+		return std::string();
+	}
+}
+
+void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + value);
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
 		} else {
-			return {};
+			return;
 		}
 	}
+	switch ( found_ctl->type ) {
+		case ctl_type::boolean:
+			ctl_set_boolean( ctl, ConvertStrTo<bool>( value ), throw_if_unknown );
+			break;
+		case ctl_type::integer:
+			ctl_set_integer( ctl, ConvertStrTo<int64>( value ), throw_if_unknown );
+			break;
+		case ctl_type::floatingpoint:
+			ctl_set_floatingpoint( ctl, ConvertStrTo<double>( value ), throw_if_unknown );
+			break;
+		case ctl_type::text:
+			ctl_set_text( ctl, value, throw_if_unknown );
+			break;
+	}
 }
-void module_impl::ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown ) {
+void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown ) {
 	if ( !ctl.empty() ) {
 		// cppcheck false-positive
 		// cppcheck-suppress containerOutOfBounds
@@ -1677,35 +1876,113 @@
 			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
 	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
 	if ( ctl == "" ) {
-		throw openmpt::exception("empty ctl: := " + value);
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "load.skip_samples" || ctl == "load_skip_samples" ) {
-		m_ctl_load_skip_samples = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_samples = value;
 	} else if ( ctl == "load.skip_patterns" || ctl == "load_skip_patterns" ) {
-		m_ctl_load_skip_patterns = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_patterns = value;
 	} else if ( ctl == "load.skip_plugins" ) {
-		m_ctl_load_skip_plugins = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_plugins = value;
 	} else if ( ctl == "load.skip_subsongs_init" ) {
-		m_ctl_load_skip_subsongs_init = ConvertStrTo<bool>( value );
+		m_ctl_load_skip_subsongs_init = value;
 	} else if ( ctl == "seek.sync_samples" ) {
-		m_ctl_seek_sync_samples = ConvertStrTo<bool>( value );
+		m_ctl_seek_sync_samples = value;
+	} else if ( ctl == "render.resampler.emulate_amiga" ) {
+		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
+		const bool enabled = value;
+		if ( enabled )
+			newsettings.emulateAmiga = translate_amiga_filter_type( m_ctl_render_resampler_emulate_amiga_type );
+		else
+			newsettings.emulateAmiga = Resampling::AmigaFilter::Off;
+		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
+			m_sndFile->SetResamplerSettings( newsettings );
+		}
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
+		} else {
+			return;
+		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "subsong" ) {
-		select_subsong( ConvertStrTo<int32>( value ) );
-	} else if ( ctl == "play.at_end" ) {
-		if ( value == "fadeout" ) {
-			m_ctl_play_at_end = song_end_action::fadeout_song;
-		} else if(value == "continue") {
-			m_ctl_play_at_end = song_end_action::continue_song;
-		} else if(value == "stop") {
-			m_ctl_play_at_end = song_end_action::stop_song;
+		select_subsong( mpt::saturate_cast<int32>( value ) );
+	} else if ( ctl == "dither" ) {
+		int dither = mpt::saturate_cast<int>( value );
+		if ( dither < 0 || dither >= NumDitherModes ) {
+			dither = DitherDefault;
+		}
+		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
+		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + mpt::fmt::val(value));
 		} else {
-			throw openmpt::exception("unknown song end action:" + value);
+			return;
 		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + mpt::fmt::val( value ) );
 	} else if ( ctl == "play.tempo_factor" ) {
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid tempo factor");
 		}
@@ -1715,22 +1992,55 @@
 		if ( !is_loaded() ) {
 			return;
 		}
-		double factor = ConvertStrTo<double>( value );
+		double factor = value;
 		if ( factor <= 0.0 || factor > 4.0 ) {
 			throw openmpt::exception("invalid pitch factor");
 		}
 		m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
 		m_sndFile->RecalculateSamplesPerTick();
-	} else if ( ctl == "render.resampler.emulate_amiga" ) {
-		CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
-		const bool enabled = ConvertStrTo<bool>( value );
-		if ( enabled )
-			newsettings.emulateAmiga = translate_amiga_filter_type( m_ctl_render_resampler_emulate_amiga_type );
-		else
-			newsettings.emulateAmiga = Resampling::AmigaFilter::Off;
-		if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
-			m_sndFile->SetResamplerSettings( newsettings );
+	} else if ( ctl == "render.opl.volume_factor" ) {
+		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
+	} else {
+		MPT_ASSERT_NOTREACHED();
+	}
+}
+void module_impl::ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown ) {
+	if ( !ctl.empty() ) {
+		// cppcheck false-positive
+		// cppcheck-suppress containerOutOfBounds
+		char rightmost = ctl.back();
+		if ( rightmost == '!' || rightmost == '?' ) {
+			if ( rightmost == '!' ) {
+				throw_if_unknown = true;
+			} else if ( rightmost == '?' ) {
+				throw_if_unknown = false;
+			}
+			ctl = ctl.substr( 0, ctl.length() - 1 );
 		}
+	}
+	auto found_ctl = std::find_if(get_ctl_infos().first, get_ctl_infos().second, [&](const ctl_info & info) -> bool { return info.name == ctl; });
+	if ( found_ctl == get_ctl_infos().second ) {
+		if ( ctl == "" ) {
+			throw openmpt::exception("empty ctl: := " + std::string( value ) );
+		} else if ( throw_if_unknown ) {
+			throw openmpt::exception("unknown ctl: " + std::string(ctl) + " := " + std::string(value));
+		} else {
+			return;
+		}
+	}
+
+	if ( ctl == "" ) {
+		throw openmpt::exception("empty ctl: := " + std::string( value ) );
+	} else if ( ctl == "play.at_end" ) {
+		if ( value == "fadeout" ) {
+			m_ctl_play_at_end = song_end_action::fadeout_song;
+		} else if(value == "continue") {
+			m_ctl_play_at_end = song_end_action::continue_song;
+		} else if(value == "stop") {
+			m_ctl_play_at_end = song_end_action::stop_song;
+		} else {
+			throw openmpt::exception("unknown song end action:" + std::string(value));
+		}
 	} else if ( ctl == "render.resampler.emulate_amiga_type" ) {
 		if ( value == "a500" ) {
 			m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::a500;
@@ -1743,7 +2053,6 @@
 		} else {
 			throw openmpt::exception( "invalid amiga filter type" );
 		}
-
 		if ( m_sndFile->m_Resampler.m_Settings.emulateAmiga != Resampling::AmigaFilter::Off ) {
 			CResamplerSettings newsettings = m_sndFile->m_Resampler.m_Settings;
 			newsettings.emulateAmiga = translate_amiga_filter_type( m_ctl_render_resampler_emulate_amiga_type );
@@ -1751,20 +2060,8 @@
 				m_sndFile->SetResamplerSettings( newsettings );
 			}
 		}
-	} else if ( ctl == "render.opl.volume_factor" ) {
-		m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( ConvertStrTo<double>( value ) * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) );
-	} else if ( ctl == "dither" ) {
-		int dither = ConvertStrTo<int>( value );
-		if ( dither < 0 || dither >= NumDitherModes ) {
-			dither = DitherDefault;
-		}
-		m_Dither->SetMode( static_cast<DitherMode>( dither ) );
 	} else {
-		if ( throw_if_unknown ) {
-			throw openmpt::exception("unknown ctl: " + ctl + " := " + value);
-		} else {
-			// ignore
-		}
+		MPT_ASSERT_NOTREACHED();
 	}
 }
 
Index: libopenmpt/libopenmpt_impl.hpp
===================================================================
--- libopenmpt/libopenmpt_impl.hpp	(revision 12356)
+++ libopenmpt/libopenmpt_impl.hpp	(working copy)
@@ -15,6 +15,7 @@
 
 #include <iosfwd>
 #include <memory>
+#include <utility>
 
 #if defined(_MSC_VER)
 #pragma warning(push)
@@ -99,6 +100,17 @@
 
 	static constexpr std::int32_t all_subsongs = -1;
 
+	enum class ctl_type {
+		boolean,
+		integer,
+		floatingpoint,
+		text,
+	};
+	struct ctl_info {
+		const char * name;
+		ctl_type type;
+	};
+
 	std::unique_ptr<log_interface> m_Log;
 	std::unique_ptr<log_forwarder> m_LogForwarder;
 	std::int32_t m_current_subsong;
@@ -139,8 +151,7 @@
 	static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log );
 public:
 	static std::vector<std::string> get_supported_extensions();
-	static bool is_extension_supported( const char * extension );
-	static bool is_extension_supported( const std::string & extension );
+	static bool is_extension_supported( std::string_view extension );
 	static double could_open_probability( callback_stream_wrapper stream, double effort, std::unique_ptr<log_interface> log );
 	static double could_open_probability( std::istream & stream, double effort, std::unique_ptr<log_interface> log );
 	static std::size_t probe_file_header_get_recommended_size();
@@ -215,9 +226,18 @@
 	std::string highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const;
 	std::string format_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
 	std::string highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const;
+	std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> get_ctl_infos() const;
 	std::vector<std::string> get_ctls() const;
 	std::string ctl_get( std::string ctl, bool throw_if_unknown = true ) const;
+	bool ctl_get_boolean( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::int64_t ctl_get_integer( std::string_view ctl, bool throw_if_unknown = true ) const;
+	double ctl_get_floatingpoint( std::string_view ctl, bool throw_if_unknown = true ) const;
+	std::string ctl_get_text( std::string_view ctl, bool throw_if_unknown = true ) const;
 	void ctl_set( std::string ctl, const std::string & value, bool throw_if_unknown = true );
+	void ctl_set_boolean( std::string_view ctl, bool value, bool throw_if_unknown = true );
+	void ctl_set_integer( std::string_view ctl, std::int64_t value, bool throw_if_unknown = true );
+	void ctl_set_floatingpoint( std::string_view ctl, double value, bool throw_if_unknown = true );
+	void ctl_set_text( std::string_view ctl, std::string_view value, bool throw_if_unknown = true );
 }; // class module_impl
 
 namespace helper {
Index: openmpt123/openmpt123.cpp
===================================================================
--- openmpt123/openmpt123.cpp	(revision 12356)
+++ openmpt123/openmpt123.cpp	(working copy)
@@ -736,33 +736,6 @@
 }
 
 
-template < typename T, typename Tmod >
-T ctl_get( Tmod & mod, const std::string & ctl ) {
-	T result = T();
-	try {
-		std::istringstream str;
-		str.imbue( std::locale::classic() );
-		str.str( mod.ctl_get( ctl ) );
-		str >> std::fixed >> std::setprecision(16) >> result;
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return result;
-}
-
-template < typename T, typename Tmod >
-void ctl_set( Tmod & mod, const std::string & ctl, const T & val ) {
-	try {
-		std::ostringstream str;
-		str.imbue( std::locale::classic() );
-		str << std::fixed << std::setprecision(16) << val;
-		mod.ctl_set( ctl, str.str() );
-	} catch ( const openmpt::exception & ) {
-		// ignore
-	}
-	return;
-}
-
 template < typename Tmod >
 static void apply_mod_settings( commandlineflags & flags, Tmod & mod ) {
 	flags.separation = std::max( flags.separation, std::int32_t(   0 ) );
@@ -778,12 +751,17 @@
 	mod.set_render_param( openmpt::module::RENDER_STEREOSEPARATION_PERCENT, flags.separation );
 	mod.set_render_param( openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH, flags.filtertaps );
 	mod.set_render_param( openmpt::module::RENDER_VOLUMERAMPING_STRENGTH, flags.ramping );
-	ctl_set( mod, "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
-	ctl_set( mod, "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
-	std::ostringstream dither_str;
-	dither_str.imbue( std::locale::classic() );
-	dither_str << flags.dither;
-	mod.ctl_set( "dither", dither_str.str() );
+	try {
+		mod.ctl_set_floatingpoint( "play.tempo_factor", tempo_flag_to_double( flags.tempo ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	try {
+		mod.ctl_set_floatingpoint( "play.pitch_factor", pitch_flag_to_double( flags.pitch ) );
+	} catch ( const openmpt::exception & ) {
+		// ignore
+	}
+	mod.ctl_set_integer( "dither", flags.dither );
 }
 
 struct prev_file { int count; prev_file( int c ) : count(c) { } };
Index: soundlib/Sndfile.h
===================================================================
--- soundlib/Sndfile.h	(revision 12356)
+++ soundlib/Sndfile.h	(working copy)
@@ -824,7 +824,7 @@
 	bool ReadWAV(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule);
 
 	static std::vector<const char *> GetSupportedExtensions(bool otherFormats);
-	static bool IsExtensionSupported(const char *ext); // UTF8, casing of ext is ignored
+	static bool IsExtensionSupported(std::string_view ext); // UTF8, casing of ext is ignored
 	static mpt::ustring ModContainerTypeToString(MODCONTAINERTYPE containertype);
 	static mpt::ustring ModContainerTypeToTracker(MODCONTAINERTYPE containertype);
 
Index: soundlib/Tables.cpp
===================================================================
--- soundlib/Tables.cpp	(revision 12356)
+++ soundlib/Tables.cpp	(working copy)
@@ -176,21 +176,19 @@
 }
 
 
-static bool IsEqualExtension(const char *a, const char *b)
+static bool IsEqualExtension(std::string_view a, std::string_view b)
 {
-	std::size_t lena = std::strlen(a);
-	std::size_t lenb = std::strlen(b);
-	if(lena != lenb)
+	if(a.length() != b.length())
 	{
 		return false;
 	}
-	return mpt::CompareNoCaseAscii(a, b, lena) == 0;
+	return mpt::CompareNoCaseAscii(a, b) == 0;
 }
 
 
-bool CSoundFile::IsExtensionSupported(const char *ext)
+bool CSoundFile::IsExtensionSupported(std::string_view ext)
 {
-	if(ext == nullptr || ext[0] == 0)
+	if(ext.length() == 0)
 	{
 		return false;
 	}
libopenmpt-api-cpp17-v8.patch (55,347 bytes)   
manx

manx

2019-12-19 09:09

administrator   ~0004163

Implemented in r12367, libopenmpt 0.5.0-pre.12.

Issue History

Date Modified Username Field Change
2019-07-29 20:47 manx New Issue
2019-07-29 20:47 manx Status new => assigned
2019-07-29 20:47 manx Assigned To => manx
2019-07-29 20:47 manx Relationship added related to 0001240
2019-10-03 08:41 manx Note Added: 0004102
2019-10-14 18:25 manx File Added: libopenmpt-api-cpp17-v3.patch
2019-10-14 18:27 manx Note Added: 0004109
2019-10-23 07:39 manx File Added: libopenmpt-api-cpp17-v4.patch
2019-10-27 17:03 Saga Musix Note Added: 0004122
2019-10-28 10:41 manx File Added: libopenmpt-api-cpp17-v5.patch
2019-10-28 10:41 manx Note Added: 0004126
2019-11-04 15:25 manx File Added: libopenmpt-api-cpp17-v6.patch
2019-11-10 17:52 Saga Musix Note Added: 0004150
2019-11-17 09:17 manx Note Added: 0004154
2019-11-17 11:43 Saga Musix Note Added: 0004155
2019-12-14 15:12 manx Note Added: 0004161
2019-12-14 15:12 manx File Added: libopenmpt-api-cpp17-v8.patch
2019-12-19 09:09 manx Status assigned => resolved
2019-12-19 09:09 manx Resolution open => fixed
2019-12-19 09:09 manx Fixed in Version => OpenMPT 1.29.01.00 / libopenmpt 0.5.0 (upgrade first)
2019-12-19 09:09 manx Note Added: 0004163