Updated libopenmpt to version 0.3.12.
parent
7499edd8ca
commit
dec3e27406
|
@ -348,9 +348,12 @@ endif
|
||||||
|
|
||||||
ifeq ($(HOST),unix)
|
ifeq ($(HOST),unix)
|
||||||
|
|
||||||
|
ifeq ($(IS_CROSS),1)
|
||||||
|
else
|
||||||
ifeq ($(shell help2man --version > /dev/null 2>&1 && echo yes ),yes)
|
ifeq ($(shell help2man --version > /dev/null 2>&1 && echo yes ),yes)
|
||||||
MPT_WITH_HELP2MAN := 1
|
MPT_WITH_HELP2MAN := 1
|
||||||
endif
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(shell doxygen --version > /dev/null 2>&1 && echo yes ),yes)
|
ifeq ($(shell doxygen --version > /dev/null 2>&1 && echo yes ),yes)
|
||||||
MPT_WITH_DOXYGEN := 1
|
MPT_WITH_DOXYGEN := 1
|
||||||
|
|
|
@ -181,13 +181,16 @@ For detailed requirements, see `libopenmpt/dox/quickstart.md`.
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
make CONFIG=emscripten
|
make CONFIG=emscripten # for emscripten >= 1.38.1
|
||||||
|
|
||||||
|
make CONFIG=emscripten-old # for emscripten < 1.38.0
|
||||||
|
|
||||||
Running the test suite on the command line is also supported by using
|
Running the test suite on the command line is also supported by using
|
||||||
node.js. Version 0.10.25 or greater has been tested. Earlier versions
|
node.js. Version 0.10.25 or greater has been tested. Earlier versions
|
||||||
might or might not work. Depending on how your distribution calls the
|
might or might not work. Depending on how your distribution calls the
|
||||||
`node.js` binary, you might have to edit
|
`node.js` binary, you might have to edit
|
||||||
`build/make/config-emscripten.mk`.
|
`build/make/config-emscripten.mk` or
|
||||||
|
`build/make/config-emscripten-old.mk`.
|
||||||
|
|
||||||
- Haiku:
|
- Haiku:
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,11 @@
|
||||||
#elif defined(_MSC_VER)
|
#elif defined(_MSC_VER)
|
||||||
|
|
||||||
#define MPT_COMPILER_MSVC 1
|
#define MPT_COMPILER_MSVC 1
|
||||||
#if (_MSC_VER >= 1913)
|
#if (_MSC_VER >= 1915)
|
||||||
|
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,8)
|
||||||
|
#elif (_MSC_VER >= 1914)
|
||||||
|
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,7)
|
||||||
|
#elif (_MSC_VER >= 1913)
|
||||||
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,6)
|
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,6)
|
||||||
#elif (_MSC_VER >= 1912)
|
#elif (_MSC_VER >= 1912)
|
||||||
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,5)
|
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017,5)
|
||||||
|
|
|
@ -508,7 +508,7 @@ static MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i)
|
||||||
template<std::size_t hihi, std::size_t hilo, std::size_t lohi, std::size_t lolo>
|
template<std::size_t hihi, std::size_t hilo, std::size_t lohi, std::size_t lolo>
|
||||||
struct IEEE754binary32Emulated
|
struct IEEE754binary32Emulated
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
typedef IEEE754binary32Emulated<hihi,hilo,lohi,lolo> self_t;
|
typedef IEEE754binary32Emulated<hihi,hilo,lohi,lolo> self_t;
|
||||||
mpt::byte bytes[4];
|
mpt::byte bytes[4];
|
||||||
public:
|
public:
|
||||||
|
@ -569,7 +569,7 @@ public:
|
||||||
template<std::size_t hihihi, std::size_t hihilo, std::size_t hilohi, std::size_t hilolo, std::size_t lohihi, std::size_t lohilo, std::size_t lolohi, std::size_t lololo>
|
template<std::size_t hihihi, std::size_t hihilo, std::size_t hilohi, std::size_t hilolo, std::size_t lohihi, std::size_t lohilo, std::size_t lolohi, std::size_t lololo>
|
||||||
struct IEEE754binary64Emulated
|
struct IEEE754binary64Emulated
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
typedef IEEE754binary64Emulated<hihihi,hihilo,hilohi,hilolo,lohihi,lohilo,lolohi,lololo> self_t;
|
typedef IEEE754binary64Emulated<hihihi,hihilo,hilohi,hilolo,lohihi,lohilo,lolohi,lololo> self_t;
|
||||||
mpt::byte bytes[8];
|
mpt::byte bytes[8];
|
||||||
public:
|
public:
|
||||||
|
@ -655,7 +655,7 @@ MPT_BINARY_STRUCT(IEEE754binary64EmulatedLE, 8)
|
||||||
|
|
||||||
struct IEEE754binary32Native
|
struct IEEE754binary32Native
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
float32 value;
|
float32 value;
|
||||||
public:
|
public:
|
||||||
MPT_FORCEINLINE mpt::byte GetByte(std::size_t i) const
|
MPT_FORCEINLINE mpt::byte GetByte(std::size_t i) const
|
||||||
|
@ -721,7 +721,7 @@ public:
|
||||||
|
|
||||||
struct IEEE754binary64Native
|
struct IEEE754binary64Native
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
float64 value;
|
float64 value;
|
||||||
public:
|
public:
|
||||||
MPT_FORCEINLINE mpt::byte GetByte(std::size_t i) const
|
MPT_FORCEINLINE mpt::byte GetByte(std::size_t i) const
|
||||||
|
@ -848,7 +848,7 @@ struct packed
|
||||||
public:
|
public:
|
||||||
typedef T base_type;
|
typedef T base_type;
|
||||||
typedef Tendian endian_type;
|
typedef Tendian endian_type;
|
||||||
private:
|
public:
|
||||||
#if MPT_PLATFORM_ENDIAN_KNOWN
|
#if MPT_PLATFORM_ENDIAN_KNOWN
|
||||||
mpt::byte data[sizeof(base_type)];
|
mpt::byte data[sizeof(base_type)];
|
||||||
#else // !MPT_PLATFORM_ENDIAN_KNOWN
|
#else // !MPT_PLATFORM_ENDIAN_KNOWN
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#if MPT_COMPILER_MSVC
|
#if MPT_COMPILER_MSVC
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable:4091) // 'typedef ': ignored on left of '' when no variable is declared
|
#pragma warning(disable:4091) // 'typedef ': ignored on left of '' when no variable is declared
|
||||||
|
|
|
@ -18,8 +18,8 @@ OPENMPT_NAMESPACE_BEGIN
|
||||||
//Version definitions. The only thing that needs to be changed when changing version number.
|
//Version definitions. The only thing that needs to be changed when changing version number.
|
||||||
#define VER_MAJORMAJOR 1
|
#define VER_MAJORMAJOR 1
|
||||||
#define VER_MAJOR 27
|
#define VER_MAJOR 27
|
||||||
#define VER_MINOR 08
|
#define VER_MINOR 10
|
||||||
#define VER_MINORMINOR 02
|
#define VER_MINORMINOR 00
|
||||||
|
|
||||||
//Version string. For example "1.17.02.28"
|
//Version string. For example "1.17.02.28"
|
||||||
#define MPT_VERSION_STR VER_STRINGIZE(VER_MAJORMAJOR) "." VER_STRINGIZE(VER_MAJOR) "." VER_STRINGIZE(VER_MINOR) "." VER_STRINGIZE(VER_MINORMINOR)
|
#define MPT_VERSION_STR VER_STRINGIZE(VER_MAJORMAJOR) "." VER_STRINGIZE(VER_MAJOR) "." VER_STRINGIZE(VER_MINOR) "." VER_STRINGIZE(VER_MINORMINOR)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
miniz DEFLATE implementation.
|
miniz DEFLATE implementation.
|
||||||
https://github.com/richgel999/miniz
|
https://github.com/richgel999/miniz
|
||||||
2.0.6 beta
|
2.0.7
|
||||||
Modifications for OpenMPT:
|
Modifications for OpenMPT:
|
||||||
* #define MINIZ_NO_STDIO has been set because OpenMPT does not need stdio
|
* #define MINIZ_NO_STDIO has been set because OpenMPT does not need stdio
|
||||||
functionality and miniz relies on secure-CRT file i/o functions in windows
|
functionality and miniz relies on secure-CRT file i/o functions in windows
|
||||||
|
|
|
@ -1936,6 +1936,8 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun
|
||||||
d->m_pSrc = NULL;
|
d->m_pSrc = NULL;
|
||||||
d->m_src_buf_left = 0;
|
d->m_src_buf_left = 0;
|
||||||
d->m_out_buf_ofs = 0;
|
d->m_out_buf_ofs = 0;
|
||||||
|
if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
|
||||||
|
MZ_CLEAR_OBJ(d->m_dict);
|
||||||
memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
|
memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
|
||||||
memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
|
memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
|
||||||
return TDEFL_STATUS_OKAY;
|
return TDEFL_STATUS_OKAY;
|
||||||
|
@ -3016,7 +3018,7 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
|
||||||
#define MZ_FFLUSH fflush
|
#define MZ_FFLUSH fflush
|
||||||
#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
|
#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
|
||||||
#define MZ_DELETE_FILE remove
|
#define MZ_DELETE_FILE remove
|
||||||
#elif defined(__APPLE__) && _LARGEFILE64_SOURCE
|
#elif defined(__APPLE__)
|
||||||
#ifndef MINIZ_NO_TIME
|
#ifndef MINIZ_NO_TIME
|
||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -3882,7 +3884,10 @@ mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename,
|
||||||
/* TODO: Better sanity check archive_size and the # of actual remaining bytes */
|
/* TODO: Better sanity check archive_size and the # of actual remaining bytes */
|
||||||
|
|
||||||
if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
|
if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
|
||||||
|
{
|
||||||
|
MZ_FCLOSE(pFile);
|
||||||
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
|
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
|
||||||
|
}
|
||||||
|
|
||||||
if (!mz_zip_reader_init_internal(pZip, flags))
|
if (!mz_zip_reader_init_internal(pZip, flags))
|
||||||
{
|
{
|
||||||
|
@ -6032,14 +6037,15 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n
|
||||||
mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
|
mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
|
||||||
mz_uint16 bit_flags = 0;
|
mz_uint16 bit_flags = 0;
|
||||||
|
|
||||||
|
if ((int)level_and_flags < 0)
|
||||||
|
level_and_flags = MZ_DEFAULT_LEVEL;
|
||||||
|
|
||||||
if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
|
if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
|
||||||
bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
|
bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
|
||||||
|
|
||||||
if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
|
if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
|
||||||
bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;
|
bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;
|
||||||
|
|
||||||
if ((int)level_and_flags < 0)
|
|
||||||
level_and_flags = MZ_DEFAULT_LEVEL;
|
|
||||||
level = level_and_flags & 0xF;
|
level = level_and_flags & 0xF;
|
||||||
store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
|
store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* miniz.c 2.0.6 beta - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
|
/* miniz.c 2.0.7 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
|
||||||
See "unlicense" statement at the end of this file.
|
See "unlicense" statement at the end of this file.
|
||||||
Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
|
Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
|
||||||
Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt
|
Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt
|
||||||
|
@ -247,11 +247,11 @@ enum
|
||||||
MZ_DEFAULT_COMPRESSION = -1
|
MZ_DEFAULT_COMPRESSION = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MZ_VERSION "10.0.1"
|
#define MZ_VERSION "10.0.2"
|
||||||
#define MZ_VERNUM 0xA010
|
#define MZ_VERNUM 0xA020
|
||||||
#define MZ_VER_MAJOR 10
|
#define MZ_VER_MAJOR 10
|
||||||
#define MZ_VER_MINOR 0
|
#define MZ_VER_MINOR 0
|
||||||
#define MZ_VER_REVISION 1
|
#define MZ_VER_REVISION 2
|
||||||
#define MZ_VER_SUBREVISION 0
|
#define MZ_VER_SUBREVISION 0
|
||||||
|
|
||||||
#ifndef MINIZ_NO_ZLIB_APIS
|
#ifndef MINIZ_NO_ZLIB_APIS
|
||||||
|
|
|
@ -5,6 +5,37 @@ Changelog {#changelog}
|
||||||
For fully detailed change log, please see the source repository directly. This
|
For fully detailed change log, please see the source repository directly. This
|
||||||
is just a high-level summary.
|
is just a high-level summary.
|
||||||
|
|
||||||
|
### libopenmpt 0.3.12 (2018-09-24)
|
||||||
|
|
||||||
|
* [**Bug**] openmpt123: Prevent libsdl2 and libsdl from being enabled at the
|
||||||
|
same time because they conflict with each other.
|
||||||
|
* [**Bug**] Make building the JavaScript package work with Emscripten version
|
||||||
|
1.38.1 or later by disabling WebAssembly generation which generates a
|
||||||
|
different set of output files. Emscripten 1.38.1 changed the default to
|
||||||
|
WebAssembly.
|
||||||
|
* [**Bug**] libmodplug: Setting `SNDMIX_NORESAMPLING` in the C++ API always
|
||||||
|
resulted in linear interpolation instead of nearest neighbour.
|
||||||
|
|
||||||
|
* [**Change**] The old Emscripten configuration which is compatible with
|
||||||
|
Emscripten before 1.38.0 has been renamed to `CONFIG=emscripten-old`.
|
||||||
|
|
||||||
|
* libopenmpt now compiles without warnings with GCC 8.
|
||||||
|
|
||||||
|
* Jump commands on the same row as the end of a pattern loop covering the
|
||||||
|
restart position of the module could cause the module to loop even when
|
||||||
|
looping was disabled.
|
||||||
|
* MO3: Reject overly long MP3 and Vorbis samples.
|
||||||
|
* `play_note` from the libopenmpt_ext interface sometimes silenced the start
|
||||||
|
of a triggered sample.
|
||||||
|
|
||||||
|
### libopenmpt 0.3.11 (2018-07-28)
|
||||||
|
|
||||||
|
* [**Sec**] Crash with some malformed custom tunings in MPTM files (r10615).
|
||||||
|
|
||||||
|
* Channels whose volume envelope was playing at volume 0 while being moved to
|
||||||
|
a NNA background channel were cut off completely since libopenmpt 0.3.8.
|
||||||
|
* AMF (ASYLUM): Convert 7-bit panning to 8-bit panning for playback.
|
||||||
|
|
||||||
### libopenmpt 0.3.10 (2018-06-17)
|
### libopenmpt 0.3.10 (2018-06-17)
|
||||||
|
|
||||||
* [**Bug**] Internal mixer state was not initialized properly when initially
|
* [**Bug**] Internal mixer state was not initialized properly when initially
|
||||||
|
|
|
@ -189,19 +189,19 @@ LIBOPENMPT_API uint32_t openmpt_get_library_version(void);
|
||||||
*/
|
*/
|
||||||
LIBOPENMPT_API uint32_t openmpt_get_core_version(void);
|
LIBOPENMPT_API uint32_t openmpt_get_core_version(void);
|
||||||
|
|
||||||
/*! Return a verbose library version string from openmpt_get_string(). \deprecated Please use \code "library_version" \endcode directly. */
|
/*! Return a verbose library version string from openmpt_get_string(). \deprecated Please use `"library_version"` directly. */
|
||||||
#define OPENMPT_STRING_LIBRARY_VERSION LIBOPENMPT_DEPRECATED_STRING( "library_version" )
|
#define OPENMPT_STRING_LIBRARY_VERSION LIBOPENMPT_DEPRECATED_STRING( "library_version" )
|
||||||
/*! Return a verbose library features string from openmpt_get_string(). \deprecated Please use \code "library_features" \endcode directly. */
|
/*! Return a verbose library features string from openmpt_get_string(). \deprecated Please use `"library_features"` directly. */
|
||||||
#define OPENMPT_STRING_LIBRARY_FEATURES LIBOPENMPT_DEPRECATED_STRING( "library_features" )
|
#define OPENMPT_STRING_LIBRARY_FEATURES LIBOPENMPT_DEPRECATED_STRING( "library_features" )
|
||||||
/*! Return a verbose OpenMPT core version string from openmpt_get_string(). \deprecated Please use \code "core_version" \endcode directly. */
|
/*! Return a verbose OpenMPT core version string from openmpt_get_string(). \deprecated Please use `"core_version"` directly. */
|
||||||
#define OPENMPT_STRING_CORE_VERSION LIBOPENMPT_DEPRECATED_STRING( "core_version" )
|
#define OPENMPT_STRING_CORE_VERSION LIBOPENMPT_DEPRECATED_STRING( "core_version" )
|
||||||
/*! Return information about the current build (e.g. the build date or compiler used) from openmpt_get_string(). \deprecated Please use \code "build" \endcode directly. */
|
/*! Return information about the current build (e.g. the build date or compiler used) from openmpt_get_string(). \deprecated Please use `"build"` directly. */
|
||||||
#define OPENMPT_STRING_BUILD LIBOPENMPT_DEPRECATED_STRING( "build" )
|
#define OPENMPT_STRING_BUILD LIBOPENMPT_DEPRECATED_STRING( "build" )
|
||||||
/*! Return all contributors from openmpt_get_string(). \deprecated Please use \code "credits" \endcode directly. */
|
/*! Return all contributors from openmpt_get_string(). \deprecated Please use `"credits"` directly. */
|
||||||
#define OPENMPT_STRING_CREDITS LIBOPENMPT_DEPRECATED_STRING( "credits" )
|
#define OPENMPT_STRING_CREDITS LIBOPENMPT_DEPRECATED_STRING( "credits" )
|
||||||
/*! Return contact information about libopenmpt from openmpt_get_string(). \deprecated Please use \code "contact" \endcode directly. */
|
/*! Return contact information about libopenmpt from openmpt_get_string(). \deprecated Please use `"contact"` directly. */
|
||||||
#define OPENMPT_STRING_CONTACT LIBOPENMPT_DEPRECATED_STRING( "contact" )
|
#define OPENMPT_STRING_CONTACT LIBOPENMPT_DEPRECATED_STRING( "contact" )
|
||||||
/*! Return the libopenmpt license from openmpt_get_string(). \deprecated Please use \code "license" \endcode directly. */
|
/*! Return the libopenmpt license from openmpt_get_string(). \deprecated Please use `"license"` directly. */
|
||||||
#define OPENMPT_STRING_LICENSE LIBOPENMPT_DEPRECATED_STRING( "license" )
|
#define OPENMPT_STRING_LICENSE LIBOPENMPT_DEPRECATED_STRING( "license" )
|
||||||
|
|
||||||
/*! \brief Free a string returned by libopenmpt
|
/*! \brief Free a string returned by libopenmpt
|
||||||
|
@ -503,7 +503,7 @@ LIBOPENMPT_API void * openmpt_error_func_errno_userdata( int * error );
|
||||||
* \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
|
* \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
|
||||||
* \sa \ref libopenmpt_c_fileio
|
* \sa \ref libopenmpt_c_fileio
|
||||||
* \sa openmpt_stream_callbacks
|
* \sa openmpt_stream_callbacks
|
||||||
* \deprecated Please use openmpt_module_could_open_probability2().
|
* \deprecated Please use openmpt_could_open_probability2().
|
||||||
* \since 0.3.0
|
* \since 0.3.0
|
||||||
*/
|
*/
|
||||||
LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_probability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
|
LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_probability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
|
||||||
|
@ -520,7 +520,7 @@ LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_probability( open
|
||||||
* \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
|
* \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
|
||||||
* \sa \ref libopenmpt_c_fileio
|
* \sa \ref libopenmpt_c_fileio
|
||||||
* \sa openmpt_stream_callbacks
|
* \sa openmpt_stream_callbacks
|
||||||
* \deprecated Please use openmpt_module_could_open_probability2().
|
* \deprecated Please use openmpt_could_open_probability2().
|
||||||
*/
|
*/
|
||||||
LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_propability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
|
LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_propability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
|
||||||
|
|
||||||
|
|
|
@ -173,19 +173,19 @@ LIBOPENMPT_CXX_API std::uint32_t get_core_version();
|
||||||
|
|
||||||
namespace string {
|
namespace string {
|
||||||
|
|
||||||
//! Return a verbose library version string from openmpt::string::get(). \deprecated Please use \code "library_version" \endcode directly.
|
//! Return a verbose library version string from openmpt::string::get(). \deprecated Please use `"library_version"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char library_version LIBOPENMPT_ATTR_DEPRECATED [] = "library_version";
|
LIBOPENMPT_DEPRECATED static const char library_version LIBOPENMPT_ATTR_DEPRECATED [] = "library_version";
|
||||||
//! Return a verbose library features string from openmpt::string::get(). \deprecated Please use \code "library_features" \endcode directly.
|
//! Return a verbose library features string from openmpt::string::get(). \deprecated Please use `"library_features"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char library_features LIBOPENMPT_ATTR_DEPRECATED [] = "library_features";
|
LIBOPENMPT_DEPRECATED static const char library_features LIBOPENMPT_ATTR_DEPRECATED [] = "library_features";
|
||||||
//! Return a verbose OpenMPT core version string from openmpt::string::get(). \deprecated Please use \code "core_version" \endcode directly.
|
//! Return a verbose OpenMPT core version string from openmpt::string::get(). \deprecated Please use `"core_version"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char core_version LIBOPENMPT_ATTR_DEPRECATED [] = "core_version";
|
LIBOPENMPT_DEPRECATED static const char core_version LIBOPENMPT_ATTR_DEPRECATED [] = "core_version";
|
||||||
//! Return information about the current build (e.g. the build date or compiler used) from openmpt::string::get(). \deprecated Please use \code "build" \endcode directly.
|
//! Return information about the current build (e.g. the build date or compiler used) from openmpt::string::get(). \deprecated Please use `"build"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char build LIBOPENMPT_ATTR_DEPRECATED [] = "build";
|
LIBOPENMPT_DEPRECATED static const char build LIBOPENMPT_ATTR_DEPRECATED [] = "build";
|
||||||
//! Return all contributors from openmpt::string::get(). \deprecated Please use \code "credits" \endcode directly.
|
//! Return all contributors from openmpt::string::get(). \deprecated Please use `"credits"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char credits LIBOPENMPT_ATTR_DEPRECATED [] = "credits";
|
LIBOPENMPT_DEPRECATED static const char credits LIBOPENMPT_ATTR_DEPRECATED [] = "credits";
|
||||||
//! Return contact information about libopenmpt from openmpt::string::get(). \deprecated Please use \code "contact" \endcode directly.
|
//! Return contact information about libopenmpt from openmpt::string::get(). \deprecated Please use `"contact"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char contact LIBOPENMPT_ATTR_DEPRECATED [] = "contact";
|
LIBOPENMPT_DEPRECATED static const char contact LIBOPENMPT_ATTR_DEPRECATED [] = "contact";
|
||||||
//! Return the libopenmpt license from openmpt::string::get(). \deprecated Please use \code "license" \endcode directly.
|
//! Return the libopenmpt license from openmpt::string::get(). \deprecated Please use `"license"` directly.
|
||||||
LIBOPENMPT_DEPRECATED static const char license LIBOPENMPT_ATTR_DEPRECATED [] = "license";
|
LIBOPENMPT_DEPRECATED static const char license LIBOPENMPT_ATTR_DEPRECATED [] = "license";
|
||||||
|
|
||||||
//! Get library related metadata.
|
//! Get library related metadata.
|
||||||
|
|
|
@ -66,7 +66,7 @@ namespace interface {
|
||||||
|
|
||||||
class invalid_module_pointer : public openmpt::exception {
|
class invalid_module_pointer : public openmpt::exception {
|
||||||
public:
|
public:
|
||||||
invalid_module_pointer() throw()
|
invalid_module_pointer()
|
||||||
: openmpt::exception("module * not valid")
|
: openmpt::exception("module * not valid")
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -78,7 +78,7 @@ public:
|
||||||
|
|
||||||
class argument_null_pointer : public openmpt::exception {
|
class argument_null_pointer : public openmpt::exception {
|
||||||
public:
|
public:
|
||||||
argument_null_pointer() throw()
|
argument_null_pointer()
|
||||||
: openmpt::exception("argument null pointer")
|
: openmpt::exception("argument null pointer")
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -275,6 +275,16 @@ namespace openmpt {
|
||||||
chn.nPan = Util::Round<int32_t>( Clamp( panning * 128.0, -128.0, 128.0 ) + 128.0 );
|
chn.nPan = Util::Round<int32_t>( Clamp( panning * 128.0, -128.0, 128.0 ) + 128.0 );
|
||||||
chn.nVolume = Util::Round<int32_t>( Clamp( volume * 256.0, 0.0, 256.0 ) );
|
chn.nVolume = Util::Round<int32_t>( Clamp( volume * 256.0, 0.0, 256.0 ) );
|
||||||
|
|
||||||
|
// Remove channel from list of mixed channels to fix https://bugs.openmpt.org/view.php?id=209
|
||||||
|
// This is required because a previous note on the same channel might have just stopped playing,
|
||||||
|
// but the channel is still in the mix list.
|
||||||
|
// Since the channel volume / etc is only updated every tick in CSoundFile::ReadNote, and we
|
||||||
|
// do not want to duplicate mixmode-dependant logic here, CSoundFile::CreateStereoMix may already
|
||||||
|
// try to mix our newly set up channel at volume 0 if we don't remove it from the list.
|
||||||
|
auto mix_begin = std::begin( m_sndFile->m_PlayState.ChnMix );
|
||||||
|
auto mix_end = std::remove( mix_begin, mix_begin + m_sndFile->m_nMixChannels, free_channel );
|
||||||
|
m_sndFile->m_nMixChannels = static_cast<CHANNELINDEX>( std::distance( mix_begin, mix_end ) );
|
||||||
|
|
||||||
return free_channel;
|
return free_channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -497,7 +497,8 @@ LIBOPENMPT_MODPLUG_API unsigned int ModPlug_SampleName(ModPlugFile* file, unsign
|
||||||
}
|
}
|
||||||
retval = (int)tmpretval;
|
retval = (int)tmpretval;
|
||||||
if(buff){
|
if(buff){
|
||||||
strncpy(buff,str,retval+1);
|
memcpy(buff,str,retval+1);
|
||||||
|
buff[retval] = '\0';
|
||||||
}
|
}
|
||||||
openmpt_free_string(str);
|
openmpt_free_string(str);
|
||||||
return retval;
|
return retval;
|
||||||
|
@ -522,7 +523,8 @@ LIBOPENMPT_MODPLUG_API unsigned int ModPlug_InstrumentName(ModPlugFile* file, un
|
||||||
}
|
}
|
||||||
retval = (int)tmpretval;
|
retval = (int)tmpretval;
|
||||||
if(buff){
|
if(buff){
|
||||||
strncpy(buff,str,retval+1);
|
memcpy(buff,str,retval+1);
|
||||||
|
buff[retval] = '\0';
|
||||||
}
|
}
|
||||||
openmpt_free_string(str);
|
openmpt_free_string(str);
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
@ -189,7 +189,7 @@ BOOL CSoundFile::Create( LPCBYTE lpStream, DWORD dwMemLength ) {
|
||||||
try {
|
try {
|
||||||
openmpt::module * m = new openmpt::module( lpStream, dwMemLength );
|
openmpt::module * m = new openmpt::module( lpStream, dwMemLength );
|
||||||
set_self( this, m );
|
set_self( this, m );
|
||||||
std::strncpy( m_szNames[0], mod->get_metadata("title").c_str(), sizeof( m_szNames[0] ) );
|
std::strncpy( m_szNames[0], mod->get_metadata("title").c_str(), sizeof( m_szNames[0] ) - 1 );
|
||||||
m_szNames[0][ sizeof( m_szNames[0] ) - 1 ] = '\0';
|
m_szNames[0][ sizeof( m_szNames[0] ) - 1 ] = '\0';
|
||||||
std::string type = mod->get_metadata("type");
|
std::string type = mod->get_metadata("type");
|
||||||
m_nType = MOD_TYPE_NONE;
|
m_nType = MOD_TYPE_NONE;
|
||||||
|
@ -678,7 +678,7 @@ static int get_filter_length() {
|
||||||
return 8;
|
return 8;
|
||||||
} else if ( ( CSoundFile::gdwSoundSetup & SNDMIX_HQRESAMPLER ) == SNDMIX_HQRESAMPLER ) {
|
} else if ( ( CSoundFile::gdwSoundSetup & SNDMIX_HQRESAMPLER ) == SNDMIX_HQRESAMPLER ) {
|
||||||
return 4;
|
return 4;
|
||||||
} else if ( ( CSoundFile::gdwSoundSetup & SNDMIX_HQRESAMPLER ) == SNDMIX_NORESAMPLING ) {
|
} else if ( ( CSoundFile::gdwSoundSetup & SNDMIX_NORESAMPLING ) == SNDMIX_NORESAMPLING ) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return 2;
|
return 2;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
/*! \brief libopenmpt minor version number */
|
/*! \brief libopenmpt minor version number */
|
||||||
#define OPENMPT_API_VERSION_MINOR 3
|
#define OPENMPT_API_VERSION_MINOR 3
|
||||||
/*! \brief libopenmpt patch version number */
|
/*! \brief libopenmpt patch version number */
|
||||||
#define OPENMPT_API_VERSION_PATCH 10
|
#define OPENMPT_API_VERSION_PATCH 12
|
||||||
/*! \brief libopenmpt pre-release tag */
|
/*! \brief libopenmpt pre-release tag */
|
||||||
#define OPENMPT_API_VERSION_PREREL ""
|
#define OPENMPT_API_VERSION_PREREL ""
|
||||||
/*! \brief libopenmpt pre-release flag */
|
/*! \brief libopenmpt pre-release flag */
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
LIBOPENMPT_VERSION_MAJOR=0
|
LIBOPENMPT_VERSION_MAJOR=0
|
||||||
LIBOPENMPT_VERSION_MINOR=3
|
LIBOPENMPT_VERSION_MINOR=3
|
||||||
LIBOPENMPT_VERSION_PATCH=10
|
LIBOPENMPT_VERSION_PATCH=12
|
||||||
LIBOPENMPT_VERSION_PREREL=
|
LIBOPENMPT_VERSION_PREREL=
|
||||||
|
|
||||||
LIBOPENMPT_LTVER_CURRENT=1
|
LIBOPENMPT_LTVER_CURRENT=1
|
||||||
LIBOPENMPT_LTVER_REVISION=10
|
LIBOPENMPT_LTVER_REVISION=12
|
||||||
LIBOPENMPT_LTVER_AGE=1
|
LIBOPENMPT_LTVER_AGE=1
|
||||||
|
|
|
@ -99,35 +99,35 @@ static const char * const license =
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct silent_exit_exception : public std::exception {
|
struct silent_exit_exception : public std::exception {
|
||||||
silent_exit_exception() throw() { }
|
silent_exit_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_license_exception : public std::exception {
|
struct show_license_exception : public std::exception {
|
||||||
show_license_exception() throw() { }
|
show_license_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_credits_exception : public std::exception {
|
struct show_credits_exception : public std::exception {
|
||||||
show_credits_exception() throw() { }
|
show_credits_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_man_version_exception : public std::exception {
|
struct show_man_version_exception : public std::exception {
|
||||||
show_man_version_exception() throw() { }
|
show_man_version_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_man_help_exception : public std::exception {
|
struct show_man_help_exception : public std::exception {
|
||||||
show_man_help_exception() throw() { }
|
show_man_help_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_short_version_number_exception : public std::exception {
|
struct show_short_version_number_exception : public std::exception {
|
||||||
show_short_version_number_exception() throw() { }
|
show_short_version_number_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_version_number_exception : public std::exception {
|
struct show_version_number_exception : public std::exception {
|
||||||
show_version_number_exception() throw() { }
|
show_version_number_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_long_version_number_exception : public std::exception {
|
struct show_long_version_number_exception : public std::exception {
|
||||||
show_long_version_number_exception() throw() { }
|
show_long_version_number_exception() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
bool IsTerminal( int fd ) {
|
bool IsTerminal( int fd ) {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct exception : public openmpt::exception {
|
struct exception : public openmpt::exception {
|
||||||
exception( const std::string & text ) throw() : openmpt::exception(text) { }
|
exception( const std::string & text ) : openmpt::exception(text) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct show_help_exception {
|
struct show_help_exception {
|
||||||
|
@ -565,7 +565,7 @@ protected:
|
||||||
sampleQueueMaxFrames = frames;
|
sampleQueueMaxFrames = frames;
|
||||||
}
|
}
|
||||||
template < typename Tsample >
|
template < typename Tsample >
|
||||||
float pop_queue() {
|
Tsample pop_queue() {
|
||||||
float val = 0.0f;
|
float val = 0.0f;
|
||||||
if ( !sampleQueue.empty() ) {
|
if ( !sampleQueue.empty() ) {
|
||||||
val = sampleQueue.front();
|
val = sampleQueue.front();
|
||||||
|
|
|
@ -57,6 +57,10 @@
|
||||||
|
|
||||||
#endif // MPT_BUILD_MSVC
|
#endif // MPT_BUILD_MSVC
|
||||||
|
|
||||||
|
#if defined(MPT_WITH_SDL) && defined(MPT_WITH_SDL2)
|
||||||
|
#error "MPT_WITH_SDL2 and MPT_WITH_SDL are mutually exclusive."
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(MPT_WITH_SDL)
|
#if defined(MPT_WITH_SDL)
|
||||||
#ifndef MPT_NEEDS_THREADS
|
#ifndef MPT_NEEDS_THREADS
|
||||||
#define MPT_NEEDS_THREADS
|
#define MPT_NEEDS_THREADS
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct portaudio_exception : public exception {
|
struct portaudio_exception : public exception {
|
||||||
portaudio_exception( PaError code ) throw() : exception( Pa_GetErrorText( code ) ) { }
|
portaudio_exception( PaError code ) : exception( Pa_GetErrorText( code ) ) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*PaUtilLogCallback ) (const char *log);
|
typedef void (*PaUtilLogCallback ) (const char *log);
|
||||||
|
|
|
@ -42,7 +42,7 @@ struct pulseaudio_exception : public exception {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pulseaudio_exception( int error ) throw() : exception( error_to_string( error ) ) { }
|
pulseaudio_exception( int error ) : exception( error_to_string( error ) ) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
class pulseaudio_stream_raii : public write_buffers_interface {
|
class pulseaudio_stream_raii : public write_buffers_interface {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct sdl_exception : public exception {
|
struct sdl_exception : public exception {
|
||||||
sdl_exception( int /*code*/ ) throw() : exception( "SDL error" ) { }
|
sdl_exception( int /*code*/ ) : exception( "SDL error" ) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
class sdl_stream_raii : public write_buffers_blocking_wrapper {
|
class sdl_stream_raii : public write_buffers_blocking_wrapper {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct sdl2_exception : public exception {
|
struct sdl2_exception : public exception {
|
||||||
sdl2_exception( int /*code*/ ) throw() : exception( "SDL2 error" ) { }
|
sdl2_exception( int /*code*/ ) : exception( "SDL2 error" ) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
static void check_sdl2_error( int e ) {
|
static void check_sdl2_error( int e ) {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
namespace openmpt123 {
|
namespace openmpt123 {
|
||||||
|
|
||||||
struct waveout_exception : public exception {
|
struct waveout_exception : public exception {
|
||||||
waveout_exception() throw() : exception( "waveout" ) { }
|
waveout_exception() : exception( "waveout" ) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
class waveout_stream_raii : public write_buffers_interface {
|
class waveout_stream_raii : public write_buffers_interface {
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "Sndfile.h"
|
#include "Sndfile.h"
|
||||||
#include "Dither.h"
|
#include "Dither.h"
|
||||||
#include "../soundbase/SampleFormat.h"
|
#include "../soundbase/SampleFormat.h"
|
||||||
|
|
|
@ -444,7 +444,7 @@ void CSoundFile::WriteInstrumentPropertyForAllInstruments(uint32 code, uint16 si
|
||||||
type tmp; \
|
type tmp; \
|
||||||
tmp = file.ReadTruncatedIntLE<type>(fsize); \
|
tmp = file.ReadTruncatedIntLE<type>(fsize); \
|
||||||
STATIC_ASSERT(sizeof(tmp) == sizeof(input-> name )); \
|
STATIC_ASSERT(sizeof(tmp) == sizeof(input-> name )); \
|
||||||
memcpy(&(input-> name ), &tmp, sizeof(type)); \
|
input-> name = decltype(input-> name )(tmp); \
|
||||||
result = true; \
|
result = true; \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
|
|
|
@ -137,6 +137,7 @@ bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
|
|
||||||
InitializeGlobals(MOD_TYPE_AMF0);
|
InitializeGlobals(MOD_TYPE_AMF0);
|
||||||
InitializeChannels();
|
InitializeChannels();
|
||||||
|
SetupMODPanning(true);
|
||||||
m_nChannels = 8;
|
m_nChannels = 8;
|
||||||
m_nDefaultSpeed = fileHeader.defaultSpeed;
|
m_nDefaultSpeed = fileHeader.defaultSpeed;
|
||||||
m_nDefaultTempo.Set(fileHeader.defaultTempo);
|
m_nDefaultTempo.Set(fileHeader.defaultTempo);
|
||||||
|
@ -185,6 +186,13 @@ bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
m.command = data[2];
|
m.command = data[2];
|
||||||
m.param = data[3];
|
m.param = data[3];
|
||||||
ConvertModCommand(m);
|
ConvertModCommand(m);
|
||||||
|
#ifdef MODPLUG_TRACKER
|
||||||
|
if(m.command == CMD_PANNING8)
|
||||||
|
{
|
||||||
|
// Convert 7-bit panning to 8-bit
|
||||||
|
m.param = mpt::saturate_cast<ModCommand::PARAM>(m.param * 2u);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -610,7 +610,8 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
mpt::String::Write<mpt::String::nullTerminated>(plugin.Info.szLibraryName, "DigiBooster Pro Echo");
|
mpt::String::Write<mpt::String::nullTerminated>(plugin.Info.szLibraryName, "DigiBooster Pro Echo");
|
||||||
|
|
||||||
plugin.pluginData.resize(sizeof(DigiBoosterEcho::PluginChunk));
|
plugin.pluginData.resize(sizeof(DigiBoosterEcho::PluginChunk));
|
||||||
new (plugin.pluginData.data()) DigiBoosterEcho::PluginChunk(settings[1], settings[3], settings[5], settings[7]);
|
DigiBoosterEcho::PluginChunk chunk = DigiBoosterEcho::PluginChunk::Create(settings[1], settings[3], settings[5], settings[7]);
|
||||||
|
new (plugin.pluginData.data()) DigiBoosterEcho::PluginChunk(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -973,7 +973,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
m_nMixLevels = mixLevelsOriginal;
|
m_nMixLevels = mixLevelsOriginal;
|
||||||
}
|
}
|
||||||
// Need to do this before reading the patterns because m_nChannels might be modified by LoadExtendedSongProperties. *sigh*
|
// Need to do this before reading the patterns because m_nChannels might be modified by LoadExtendedSongProperties. *sigh*
|
||||||
LoadExtendedSongProperties(file, &interpretModPlugMade);
|
LoadExtendedSongProperties(file, false, &interpretModPlugMade);
|
||||||
|
|
||||||
// Reading Patterns
|
// Reading Patterns
|
||||||
Patterns.ResizeArray(numPats);
|
Patterns.ResizeArray(numPats);
|
||||||
|
@ -2324,7 +2324,7 @@ void ReadFieldCast(FileReader &chunk, std::size_t size, T &field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool *pInterpretMptMade)
|
void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannelCount, bool *pInterpretMptMade)
|
||||||
{
|
{
|
||||||
if(!file.ReadMagic("STPM")) // 'MPTS'
|
if(!file.ReadMagic("STPM")) // 'MPTS'
|
||||||
{
|
{
|
||||||
|
@ -2362,7 +2362,7 @@ void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool *pInterpretMp
|
||||||
case MAGIC4BE('R','P','B','.'): ReadField(chunk, size, m_nDefaultRowsPerBeat); break;
|
case MAGIC4BE('R','P','B','.'): ReadField(chunk, size, m_nDefaultRowsPerBeat); break;
|
||||||
case MAGIC4BE('R','P','M','.'): ReadField(chunk, size, m_nDefaultRowsPerMeasure); break;
|
case MAGIC4BE('R','P','M','.'): ReadField(chunk, size, m_nDefaultRowsPerMeasure); break;
|
||||||
// FIXME: If there are only PC events on the last few channels in an MPTM MO3, they won't be imported!
|
// FIXME: If there are only PC events on the last few channels in an MPTM MO3, they won't be imported!
|
||||||
case MAGIC4BE('C','.','.','.'): if(GetType() != MOD_TYPE_XM && m_ContainerType != MOD_CONTAINERTYPE_MO3) { CHANNELINDEX chn = 0; ReadField(chunk, size, chn); m_nChannels = Clamp(chn, m_nChannels, MAX_BASECHANNELS); } break;
|
case MAGIC4BE('C','.','.','.'): if(!ignoreChannelCount) { CHANNELINDEX chn = 0; ReadField(chunk, size, chn); m_nChannels = Clamp(chn, m_nChannels, MAX_BASECHANNELS); } break;
|
||||||
case MAGIC4BE('T','M','.','.'): ReadFieldCast(chunk, size, m_nTempoMode); break;
|
case MAGIC4BE('T','M','.','.'): ReadFieldCast(chunk, size, m_nTempoMode); break;
|
||||||
case MAGIC4BE('P','M','M','.'): ReadFieldCast(chunk, size, m_nMixLevels); break;
|
case MAGIC4BE('P','M','M','.'): ReadFieldCast(chunk, size, m_nMixLevels); break;
|
||||||
case MAGIC4BE('C','W','V','.'): ReadField(chunk, size, m_dwCreatedWithVersion); break;
|
case MAGIC4BE('C','W','V','.'): ReadField(chunk, size, m_dwCreatedWithVersion); break;
|
||||||
|
|
|
@ -377,7 +377,7 @@ bool CSoundFile::ReadITProject(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
if(code == MAGIC4BE('M', 'P', 'T', 'S'))
|
if(code == MAGIC4BE('M', 'P', 'T', 'S'))
|
||||||
{
|
{
|
||||||
file.SkipBack(4);
|
file.SkipBack(4);
|
||||||
LoadExtendedSongProperties(file);
|
LoadExtendedSongProperties(file, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_nMaxPeriod = 0xF000;
|
m_nMaxPeriod = 0xF000;
|
||||||
|
|
|
@ -1812,7 +1812,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadExtendedInstrumentProperties(chunk);
|
LoadExtendedInstrumentProperties(chunk);
|
||||||
LoadExtendedSongProperties(chunk);
|
LoadExtendedSongProperties(chunk, true);
|
||||||
if(cwtv > 0x0889 && cwtv <= 0x8FF)
|
if(cwtv > 0x0889 && cwtv <= 0x8FF)
|
||||||
{
|
{
|
||||||
m_nType = MOD_TYPE_MPT;
|
m_nType = MOD_TYPE_MPT;
|
||||||
|
|
|
@ -712,7 +712,7 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags)
|
||||||
LoadExtendedInstrumentProperties(file, &interpretOpenMPTMade);
|
LoadExtendedInstrumentProperties(file, &interpretOpenMPTMade);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadExtendedSongProperties(file, &interpretOpenMPTMade);
|
LoadExtendedSongProperties(file, true, &interpretOpenMPTMade);
|
||||||
|
|
||||||
if(interpretOpenMPTMade && m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 17, 00, 00))
|
if(interpretOpenMPTMade && m_dwLastSavedWithVersion < MAKE_VERSION_NUMERIC(1, 17, 00, 00))
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,12 +10,16 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
OPENMPT_NAMESPACE_BEGIN
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
NUM_MACROS = 16, // number of parametered macros
|
NUM_MACROS = 16, // number of parametered macros
|
||||||
MACRO_LENGTH = 32, // max number of chars per macro
|
MACRO_LENGTH = 32, // max number of chars per macro
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OPENMPT_NAMESPACE_END
|
||||||
|
|
||||||
#ifdef MODPLUG_TRACKER
|
#ifdef MODPLUG_TRACKER
|
||||||
#include "plugins/PluginStructs.h"
|
#include "plugins/PluginStructs.h"
|
||||||
#endif // MODPLUG_TRACKER
|
#endif // MODPLUG_TRACKER
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "../common/Endianness.h"
|
#include "../common/Endianness.h"
|
||||||
#include "../common/mptIO.h"
|
#include "../common/mptIO.h"
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,7 @@ struct FLACDecoder
|
||||||
sample.uFlags.set(CHN_16BIT, metadata->data.stream_info.bits_per_sample > 8);
|
sample.uFlags.set(CHN_16BIT, metadata->data.stream_info.bits_per_sample > 8);
|
||||||
sample.uFlags.set(CHN_STEREO, metadata->data.stream_info.channels > 1);
|
sample.uFlags.set(CHN_STEREO, metadata->data.stream_info.channels > 1);
|
||||||
sample.nLength = mpt::saturate_cast<SmpLength>(metadata->data.stream_info.total_samples);
|
sample.nLength = mpt::saturate_cast<SmpLength>(metadata->data.stream_info.total_samples);
|
||||||
|
LimitMax(sample.nLength, MAX_SAMPLE_LENGTH);
|
||||||
sample.nC5Speed = metadata->data.stream_info.sample_rate;
|
sample.nC5Speed = metadata->data.stream_info.sample_rate;
|
||||||
client.ready = (sample.AllocateSample() != 0);
|
client.ready = (sample.AllocateSample() != 0);
|
||||||
} else if(metadata->type == FLAC__METADATA_TYPE_APPLICATION && !memcmp(metadata->data.application.id, "riff", 4) && client.ready)
|
} else if(metadata->type == FLAC__METADATA_TYPE_APPLICATION && !memcmp(metadata->data.application.id, "riff", 4) && client.ready)
|
||||||
|
|
|
@ -237,6 +237,12 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool mo3Dec
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(length > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
mpg123_delete(mh);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DestroySampleThreadsafe(sample);
|
DestroySampleThreadsafe(sample);
|
||||||
if(!mo3Decode)
|
if(!mo3Decode)
|
||||||
{
|
{
|
||||||
|
@ -303,6 +309,10 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool mo3Dec
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if((raw_sample_data.size() / channels) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
} while((bytes_left >= 0) && (frame_size > 0));
|
} while((bytes_left >= 0) && (frame_size > 0));
|
||||||
|
|
||||||
mp3_free(mp3);
|
mp3_free(mp3);
|
||||||
|
@ -312,6 +322,11 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool mo3Dec
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((raw_sample_data.size() / channels) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DestroySampleThreadsafe(sample);
|
DestroySampleThreadsafe(sample);
|
||||||
if(!mo3Decode)
|
if(!mo3Decode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -392,6 +392,10 @@ bool CSoundFile::ReadMediaFoundationSample(SAMPLEINDEX sample, FileReader &file,
|
||||||
MPT_MF_CHECKED(buffer->Lock(&data, NULL, &dataSize));
|
MPT_MF_CHECKED(buffer->Lock(&data, NULL, &dataSize));
|
||||||
rawData.insert(rawData.end(), mpt::byte_cast<char*>(data), mpt::byte_cast<char*>(data + dataSize));
|
rawData.insert(rawData.end(), mpt::byte_cast<char*>(data), mpt::byte_cast<char*>(data + dataSize));
|
||||||
MPT_MF_CHECKED(buffer->Unlock());
|
MPT_MF_CHECKED(buffer->Unlock());
|
||||||
|
if(rawData.size() / numChannels / (bitsPerSample / 8) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mptMFSafeRelease(&buffer);
|
mptMFSafeRelease(&buffer);
|
||||||
mptMFSafeRelease(&mfSample);
|
mptMFSafeRelease(&mfSample);
|
||||||
|
|
|
@ -132,6 +132,10 @@ bool CSoundFile::ReadOpusSample(SAMPLEINDEX sample, FileReader &file)
|
||||||
// other errors are fatal, stop decoding
|
// other errors are fatal, stop decoding
|
||||||
eof = true;
|
eof = true;
|
||||||
}
|
}
|
||||||
|
if((raw_sample_data.size() / channels) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
op_free(of);
|
op_free(of);
|
||||||
|
|
|
@ -212,6 +212,10 @@ bool CSoundFile::ReadVorbisSample(SAMPLEINDEX sample, FileReader &file)
|
||||||
CopyChannelToInterleaved<SC::Convert<int16, float> >(&(raw_sample_data[0]) + offset * channels, output[chn], channels, decodedSamples, chn);
|
CopyChannelToInterleaved<SC::Convert<int16, float> >(&(raw_sample_data[0]) + offset * channels, output[chn], channels, decodedSamples, chn);
|
||||||
}
|
}
|
||||||
offset += decodedSamples;
|
offset += decodedSamples;
|
||||||
|
if((raw_sample_data.size() / channels) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,6 +285,10 @@ bool CSoundFile::ReadVorbisSample(SAMPLEINDEX sample, FileReader &file)
|
||||||
CopyChannelToInterleaved<SC::Convert<int16, float> >(&(raw_sample_data[0]) + offset * channels, output[chn], channels, decodedSamples, chn);
|
CopyChannelToInterleaved<SC::Convert<int16, float> >(&(raw_sample_data[0]) + offset * channels, output[chn], channels, decodedSamples, chn);
|
||||||
}
|
}
|
||||||
offset += decodedSamples;
|
offset += decodedSamples;
|
||||||
|
if((raw_sample_data.size() / channels) > MAX_SAMPLE_LENGTH)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
error = stb_vorbis_get_error(vorb);
|
error = stb_vorbis_get_error(vorb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2177,11 +2177,11 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
|
||||||
IMixPlugin *pPlugin = nullptr;
|
IMixPlugin *pPlugin = nullptr;
|
||||||
if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan
|
if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan
|
||||||
{
|
{
|
||||||
PLUGINDEX nPlugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes);
|
PLUGINDEX plugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes);
|
||||||
|
|
||||||
if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS)
|
if(plugin > 0 && plugin <= MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
pPlugin = m_MixPlugins[nPlugin-1].pMixPlugin;
|
pPlugin = m_MixPlugins[plugin - 1].pMixPlugin;
|
||||||
if(pPlugin)
|
if(pPlugin)
|
||||||
{
|
{
|
||||||
// apply NNA to this plugin iff it is currently playing a note on this tracker channel
|
// apply NNA to this plugin iff it is currently playing a note on this tracker channel
|
||||||
|
@ -2193,7 +2193,7 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
|
||||||
#endif // NO_PLUGINS
|
#endif // NO_PLUGINS
|
||||||
|
|
||||||
// New Note Action
|
// New Note Action
|
||||||
if((srcChn.nRealVolume > 0 && srcChn.nLength > 0) || applyNNAtoPlug)
|
if(srcChn.IsSamplePlaying() || applyNNAtoPlug)
|
||||||
{
|
{
|
||||||
nnaChn = GetNNAChannel(nChn);
|
nnaChn = GetNNAChannel(nChn);
|
||||||
if(nnaChn != 0)
|
if(nnaChn != 0)
|
||||||
|
@ -2209,16 +2209,12 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
|
||||||
#ifndef NO_PLUGINS
|
#ifndef NO_PLUGINS
|
||||||
if(applyNNAtoPlug && pPlugin)
|
if(applyNNAtoPlug && pPlugin)
|
||||||
{
|
{
|
||||||
//Move note to the NNA channel (odd, but makes sense with DNA stuff).
|
|
||||||
//Actually a bad idea since it then become very hard to kill some notes.
|
|
||||||
//pPlugin->MoveNote(pChn.nNote, pChn.pModInstrument->nMidiChannel, nChn, n);
|
|
||||||
switch(srcChn.nNNA)
|
switch(srcChn.nNNA)
|
||||||
{
|
{
|
||||||
case NNA_NOTEOFF:
|
case NNA_NOTEOFF:
|
||||||
case NNA_NOTECUT:
|
case NNA_NOTECUT:
|
||||||
case NNA_NOTEFADE:
|
case NNA_NOTEFADE:
|
||||||
//switch off note played on this plugin, on this tracker channel and midi channel
|
// Switch off note played on this plugin, on this tracker channel and midi channel
|
||||||
//pPlugin->MidiCommand(pChn.pModInstrument->nMidiChannel, pChn.pModInstrument->nMidiProgram, pChn.nNote + NOTE_MAX_SPECIAL, 0, n);
|
|
||||||
SendMIDINote(nChn, NOTE_KEYOFF, 0);
|
SendMIDINote(nChn, NOTE_KEYOFF, 0);
|
||||||
srcChn.nArpeggioLastNote = NOTE_NONE;
|
srcChn.nArpeggioLastNote = NOTE_NONE;
|
||||||
break;
|
break;
|
||||||
|
@ -2300,8 +2296,8 @@ bool CSoundFile::ProcessEffects()
|
||||||
const bool isFirstTick = m_SongFlags[SONG_FIRSTTICK];
|
const bool isFirstTick = m_SongFlags[SONG_FIRSTTICK];
|
||||||
if(isFirstTick)
|
if(isFirstTick)
|
||||||
pChn->m_RowPlug = pChn->rowCommand.instr;
|
pChn->m_RowPlug = pChn->rowCommand.instr;
|
||||||
const PLUGINDEX nPlug = pChn->m_RowPlug;
|
const PLUGINDEX plugin = pChn->m_RowPlug;
|
||||||
const bool hasValidPlug = (nPlug > 0 && nPlug <= MAX_MIXPLUGINS && m_MixPlugins[nPlug-1].pMixPlugin);
|
const bool hasValidPlug = (plugin > 0 && plugin <= MAX_MIXPLUGINS && m_MixPlugins[plugin - 1].pMixPlugin);
|
||||||
if(hasValidPlug)
|
if(hasValidPlug)
|
||||||
{
|
{
|
||||||
if(isFirstTick)
|
if(isFirstTick)
|
||||||
|
@ -2311,14 +2307,14 @@ bool CSoundFile::ProcessEffects()
|
||||||
{
|
{
|
||||||
PlugParamValue targetvalue = ModCommand::GetValueEffectCol(pChn->rowCommand.command, pChn->rowCommand.param) / PlugParamValue(ModCommand::maxColumnValue);
|
PlugParamValue targetvalue = ModCommand::GetValueEffectCol(pChn->rowCommand.command, pChn->rowCommand.param) / PlugParamValue(ModCommand::maxColumnValue);
|
||||||
pChn->m_plugParamTargetValue = targetvalue;
|
pChn->m_plugParamTargetValue = targetvalue;
|
||||||
pChn->m_plugParamValueStep = (targetvalue - m_MixPlugins[nPlug-1].pMixPlugin->GetParameter(plugparam)) / float(GetNumTicksOnCurrentRow());
|
pChn->m_plugParamValueStep = (targetvalue - m_MixPlugins[plugin-1].pMixPlugin->GetParameter(plugparam)) / float(GetNumTicksOnCurrentRow());
|
||||||
}
|
}
|
||||||
if(m_PlayState.m_nTickCount + 1 == GetNumTicksOnCurrentRow())
|
if(m_PlayState.m_nTickCount + 1 == GetNumTicksOnCurrentRow())
|
||||||
{ // On last tick, set parameter exactly to target value.
|
{ // On last tick, set parameter exactly to target value.
|
||||||
m_MixPlugins[nPlug-1].pMixPlugin->SetParameter(plugparam, pChn->m_plugParamTargetValue);
|
m_MixPlugins[plugin - 1].pMixPlugin->SetParameter(plugparam, pChn->m_plugParamTargetValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
m_MixPlugins[nPlug-1].pMixPlugin->ModifyParameter(plugparam, pChn->m_plugParamValueStep);
|
m_MixPlugins[plugin - 1].pMixPlugin->ModifyParameter(plugparam, pChn->m_plugParamValueStep);
|
||||||
}
|
}
|
||||||
#endif // NO_PLUGINS
|
#endif // NO_PLUGINS
|
||||||
}
|
}
|
||||||
|
@ -3398,29 +3394,6 @@ bool CSoundFile::ProcessEffects()
|
||||||
const bool doBreakRow = (nBreakRow != ROWINDEX_INVALID);
|
const bool doBreakRow = (nBreakRow != ROWINDEX_INVALID);
|
||||||
const bool doPosJump = (nPosJump != ORDERINDEX_INVALID);
|
const bool doPosJump = (nPosJump != ORDERINDEX_INVALID);
|
||||||
|
|
||||||
// Pattern Loop
|
|
||||||
if(doPatternLoop)
|
|
||||||
{
|
|
||||||
m_PlayState.m_nNextOrder = m_PlayState.m_nCurrentOrder;
|
|
||||||
m_PlayState.m_nNextRow = nPatLoopRow;
|
|
||||||
if(m_PlayState.m_nPatternDelay)
|
|
||||||
{
|
|
||||||
m_PlayState.m_nNextRow++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IT Compatibility: If the restart row is past the end of the current pattern
|
|
||||||
// (e.g. when continued from a previous pattern without explicit SB0 effect), continue the next pattern.
|
|
||||||
// Test case: LoopStartAfterPatternEnd.it
|
|
||||||
if(nPatLoopRow >= Patterns[m_PlayState.m_nPattern].GetNumRows())
|
|
||||||
{
|
|
||||||
m_PlayState.m_nNextOrder++;
|
|
||||||
m_PlayState.m_nNextRow = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// As long as the pattern loop is running, mark the looped rows as not visited yet
|
|
||||||
visitedSongRows.ResetPatternLoop(m_PlayState.m_nCurrentOrder, nPatLoopRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern Break / Position Jump only if no loop running
|
// Pattern Break / Position Jump only if no loop running
|
||||||
// Exception: FastTracker 2 in all cases, Impulse Tracker in case of position jump
|
// Exception: FastTracker 2 in all cases, Impulse Tracker in case of position jump
|
||||||
// Test case for FT2 exception: PatLoop-Jumps.xm, PatLoop-Various.xm
|
// Test case for FT2 exception: PatLoop-Jumps.xm, PatLoop-Various.xm
|
||||||
|
@ -3451,8 +3424,28 @@ bool CSoundFile::ProcessEffects()
|
||||||
m_PlayState.m_nNextRow = nBreakRow;
|
m_PlayState.m_nNextRow = nBreakRow;
|
||||||
if(!m_SongFlags[SONG_PATTERNLOOP])
|
if(!m_SongFlags[SONG_PATTERNLOOP])
|
||||||
m_PlayState.m_nNextOrder = nPosJump;
|
m_PlayState.m_nNextOrder = nPosJump;
|
||||||
|
} else if(doPatternLoop)
|
||||||
|
{
|
||||||
|
// Pattern Loop
|
||||||
|
m_PlayState.m_nNextOrder = m_PlayState.m_nCurrentOrder;
|
||||||
|
m_PlayState.m_nNextRow = nPatLoopRow;
|
||||||
|
if(m_PlayState.m_nPatternDelay)
|
||||||
|
{
|
||||||
|
m_PlayState.m_nNextRow++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IT Compatibility: If the restart row is past the end of the current pattern
|
||||||
|
// (e.g. when continued from a previous pattern without explicit SB0 effect), continue the next pattern.
|
||||||
|
// Test case: LoopStartAfterPatternEnd.it
|
||||||
|
if(nPatLoopRow >= Patterns[m_PlayState.m_nPattern].GetNumRows())
|
||||||
|
{
|
||||||
|
m_PlayState.m_nNextOrder++;
|
||||||
|
m_PlayState.m_nNextRow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As long as the pattern loop is running, mark the looped rows as not visited yet
|
||||||
|
visitedSongRows.ResetPatternLoop(m_PlayState.m_nCurrentOrder, nPatLoopRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4066,7 +4059,7 @@ void CSoundFile::Panning(ModChannel *pChn, uint32 param, PanningType panBits) co
|
||||||
pChn->nPan = param * 4;
|
pChn->nPan = param * 4;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_DSM | MOD_TYPE_AMF | MOD_TYPE_MTM)))
|
if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_DSM | MOD_TYPE_AMF0 | MOD_TYPE_AMF | MOD_TYPE_MTM)))
|
||||||
{
|
{
|
||||||
// Real 8-bit panning
|
// Real 8-bit panning
|
||||||
pChn->nPan = param;
|
pChn->nPan = param;
|
||||||
|
@ -4936,16 +4929,16 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
|
||||||
} else if(macroCode == 0x03 && !isExtended)
|
} else if(macroCode == 0x03 && !isExtended)
|
||||||
{
|
{
|
||||||
// F0.F0.03.xx: Set plug dry/wet
|
// F0.F0.03.xx: Set plug dry/wet
|
||||||
const PLUGINDEX nPlug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
||||||
if ((nPlug) && (nPlug <= MAX_MIXPLUGINS) && param < 0x80)
|
if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
|
||||||
{
|
{
|
||||||
const float newRatio = 1.0f - (static_cast<float>(param & 0x7F) / 127.0f);
|
const float newRatio = 1.0f - (static_cast<float>(param & 0x7F) / 127.0f);
|
||||||
if(!isSmooth)
|
if(!isSmooth)
|
||||||
{
|
{
|
||||||
m_MixPlugins[nPlug - 1].fDryRatio = newRatio;
|
m_MixPlugins[plug - 1].fDryRatio = newRatio;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
m_MixPlugins[nPlug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[nPlug - 1].fDryRatio, newRatio);
|
m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4953,11 +4946,11 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
|
||||||
} else if((macroCode & 0x80) || isExtended)
|
} else if((macroCode & 0x80) || isExtended)
|
||||||
{
|
{
|
||||||
// F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx
|
// F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx
|
||||||
const PLUGINDEX nPlug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
||||||
const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
|
const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
|
||||||
if((nPlug) && (nPlug <= MAX_MIXPLUGINS))
|
if(plug > 0 && plug <= MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
IMixPlugin *pPlugin = m_MixPlugins[nPlug - 1].pMixPlugin;
|
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
|
||||||
if(pPlugin && param < 0x80)
|
if(pPlugin && param < 0x80)
|
||||||
{
|
{
|
||||||
const float fParam = param / 127.0f;
|
const float fParam = param / 127.0f;
|
||||||
|
@ -4984,15 +4977,15 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
|
||||||
const CHANNELINDEX plugChannel = (nChn < GetNumChannels()) ? nChn + 1 : chn.nMasterChn;
|
const CHANNELINDEX plugChannel = (nChn < GetNumChannels()) ? nChn + 1 : chn.nMasterChn;
|
||||||
if(plugChannel > 0 && plugChannel <= GetNumChannels()) // XXX do we need this? I guess it might be relevant for previewing notes in the pattern... Or when using this mechanism for volume/panning!
|
if(plugChannel > 0 && plugChannel <= GetNumChannels()) // XXX do we need this? I guess it might be relevant for previewing notes in the pattern... Or when using this mechanism for volume/panning!
|
||||||
{
|
{
|
||||||
PLUGINDEX nPlug = 0;
|
PLUGINDEX plug = 0;
|
||||||
if(!chn.dwFlags[CHN_NOFX])
|
if(!chn.dwFlags[CHN_NOFX])
|
||||||
{
|
{
|
||||||
nPlug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nPlug > 0 && nPlug <= MAX_MIXPLUGINS)
|
if(plug > 0 && plug <= MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
IMixPlugin *pPlugin = m_MixPlugins[nPlug - 1].pMixPlugin;
|
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
|
||||||
if (pPlugin != nullptr)
|
if (pPlugin != nullptr)
|
||||||
{
|
{
|
||||||
if(macro[0] == 0xF0)
|
if(macro[0] == 0xF0)
|
||||||
|
@ -5033,10 +5026,10 @@ void CSoundFile::SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume)
|
||||||
// instro sends to a midi chan
|
// instro sends to a midi chan
|
||||||
if (pIns && pIns->HasValidMIDIChannel())
|
if (pIns && pIns->HasValidMIDIChannel())
|
||||||
{
|
{
|
||||||
PLUGINDEX nPlug = pIns->nMixPlug;
|
PLUGINDEX plug = pIns->nMixPlug;
|
||||||
if ((nPlug) && (nPlug <= MAX_MIXPLUGINS))
|
if(plug > 0 && plug <= MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
IMixPlugin *pPlug = m_MixPlugins[nPlug-1].pMixPlugin;
|
IMixPlugin *pPlug = m_MixPlugins[plug - 1].pMixPlugin;
|
||||||
if (pPlug != nullptr)
|
if (pPlug != nullptr)
|
||||||
{
|
{
|
||||||
pPlug->MidiCommand(GetBestMidiChannel(chn), pIns->nMidiProgram, pIns->wMidiBank, note, volume, chn);
|
pPlug->MidiCommand(GetBestMidiChannel(chn), pIns->nMidiProgram, pIns->wMidiBank, note, volume, chn);
|
||||||
|
@ -5467,7 +5460,12 @@ void CSoundFile::SetTempo(TEMPO param, bool setFromUI)
|
||||||
// ProTracker sets the tempo after the first tick.
|
// ProTracker sets the tempo after the first tick.
|
||||||
// Note: The case of one tick per row is handled in ProcessRow() instead.
|
// Note: The case of one tick per row is handled in ProcessRow() instead.
|
||||||
// Test case: TempoChange.mod
|
// Test case: TempoChange.mod
|
||||||
|
#if MPT_MSVC_AT_LEAST(2017,8)
|
||||||
|
// Work-around MSVC getting confused about deduced const input type in noexcept operator inside noexcept condition.
|
||||||
|
m_PlayState.m_nMusicTempo.SetRaw(std::min(param.GetRaw(), specs.GetTempoMax().GetRaw()));
|
||||||
|
#else
|
||||||
m_PlayState.m_nMusicTempo = std::min(param, specs.GetTempoMax());
|
m_PlayState.m_nMusicTempo = std::min(param, specs.GetTempoMax());
|
||||||
|
#endif
|
||||||
} else if(param < minTempo && !m_SongFlags[SONG_FIRSTTICK])
|
} else if(param < minTempo && !m_SongFlags[SONG_FIRSTTICK])
|
||||||
{
|
{
|
||||||
// Tempo Slide
|
// Tempo Slide
|
||||||
|
@ -5771,32 +5769,32 @@ PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Define search source order
|
//Define search source order
|
||||||
PLUGINDEX nPlugin = 0;
|
PLUGINDEX plugin = 0;
|
||||||
switch (priority)
|
switch (priority)
|
||||||
{
|
{
|
||||||
case ChannelOnly:
|
case ChannelOnly:
|
||||||
nPlugin = GetChannelPlugin(nChn, respectMutes);
|
plugin = GetChannelPlugin(nChn, respectMutes);
|
||||||
break;
|
break;
|
||||||
case InstrumentOnly:
|
case InstrumentOnly:
|
||||||
nPlugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
||||||
break;
|
break;
|
||||||
case PrioritiseInstrument:
|
case PrioritiseInstrument:
|
||||||
nPlugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
||||||
if ((!nPlugin) || (nPlugin > MAX_MIXPLUGINS))
|
if(!plugin || plugin > MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
nPlugin = GetChannelPlugin(nChn, respectMutes);
|
plugin = GetChannelPlugin(nChn, respectMutes);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PrioritiseChannel:
|
case PrioritiseChannel:
|
||||||
nPlugin = GetChannelPlugin(nChn, respectMutes);
|
plugin = GetChannelPlugin(nChn, respectMutes);
|
||||||
if ((!nPlugin) || (nPlugin > MAX_MIXPLUGINS))
|
if(!plugin || plugin > MAX_MIXPLUGINS)
|
||||||
{
|
{
|
||||||
nPlugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nPlugin; // 0 Means no plugin found.
|
return plugin; // 0 Means no plugin found.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5804,10 +5802,10 @@ PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority res
|
||||||
{
|
{
|
||||||
const ModChannel &channel = m_PlayState.Chn[nChn];
|
const ModChannel &channel = m_PlayState.Chn[nChn];
|
||||||
|
|
||||||
PLUGINDEX nPlugin;
|
PLUGINDEX plugin;
|
||||||
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE]) || channel.dwFlags[CHN_NOFX])
|
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE]) || channel.dwFlags[CHN_NOFX])
|
||||||
{
|
{
|
||||||
nPlugin = 0;
|
plugin = 0;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
// If it looks like this is an NNA channel, we need to find the master channel.
|
// If it looks like this is an NNA channel, we need to find the master channel.
|
||||||
|
@ -5820,13 +5818,13 @@ PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority res
|
||||||
|
|
||||||
if(nChn < MAX_BASECHANNELS)
|
if(nChn < MAX_BASECHANNELS)
|
||||||
{
|
{
|
||||||
nPlugin = ChnSettings[nChn].nMixPlugin;
|
plugin = ChnSettings[nChn].nMixPlugin;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
nPlugin = 0;
|
plugin = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nPlugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -779,7 +779,7 @@ public:
|
||||||
void SaveExtendedInstrumentProperties(INSTRUMENTINDEX nInstruments, FILE* f) const;
|
void SaveExtendedInstrumentProperties(INSTRUMENTINDEX nInstruments, FILE* f) const;
|
||||||
void SaveExtendedSongProperties(FILE* f) const;
|
void SaveExtendedSongProperties(FILE* f) const;
|
||||||
#endif // MODPLUG_NO_FILESAVE
|
#endif // MODPLUG_NO_FILESAVE
|
||||||
void LoadExtendedSongProperties(FileReader &file, bool* pInterpretMptMade = nullptr);
|
void LoadExtendedSongProperties(FileReader &file, bool ignoreChannelCount, bool* pInterpretMptMade = nullptr);
|
||||||
void LoadMPTMProperties(FileReader &file, uint16 cwtv);
|
void LoadMPTMProperties(FileReader &file, uint16 cwtv);
|
||||||
|
|
||||||
mpt::ustring GetSchismTrackerVersion(uint16 cwtv);
|
mpt::ustring GetSchismTrackerVersion(uint16 cwtv);
|
||||||
|
|
|
@ -179,6 +179,7 @@ static constexpr ModCharsetInfo ModCharsetInfos[] =
|
||||||
{ MOD_TYPE_AMS , mpt::CharsetCP437 },
|
{ MOD_TYPE_AMS , mpt::CharsetCP437 },
|
||||||
{ MOD_TYPE_AMS2, mpt::CharsetCP437 },
|
{ MOD_TYPE_AMS2, mpt::CharsetCP437 },
|
||||||
{ MOD_TYPE_DSM , mpt::CharsetCP437 },
|
{ MOD_TYPE_DSM , mpt::CharsetCP437 },
|
||||||
|
{ MOD_TYPE_PLM , mpt::CharsetCP437 },
|
||||||
// Windows
|
// Windows
|
||||||
{ MOD_TYPE_MT2 , mpt::CharsetWindows1252},
|
{ MOD_TYPE_MT2 , mpt::CharsetWindows1252},
|
||||||
{ MOD_TYPE_MPT , mpt::CharsetWindows1252},
|
{ MOD_TYPE_MPT , mpt::CharsetWindows1252},
|
||||||
|
|
|
@ -27,6 +27,7 @@ DigiBoosterEcho::DigiBoosterEcho(VSTPluginLib &factory, CSoundFile &sndFile, SND
|
||||||
, m_bufferSize(0)
|
, m_bufferSize(0)
|
||||||
, m_writePos(0)
|
, m_writePos(0)
|
||||||
, m_sampleRate(sndFile.GetSampleRate())
|
, m_sampleRate(sndFile.GetSampleRate())
|
||||||
|
, m_chunk(PluginChunk::Default())
|
||||||
{
|
{
|
||||||
m_mixBuffer.Initialize(2, 2);
|
m_mixBuffer.Initialize(2, 2);
|
||||||
InsertIntoFactoryList();
|
InsertIntoFactoryList();
|
||||||
|
|
|
@ -32,15 +32,20 @@ public:
|
||||||
char id[4];
|
char id[4];
|
||||||
uint8 param[kEchoNumParameters];
|
uint8 param[kEchoNumParameters];
|
||||||
|
|
||||||
PluginChunk(uint8 delay = 80, uint8 feedback = 150, uint8 mix = 80, uint8 cross = 255)
|
static PluginChunk Create(uint8 delay, uint8 feedback, uint8 mix, uint8 cross)
|
||||||
{
|
{
|
||||||
memcpy(id, "Echo", 4);
|
|
||||||
param[kEchoDelay] = delay;
|
|
||||||
param[kEchoFeedback] = feedback;
|
|
||||||
param[kEchoMix] = mix;
|
|
||||||
param[kEchoCross] = cross;
|
|
||||||
|
|
||||||
STATIC_ASSERT(sizeof(PluginChunk) == 8);
|
STATIC_ASSERT(sizeof(PluginChunk) == 8);
|
||||||
|
PluginChunk result;
|
||||||
|
memcpy(result.id, "Echo", 4);
|
||||||
|
result.param[kEchoDelay] = delay;
|
||||||
|
result.param[kEchoFeedback] = feedback;
|
||||||
|
result.param[kEchoMix] = mix;
|
||||||
|
result.param[kEchoCross] = cross;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
static PluginChunk Default()
|
||||||
|
{
|
||||||
|
return Create(80, 150, 80, 255);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,10 @@ RATIOTYPE CTuningRTI::GetRatio(const NOTEINDEXTYPE& stepsFromCentre) const
|
||||||
RATIOTYPE CTuningRTI::GetRatio(const NOTEINDEXTYPE& baseNote, const STEPINDEXTYPE& baseStepDiff) const
|
RATIOTYPE CTuningRTI::GetRatio(const NOTEINDEXTYPE& baseNote, const STEPINDEXTYPE& baseStepDiff) const
|
||||||
{
|
{
|
||||||
const STEPINDEXTYPE fsCount = static_cast<STEPINDEXTYPE>(GetFineStepCount());
|
const STEPINDEXTYPE fsCount = static_cast<STEPINDEXTYPE>(GetFineStepCount());
|
||||||
|
if(fsCount < 0 || fsCount > FINESTEPCOUNT_MAX)
|
||||||
|
{
|
||||||
|
return s_DefaultFallbackRatio;
|
||||||
|
}
|
||||||
if(fsCount == 0 || baseStepDiff == 0)
|
if(fsCount == 0 || baseStepDiff == 0)
|
||||||
{
|
{
|
||||||
return GetRatio(static_cast<NOTEINDEXTYPE>(baseNote + baseStepDiff));
|
return GetRatio(static_cast<NOTEINDEXTYPE>(baseNote + baseStepDiff));
|
||||||
|
@ -222,8 +226,10 @@ RATIOTYPE CTuningRTI::GetRatio(const NOTEINDEXTYPE& baseNote, const STEPINDEXTYP
|
||||||
|
|
||||||
RATIOTYPE CTuningRTI::GetRatioFine(const NOTEINDEXTYPE& note, USTEPINDEXTYPE sd) const
|
RATIOTYPE CTuningRTI::GetRatioFine(const NOTEINDEXTYPE& note, USTEPINDEXTYPE sd) const
|
||||||
{
|
{
|
||||||
if(GetFineStepCount() <= 0)
|
if(GetFineStepCount() <= 0 || GetFineStepCount() > static_cast<USTEPINDEXTYPE>(FINESTEPCOUNT_MAX))
|
||||||
return 1;
|
{
|
||||||
|
return s_DefaultFallbackRatio;
|
||||||
|
}
|
||||||
|
|
||||||
//Neither of these should happen.
|
//Neither of these should happen.
|
||||||
if(sd <= 0) sd = 1;
|
if(sd <= 0) sd = 1;
|
||||||
|
|
|
@ -1669,6 +1669,17 @@ static MPT_NOINLINE void TestMisc2()
|
||||||
VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix( 1413064016).AsUTC()), TestDate2( 56, 46, 21, 11, 10, 2014 ));
|
VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix( 1413064016).AsUTC()), TestDate2( 56, 46, 21, 11, 10, 2014 ));
|
||||||
VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix( 1413064100).AsUTC()), TestDate2( 20, 48, 21, 11, 10, 2014 ));
|
VERIFY_EQUAL(Gregorian::FromTM(mpt::Date::Unix( 1413064100).AsUTC()), TestDate2( 20, 48, 21, 11, 10, 2014 ));
|
||||||
|
|
||||||
|
// https://github.com/kripken/emscripten/issues/4251
|
||||||
|
#if MPT_OS_EMSCRIPTEN
|
||||||
|
volatile int transpose = 32;
|
||||||
|
volatile int finetune = 0;
|
||||||
|
float exp = (transpose * 128.0f + finetune) * (1.0f / (12.0f * 128.0f));
|
||||||
|
float f = ::powf(2.0f, exp);
|
||||||
|
double d = ::pow (2.0 , (double)exp);
|
||||||
|
VERIFY_EQUAL_EPS(d, 6.349605, 0.00001);
|
||||||
|
VERIFY_EQUAL_EPS(f, 6.349605, 0.00001);
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue