aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/src
diff options
context:
space:
mode:
authorJean-Francois Mauguit <jfmauguit@mac.com>2024-09-24 09:03:25 -0400
committerGitHub <noreply@github.com>2024-09-24 09:03:25 -0400
commitbab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/src
parent4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff)
parent20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff)
downloadwinamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/src')
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/.clang-format169
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSD-3-Clause.txt25
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSL-1.0.txt23
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/audio/sample.hpp35
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/audio/span.hpp403
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/algorithm.hpp61
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/aligned_array.hpp99
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/alloc.hpp180
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/arithmetic_shift.hpp119
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/array.hpp82
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/bit.hpp400
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/check_platform.hpp38
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/compiletime_warning.hpp42
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/constexpr_throw.hpp56
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect.hpp16
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_compiler.hpp190
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libc.hpp42
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libcxx.hpp49
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_os.hpp117
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_quirks.hpp147
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/floatingpoint.hpp93
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/integer.hpp33
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/macros.hpp119
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/math.hpp88
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/memory.hpp273
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/namespace.hpp81
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/numbers.hpp177
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/numeric.hpp95
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/pointer.hpp54
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/preprocessor.hpp19
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_cast.hpp81
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_round.hpp48
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/secure.hpp198
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/semantic_version.hpp97
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/source_location.hpp115
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/span.hpp196
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_arithmetic_shift.hpp328
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_bit.hpp227
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_math.hpp55
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_cast.hpp112
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_round.hpp65
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_wrapping_divide.hpp178
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/utility.hpp179
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/version.hpp15
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/base/wrapping_divide.hpp36
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64.hpp137
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64url.hpp140
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/binary/hex.hpp89
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/binary/tests/tests_binary.hpp115
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/check/libc.hpp41
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/check/mfc.hpp19
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/check/windows.hpp25
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crc/crc.hpp205
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crc/tests/tests_crc.hpp49
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crypto/exception.hpp139
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crypto/hash.hpp180
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crypto/jwk.hpp531
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/crypto/tests/tests_crypto.hpp106
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/detect/dl.hpp27
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/detect/ltdl.hpp27
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/detect/mfc.hpp22
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/detect/nlohmann_json.hpp25
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/endian/floatingpoint.hpp485
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/endian/int24.hpp122
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/endian/integer.hpp493
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_floatingpoint.hpp81
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_integer.hpp133
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/environment/environment.hpp61
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/exception_text/exception_text.hpp74
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/default_floatingpoint.hpp91
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/default_formatter.hpp37
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/default_integer.hpp114
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/default_string.hpp28
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/helpers.hpp106
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/join.hpp42
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/message.hpp332
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/message_macros.hpp37
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/simple.hpp166
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_floatingpoint.hpp250
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_integer.hpp251
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_spec.hpp221
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_message.hpp78
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_simple.hpp189
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/base.hpp122
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/io.hpp367
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/io_span.hpp146
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/io_stdstream.hpp332
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/io_virtual_wrapper.hpp405
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io/tests/tests_io.hpp573
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/callbackstream.hpp51
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor.hpp419
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_callbackstream.hpp50
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_filename_traits.hpp63
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_memory.hpp45
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_stdstream.hpp50
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_filedata.hpp67
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_memory.hpp65
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata.hpp77
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base.hpp119
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_buffered.hpp122
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_seekable.hpp101
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_unseekable.hpp174
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_callbackstream.hpp199
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_memory.hpp101
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_stdstream.hpp117
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filereader.hpp594
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/io_write/buffer.hpp202
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/json/json.hpp73
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/library/library.hpp465
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/mutex/mutex.hpp211
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/class.hpp80
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/windows_version.hpp452
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/out_of_memory/out_of_memory.hpp81
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/parse/parse.hpp107
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/parse/split.hpp44
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/parse/tests/tests_parse.hpp82
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/path/path.hpp506
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/crand.hpp64
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/default_engines.hpp37
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/device.hpp304
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/engine.hpp160
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/engine_lcg.hpp85
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/random.hpp113
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/seed.hpp49
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/random/tests/tests_random.hpp85
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string/buffer.hpp344
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_buffer.hpp87
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_utility.hpp59
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string/types.hpp396
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string/utility.hpp359
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/macros.hpp24
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/tests/tests_string_transcode.hpp246
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/transcode.hpp1282
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/system_error/system_error.hpp170
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/test/test.hpp564
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/test/test_macros.hpp84
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/uuid/guid.hpp286
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/uuid/tests/tests_uuid.hpp102
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/uuid/uuid.hpp419
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/tests/tests_uuid_namespace.hpp52
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/uuid_namespace.hpp73
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp57
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp74
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp460
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp31
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp34
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp47
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp91
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp84
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp116
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp175
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp58
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp43
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp92
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp52
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp101
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp132
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp45
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp754
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp262
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp394
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp78
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp391
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp175
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp640
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp1405
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp251
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp457
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp209
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp262
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp83
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp572
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp72
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp504
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp167
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp1081
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp121
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp503
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp74
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp475
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp80
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp779
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp104
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp664
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp219
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp707
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp105
187 files changed, 35909 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/.clang-format b/Src/external_dependencies/openmpt-trunk/src/mpt/.clang-format
new file mode 100644
index 00000000..058b40db
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/.clang-format
@@ -0,0 +1,169 @@
+# clang-format 14
+
+Language: Cpp
+Standard: c++20
+
+AccessModifierOffset: -4 #?
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: Left
+AlignConsecutiveAssignments: false
+AlignConsecutiveBitFields: false
+AlignConsecutiveDeclarations: false
+AlignConsecutiveMacros: true
+AlignEscapedNewlines: DontAlign
+AlignOperands: AlignAfterOperator
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLambdasOnASingleLine: Inline
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros: []
+BinPackArguments: true
+BinPackParameters: false
+BitFieldColonSpacing: Both
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: MultiLine
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ #AfterObjCDeclaration
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: true
+#BreakAfterJavaFieldAnnotations
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BreakBeforeConceptDeclarations: true
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeComma
+BreakStringLiterals: false
+ColumnLimit: 0
+CommentPragmas: '' #?
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4 #?
+ContinuationIndentWidth: 4 #?
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+EmptyLineAfterAccessModifier: Leave
+EmptyLineBeforeAccessModifier: Leave
+FixNamespaceComments: true
+ForEachMacros: []
+IfMacros: ['MPT_MAYBE_CONSTANT_IF']
+IncludeBlocks: Preserve
+IncludeCategories: [] #?
+IncludeIsMainRegex: '' #?
+IncludeIsMainSourceRegex: '' #?
+IndentAccessModifiers: false
+IndentCaseLabels: true
+IndentCaseBlocks: true
+IndentExternBlock: NoIndent
+IndentGotoLabels: false
+IndentPPDirectives: None
+#IndentRequiresClause: true
+InsertTrailingCommas: None
+#BeforeHash
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+#JavaImportGroups
+#JavaScriptQuotes
+#JavaScriptWrapImports
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: OuterScope
+MacroBlockBegin: '^MPT_TEST_GROUP_BEGIN$' #?
+MacroBlockEnd: '^MPT_TEST_GROUP_END$' #?
+MaxEmptyLinesToKeep: 5
+NamespaceIndentation: None
+NamespaceMacros: [] #?
+#ObjCBinPackProtocolList
+#ObjCBlockIndentWidth
+#ObjCBreakBeforeNestedBlockParam
+#ObjCSpaceAfterProperty
+#ObjCSpaceBeforeProtocolList
+PackConstructorInitializers: Never
+#PenaltyBreakAssignment
+#PenaltyBreakBeforeFirstCallParameter
+#PenaltyBreakComment
+#PenaltyBreakFirstLessLess
+#PenaltyBreakOpenParenthesis
+#PenaltyBreakString
+#PenaltyBreakTemplateDeclaration
+#PenaltyExcessCharacter
+#PenaltyIndentedWhitespace
+#PenaltyReturnTypeOnItsOwnLine
+PointerAlignment: Middle
+PPIndentWidth: -1
+#RawStringFormats
+QualifierAlignment: Leave
+#QualifierOrder: ['static', 'inline', 'constexpr', 'volatile', 'const', 'restrict', 'type']
+ReferenceAlignment: Pointer
+ReflowComments: false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: false
+#SortJavaStaticImport
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+ AfterControlStatements: true
+ AfterForeachMacros: true
+ AfterFunctionDeclarationName: false
+ AfterFunctionDefinitionName: false
+ AfterIfMacros: true
+ AfterOverloadedOperator: false
+ #AfterRequiresInClause: false
+ #AfterRequiresInExpression: false
+ BeforeNonEmptyParentheses: false
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+StatementAttributeLikeMacros: []
+StatementMacros: [ '_Pragma', '__pragma', 'MPT_WARNING', 'MPT_TEST_GROUP_INLINE_IDENTIFIER', 'MPT_TEST_GROUP_INLINE', 'MPT_TEST_GROUP_STATIC' ] #?
+TabWidth: 4
+TypenameMacros: [] #?
+UseCRLF: false
+UseTab: ForContinuationAndIndentation
+WhitespaceSensitiveMacros:
+ - MPT_PP_STRINGIFY
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSD-3-Clause.txt b/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSD-3-Clause.txt
new file mode 100644
index 00000000..7c06acae
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSD-3-Clause.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors
+Copyright (c) 1997-2003, Olivier Lapicque
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the OpenMPT project nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSL-1.0.txt b/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSL-1.0.txt
new file mode 100644
index 00000000..1dad8e93
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/LICENSE.BSL-1.0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/audio/sample.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/audio/sample.hpp
new file mode 100644
index 00000000..ef347e7e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/audio/sample.hpp
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_AUDIO_SAMPLE_HPP
+#define MPT_AUDIO_SAMPLE_HPP
+
+
+
+#include "mpt/base/floatingpoint.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+using audio_sample_int = int16;
+using audio_sample_float = nativefloat;
+
+using audio_sample = std::conditional<mpt::float_traits<audio_sample_float>::is_hard, audio_sample_float, audio_sample_int>::type;
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BINARY_HEX_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/audio/span.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/audio/span.hpp
new file mode 100644
index 00000000..5653f8d8
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/audio/span.hpp
@@ -0,0 +1,403 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_AUDIO_SPAN_HPP
+#define MPT_AUDIO_SPAN_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <cassert>
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+// LxxxLxxxLxxxLxxxLxxx xRxxxRxxxRxxxRxxxRxx
+template <typename SampleType>
+struct audio_span_planar_strided {
+public:
+ using sample_type = SampleType;
+
+private:
+ SampleType * const * m_buffers;
+ std::ptrdiff_t m_frame_stride;
+ std::size_t m_channels;
+ std::size_t m_frames;
+
+public:
+ constexpr audio_span_planar_strided(SampleType * const * buffers, std::size_t channels, std::size_t frames, std::ptrdiff_t frame_stride) noexcept
+ : m_buffers(buffers)
+ , m_frame_stride(frame_stride)
+ , m_channels(channels)
+ , m_frames(frames) {
+ return;
+ }
+ SampleType * const * data_planar() const noexcept {
+ return m_buffers;
+ }
+ SampleType * data() const noexcept {
+ return nullptr;
+ }
+ SampleType & operator()(std::size_t channel, std::size_t frame) const {
+ return m_buffers[channel][static_cast<std::ptrdiff_t>(frame) * m_frame_stride];
+ }
+ bool is_contiguous() const noexcept {
+ return false;
+ }
+ bool channels_are_contiguous() const noexcept {
+ return false;
+ }
+ bool frames_are_contiguous() const noexcept {
+ return false;
+ }
+ std::size_t size_channels() const noexcept {
+ return m_channels;
+ }
+ std::size_t size_frames() const noexcept {
+ return m_frames;
+ }
+ std::size_t size_samples() const noexcept {
+ return m_channels * m_frames;
+ }
+ std::ptrdiff_t frame_stride() const noexcept {
+ return m_frame_stride;
+ }
+};
+
+
+// LLLLL RRRRR
+template <typename SampleType>
+struct audio_span_planar {
+public:
+ using sample_type = SampleType;
+
+private:
+ SampleType * const * m_buffers;
+ std::size_t m_channels;
+ std::size_t m_frames;
+
+public:
+ constexpr audio_span_planar(SampleType * const * buffers, std::size_t channels, std::size_t frames) noexcept
+ : m_buffers(buffers)
+ , m_channels(channels)
+ , m_frames(frames) {
+ return;
+ }
+ SampleType * const * data_planar() const noexcept {
+ return m_buffers;
+ }
+ SampleType * data() const noexcept {
+ return nullptr;
+ }
+ SampleType & operator()(std::size_t channel, std::size_t frame) const {
+ return m_buffers[channel][frame];
+ }
+ bool is_contiguous() const noexcept {
+ return false;
+ }
+ bool channels_are_contiguous() const noexcept {
+ return false;
+ }
+ bool frames_are_contiguous() const noexcept {
+ return false;
+ }
+ std::size_t size_channels() const noexcept {
+ return m_channels;
+ }
+ std::size_t size_frames() const noexcept {
+ return m_frames;
+ }
+ std::size_t size_samples() const noexcept {
+ return m_channels * m_frames;
+ }
+};
+
+
+// LLLLLRRRRR
+template <typename SampleType>
+struct audio_span_contiguous {
+public:
+ using sample_type = SampleType;
+
+private:
+ SampleType * const m_buffer;
+ std::size_t m_channels;
+ std::size_t m_frames;
+
+public:
+ constexpr audio_span_contiguous(SampleType * buffer, std::size_t channels, std::size_t frames) noexcept
+ : m_buffer(buffer)
+ , m_channels(channels)
+ , m_frames(frames) {
+ return;
+ }
+ SampleType * const * data_planar() const noexcept {
+ return nullptr;
+ }
+ SampleType * data() const noexcept {
+ return m_buffer;
+ }
+ SampleType & operator()(std::size_t channel, std::size_t frame) const {
+ return m_buffer[(m_frames * channel) + frame];
+ }
+ bool is_contiguous() const noexcept {
+ return true;
+ }
+ bool channels_are_contiguous() const noexcept {
+ return true;
+ }
+ bool frames_are_contiguous() const noexcept {
+ return false;
+ }
+ std::size_t size_channels() const noexcept {
+ return m_channels;
+ }
+ std::size_t size_frames() const noexcept {
+ return m_frames;
+ }
+ std::size_t size_samples() const noexcept {
+ return m_channels * m_frames;
+ }
+};
+
+
+// LRLRLRLRLR
+template <typename SampleType>
+struct audio_span_interleaved {
+public:
+ using sample_type = SampleType;
+
+private:
+ SampleType * const m_buffer;
+ std::size_t m_channels;
+ std::size_t m_frames;
+
+public:
+ constexpr audio_span_interleaved(SampleType * buffer, std::size_t channels, std::size_t frames) noexcept
+ : m_buffer(buffer)
+ , m_channels(channels)
+ , m_frames(frames) {
+ return;
+ }
+ SampleType * const * data_planar() const noexcept {
+ return nullptr;
+ }
+ SampleType * data() const noexcept {
+ return m_buffer;
+ }
+ SampleType & operator()(std::size_t channel, std::size_t frame) const {
+ return m_buffer[m_channels * frame + channel];
+ }
+ bool is_contiguous() const noexcept {
+ return true;
+ }
+ bool channels_are_contiguous() const noexcept {
+ return false;
+ }
+ bool frames_are_contiguous() const noexcept {
+ return true;
+ }
+ std::size_t size_channels() const noexcept {
+ return m_channels;
+ }
+ std::size_t size_frames() const noexcept {
+ return m_frames;
+ }
+ std::size_t size_samples() const noexcept {
+ return m_channels * m_frames;
+ }
+};
+
+
+struct audio_span_frames_are_contiguous_t {
+};
+
+struct audio_span_channels_are_contiguous_t {
+};
+
+struct audio_span_channels_are_planar_t {
+};
+
+struct audio_span_channels_are_planar_and_strided_t {
+};
+
+// LRLRLRLRLR
+inline constexpr audio_span_frames_are_contiguous_t audio_span_frames_are_contiguous;
+
+// LLLLLRRRRR
+inline constexpr audio_span_channels_are_contiguous_t audio_span_channels_are_contiguous;
+
+// LLLLL RRRRR
+inline constexpr audio_span_channels_are_planar_t audio_span_channels_are_planar;
+
+// LxxxLxxxLxxxLxxxLxxx xRxxxRxxxRxxxRxxxRxx
+inline constexpr audio_span_channels_are_planar_and_strided_t audio_span_channels_are_planar_and_strided;
+
+template <typename SampleType>
+struct audio_span {
+public:
+ using sample_type = SampleType;
+
+private:
+ union {
+ SampleType * const contiguous;
+ SampleType * const * const planes;
+ } m_buffer;
+ std::ptrdiff_t m_frame_stride;
+ std::ptrdiff_t m_channel_stride;
+ std::size_t m_channels;
+ std::size_t m_frames;
+
+public:
+ constexpr audio_span(audio_span_interleaved<SampleType> buffer) noexcept
+ : m_frame_stride(static_cast<std::ptrdiff_t>(buffer.size_channels()))
+ , m_channel_stride(1)
+ , m_channels(buffer.size_channels())
+ , m_frames(buffer.size_frames()) {
+ m_buffer.contiguous = buffer.data();
+ }
+ constexpr audio_span(SampleType * buffer, std::size_t channels, std::size_t frames, audio_span_frames_are_contiguous_t) noexcept
+ : m_frame_stride(static_cast<std::ptrdiff_t>(channels))
+ , m_channel_stride(1)
+ , m_channels(channels)
+ , m_frames(frames) {
+ m_buffer.contiguous = buffer;
+ }
+ constexpr audio_span(audio_span_contiguous<SampleType> buffer) noexcept
+ : m_frame_stride(1)
+ , m_channel_stride(buffer.size_frames())
+ , m_channels(buffer.size_channels())
+ , m_frames(buffer.size_frames()) {
+ m_buffer.contiguous = buffer.data();
+ }
+ constexpr audio_span(SampleType * buffer, std::size_t channels, std::size_t frames, audio_span_channels_are_contiguous_t) noexcept
+ : m_frame_stride(1)
+ , m_channel_stride(static_cast<std::ptrdiff_t>(frames))
+ , m_channels(channels)
+ , m_frames(frames) {
+ m_buffer.contiguous = buffer;
+ }
+ constexpr audio_span(audio_span_planar<SampleType> buffer) noexcept
+ : m_frame_stride(1)
+ , m_channel_stride(0)
+ , m_channels(buffer.size_channels())
+ , m_frames(buffer.size_frames()) {
+ m_buffer.planes = buffer.data_planar();
+ }
+ constexpr audio_span(SampleType * const * planes, std::size_t channels, std::size_t frames, audio_span_channels_are_planar_t) noexcept
+ : m_frame_stride(1)
+ , m_channel_stride(0)
+ , m_channels(channels)
+ , m_frames(frames) {
+ m_buffer.planes = planes;
+ }
+ constexpr audio_span(audio_span_planar_strided<SampleType> buffer) noexcept
+ : m_frame_stride(static_cast<std::ptrdiff_t>(buffer.frame_stride()))
+ , m_channel_stride(0)
+ , m_channels(buffer.size_channels())
+ , m_frames(buffer.size_frames()) {
+ m_buffer.planes = buffer.data_planar();
+ }
+ constexpr audio_span(SampleType * const * planes, std::size_t channels, std::size_t frames, std::ptrdiff_t frame_stride, audio_span_channels_are_planar_and_strided_t) noexcept
+ : m_frame_stride(frame_stride)
+ , m_channel_stride(0)
+ , m_channels(channels)
+ , m_frames(frames) {
+ m_buffer.planes = planes;
+ }
+ bool is_contiguous() const noexcept {
+ return (m_channel_stride != 0);
+ }
+ SampleType * const * data_planar() const noexcept {
+ return (!is_contiguous()) ? m_buffer.planes : nullptr;
+ }
+ SampleType * data() const noexcept {
+ return is_contiguous() ? m_buffer.contiguous : nullptr;
+ }
+ SampleType & operator()(std::size_t channel, std::size_t frame) const {
+ return is_contiguous() ? m_buffer.contiguous[(m_channel_stride * static_cast<std::ptrdiff_t>(channel)) + (m_frame_stride * static_cast<std::ptrdiff_t>(frame))] : m_buffer.planes[channel][frame * static_cast<std::ptrdiff_t>(m_frame_stride)];
+ }
+ bool channels_are_contiguous() const noexcept {
+ return (m_channel_stride == static_cast<std::ptrdiff_t>(m_frames));
+ }
+ bool frames_are_contiguous() const noexcept {
+ return (m_frame_stride == static_cast<std::ptrdiff_t>(m_channels));
+ }
+ std::size_t size_channels() const noexcept {
+ return m_channels;
+ }
+ std::size_t size_frames() const noexcept {
+ return m_frames;
+ }
+ std::size_t size_samples() const noexcept {
+ return m_channels * m_frames;
+ }
+};
+
+
+template <typename Taudio_span>
+struct audio_span_with_offset {
+public:
+ using sample_type = typename Taudio_span::sample_type;
+
+private:
+ Taudio_span m_buffer;
+ std::size_t m_offset;
+
+public:
+ constexpr audio_span_with_offset(Taudio_span buffer, std::size_t offsetFrames) noexcept
+ : m_buffer(buffer)
+ , m_offset(offsetFrames) {
+ return;
+ }
+ sample_type * data() const noexcept {
+ if (!is_contiguous()) {
+ return nullptr;
+ }
+ return m_buffer.data() + (size_channels() * m_offset);
+ }
+ sample_type & operator()(std::size_t channel, std::size_t frame) const {
+ return m_buffer(channel, m_offset + frame);
+ }
+ bool is_contiguous() const noexcept {
+ return m_buffer.is_contiguous() && m_buffer.frames_are_contiguous();
+ }
+ bool channels_are_contiguous() const noexcept {
+ return m_buffer.channels_are_contiguous();
+ }
+ bool frames_are_contiguous() const noexcept {
+ return m_buffer.frames_are_contiguous();
+ }
+ std::size_t size_channels() const noexcept {
+ return m_buffer.size_channels();
+ }
+ std::size_t size_frames() const noexcept {
+ return m_buffer.size_frames() - m_offset;
+ }
+ std::size_t size_samples() const noexcept {
+ return size_channels() * size_frames();
+ }
+};
+
+
+
+template <typename BufferType>
+inline audio_span_with_offset<BufferType> make_audio_span_with_offset(BufferType buf, std::size_t offsetFrames) noexcept {
+ assert(offsetFrames <= buf.size_frames());
+ return audio_span_with_offset<BufferType>{buf, offsetFrames};
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_AUDIO_SPAN_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/algorithm.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/algorithm.hpp
new file mode 100644
index 00000000..8bb7a786
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/algorithm.hpp
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_ALGORITHM_HPP
+#define MPT_BASE_ALGORITHM_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/saturate_cast.hpp"
+
+#include <algorithm>
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Grows x with an exponential factor suitable for increasing buffer sizes.
+// Clamps the result at limit.
+// And avoids integer overflows while doing its business.
+// The growth factor is 1.5, rounding down, execpt for the initial x==1 case.
+template <typename T, typename Tlimit>
+inline T exponential_grow(const T & x, const Tlimit & limit) {
+ if (x <= 1) {
+ return 2;
+ }
+ T add = std::min(x >> 1, std::numeric_limits<T>::max() - x);
+ return std::min(x + add, mpt::saturate_cast<T>(limit));
+}
+
+template <typename T>
+inline T exponential_grow(const T & x) {
+ return mpt::exponential_grow(x, std::numeric_limits<T>::max());
+}
+
+
+// Check if val is in [lo,hi] without causing compiler warnings
+// if theses checks are always true due to the domain of T.
+// GCC does not warn if the type is templated.
+template <typename T, typename C>
+constexpr bool is_in_range(const T & val, const C & lo, const C & hi) {
+ return lo <= val && val <= hi;
+}
+
+
+template <typename Tcontainer, typename Tval>
+MPT_CONSTEXPR20_FUN bool contains(const Tcontainer & container, const Tval & value) noexcept(noexcept(std::find(std::begin(container), std::end(container), value))) {
+ return std::find(std::begin(container), std::end(container), value) != std::end(container);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ALGORITHM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/aligned_array.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/aligned_array.hpp
new file mode 100644
index 00000000..218c9062
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/aligned_array.hpp
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_ALIGNED_ARRAY_HPP
+#define MPT_BASE_ALIGNED_ARRAY_HPP
+
+
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#include <memory>
+#include <new>
+
+#include <cassert>
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename T, std::size_t count, std::align_val_t alignment>
+struct alignas(static_cast<std::size_t>(alignment)) aligned_array
+ : std::array<T, count> {
+ static_assert(static_cast<std::size_t>(alignment) >= alignof(T));
+ static_assert(((count * sizeof(T)) % static_cast<std::size_t>(alignment)) == 0);
+ static_assert(sizeof(std::array<T, count>) == (sizeof(T) * count));
+};
+
+static_assert(sizeof(mpt::aligned_array<float, 4, std::align_val_t{sizeof(float) * 4}>) == sizeof(std::array<float, 4>));
+
+
+
+template <std::size_t alignment_elements, std::size_t expected_elements, typename T, std::size_t N>
+T * align_elements(std::array<T, N> & a) {
+ static_assert(mpt::has_single_bit(alignof(T)));
+ static_assert(mpt::has_single_bit(sizeof(T)));
+ static_assert(mpt::has_single_bit(alignment_elements));
+ static_assert((expected_elements + alignment_elements - 1) <= N);
+ void * buf = a.data();
+ std::size_t size = N * sizeof(T);
+ void * result = std::align(alignment_elements * sizeof(T), expected_elements * sizeof(T), buf, size);
+ assert(result);
+ return reinterpret_cast<T *>(result);
+}
+
+template <std::size_t alignment_elements, std::size_t expected_elements, typename T, std::size_t N>
+T * align_elements(T (&a)[N]) {
+ static_assert(mpt::has_single_bit(alignof(T)));
+ static_assert(mpt::has_single_bit(sizeof(T)));
+ static_assert(mpt::has_single_bit(alignment_elements));
+ static_assert((expected_elements + alignment_elements - 1) <= N);
+ void * buf = a;
+ std::size_t size = N * sizeof(T);
+ void * result = std::align(alignment_elements * sizeof(T), expected_elements * sizeof(T), buf, size);
+ assert(result);
+ return reinterpret_cast<T *>(result);
+}
+
+
+
+template <std::size_t alignment_bytes, std::size_t expected_elements, typename T, std::size_t N>
+T * align_bytes(std::array<T, N> & a) {
+ static_assert(mpt::has_single_bit(alignof(T)));
+ static_assert(mpt::has_single_bit(sizeof(T)));
+ static_assert(mpt::has_single_bit(alignment_bytes));
+ static_assert((alignment_bytes % alignof(T)) == 0);
+ static_assert(((expected_elements * sizeof(T)) + alignment_bytes - 1) <= (N * sizeof(T)));
+ void * buf = a.data();
+ std::size_t size = N * sizeof(T);
+ void * result = std::align(alignment_bytes, expected_elements * sizeof(T), buf, size);
+ assert(result);
+ return reinterpret_cast<T *>(result);
+}
+
+template <std::size_t alignment_bytes, std::size_t expected_elements, typename T, std::size_t N>
+T * align_bytes(T (&a)[N]) {
+ static_assert(mpt::has_single_bit(alignof(T)));
+ static_assert(mpt::has_single_bit(sizeof(T)));
+ static_assert(mpt::has_single_bit(alignment_bytes));
+ static_assert((alignment_bytes % alignof(T)) == 0);
+ static_assert(((expected_elements * sizeof(T)) + alignment_bytes - 1) <= (N * sizeof(T)));
+ void * buf = a;
+ std::size_t size = N * sizeof(T);
+ void * result = std::align(alignment_bytes, expected_elements * sizeof(T), buf, size);
+ assert(result);
+ return reinterpret_cast<T *>(result);
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ALIGNED_ARRAY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/alloc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/alloc.hpp
new file mode 100644
index 00000000..075dd035
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/alloc.hpp
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_ALLOC_HPP
+#define MPT_BASE_ALLOC_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/span.hpp"
+
+#include <iterator>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename T>
+inline mpt::span<T> as_span(std::vector<T> & cont) {
+ return mpt::span<T>(cont.data(), cont.data() + cont.size());
+}
+
+template <typename T>
+inline mpt::span<const T> as_span(const std::vector<T> & cont) {
+ return mpt::span<const T>(cont.data(), cont.data() + cont.size());
+}
+
+template <typename T>
+inline span<T> as_span(std::basic_string<T> & str) {
+ return span<T>(str.data(), str.size());
+}
+
+template <typename T>
+inline span<const T> as_span(const std::basic_string<T> & str) {
+ return span<const T>(str.data(), str.size());
+}
+
+
+
+template <typename T>
+inline std::vector<typename std::remove_const<T>::type> make_vector(T * beg, T * end) {
+ return std::vector<typename std::remove_const<T>::type>(beg, end);
+}
+
+template <typename T>
+inline std::vector<typename std::remove_const<T>::type> make_vector(T * data, std::size_t size) {
+ return std::vector<typename std::remove_const<T>::type>(data, data + size);
+}
+
+template <typename T>
+inline std::vector<typename std::remove_const<T>::type> make_vector(mpt::span<T> data) {
+ return std::vector<typename std::remove_const<T>::type>(data.data(), data.data() + data.size());
+}
+
+template <typename T, std::size_t N>
+inline std::vector<typename std::remove_const<T>::type> make_vector(T (&arr)[N]) {
+ return std::vector<typename std::remove_const<T>::type>(std::begin(arr), std::end(arr));
+}
+
+template <typename T>
+inline std::vector<typename std::remove_const<T>::type> make_vector(const std::basic_string<T> & str) {
+ return std::vector<typename std::remove_const<T>::type>(str.begin(), str.end());
+}
+
+
+
+template <typename T>
+inline std::basic_string<typename std::remove_const<T>::type> make_basic_string(T * beg, T * end) {
+ return std::basic_string<typename std::remove_const<T>::type>(beg, end);
+}
+
+template <typename T>
+inline std::basic_string<typename std::remove_const<T>::type> make_basic_string(T * data, std::size_t size) {
+ return std::basic_string<typename std::remove_const<T>::type>(data, data + size);
+}
+
+template <typename T>
+inline std::basic_string<typename std::remove_const<T>::type> make_basic_string(mpt::span<T> data) {
+ return std::basic_string<typename std::remove_const<T>::type>(data.data(), data.data() + data.size());
+}
+
+template <typename T, std::size_t N>
+inline std::basic_string<typename std::remove_const<T>::type> make_basic_string(T (&arr)[N]) {
+ return std::basic_string<typename std::remove_const<T>::type>(std::begin(arr), std::end(arr));
+}
+
+template <typename T>
+inline std::basic_string<typename std::remove_const<T>::type> make_basic_string(const std::vector<T> & str) {
+ return std::vector<typename std::remove_const<T>::type>(str.begin(), str.end());
+}
+
+
+
+template <typename Tcont2, typename Tcont1>
+inline Tcont1 & append(Tcont1 & cont1, const Tcont2 & cont2) {
+ cont1.insert(cont1.end(), cont2.begin(), cont2.end());
+ return cont1;
+}
+
+template <typename Tit2, typename Tcont1>
+inline Tcont1 & append(Tcont1 & cont1, Tit2 beg, Tit2 end) {
+ cont1.insert(cont1.end(), beg, end);
+ return cont1;
+}
+
+
+
+template <typename Tdst, typename Tsrc>
+struct buffer_cast_impl {
+ inline Tdst operator()(const Tsrc & src) const {
+ return Tdst(mpt::byte_cast<const typename Tdst::value_type *>(src.data()), mpt::byte_cast<const typename Tdst::value_type *>(src.data()) + src.size());
+ }
+};
+
+// casts between vector<->string of byte-castable types
+template <typename Tdst, typename Tsrc>
+inline Tdst buffer_cast(Tsrc src) {
+ return buffer_cast_impl<Tdst, Tsrc>()(src);
+}
+
+
+
+template <typename T>
+struct as_raw_memory_impl<std::vector<T>> {
+ inline mpt::const_byte_span operator()(const std::vector<T> & v) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<const std::byte *>(v.data()), v.size() * sizeof(T));
+ }
+ inline mpt::byte_span operator()(std::vector<T> & v) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<std::byte *>(v.data()), v.size() * sizeof(T));
+ }
+};
+
+template <typename T>
+struct as_raw_memory_impl<const std::vector<T>> {
+ inline mpt::const_byte_span operator()(const std::vector<T> & v) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<const std::byte *>(v.data()), v.size() * sizeof(T));
+ }
+};
+
+
+
+template <typename T>
+class heap_value {
+private:
+ std::unique_ptr<T> m_value{};
+
+public:
+ template <typename... Targs>
+ heap_value(Targs &&... args)
+ : m_value(std::make_unique<T>(std::forward<Targs>(args)...)) {
+ return;
+ }
+ const T & operator*() const {
+ return *m_value;
+ }
+ T & operator*() {
+ return *m_value;
+ }
+};
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ALLOC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/arithmetic_shift.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/arithmetic_shift.hpp
new file mode 100644
index 00000000..65a6951f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/arithmetic_shift.hpp
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_ARITHMETIC_SHIFT_HPP
+#define MPT_BASE_ARITHMETIC_SHIFT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/saturate_cast.hpp"
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// mpt::rshift_signed
+// mpt::lshift_signed
+// Shift a signed integer value in a well-defined manner.
+// Does the same thing as MSVC would do. This is verified by the test suite.
+
+template <typename T>
+constexpr auto rshift_signed_standard(T x, int y) noexcept -> decltype(x >> y) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::numeric_limits<T>::is_signed);
+ using result_type = decltype(x >> y);
+ using unsigned_result_type = typename std::make_unsigned<result_type>::type;
+ const unsigned_result_type roffset = static_cast<unsigned_result_type>(1) << ((sizeof(result_type) * 8) - 1);
+ result_type rx = x;
+ unsigned_result_type urx = static_cast<unsigned_result_type>(rx);
+ urx += roffset;
+ urx >>= y;
+ urx -= roffset >> y;
+ return static_cast<result_type>(urx);
+}
+
+template <typename T>
+constexpr auto lshift_signed_standard(T x, int y) noexcept -> decltype(x << y) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::numeric_limits<T>::is_signed);
+ using result_type = decltype(x << y);
+ using unsigned_result_type = typename std::make_unsigned<result_type>::type;
+ const unsigned_result_type roffset = static_cast<unsigned_result_type>(1) << ((sizeof(result_type) * 8) - 1);
+ result_type rx = x;
+ unsigned_result_type urx = static_cast<unsigned_result_type>(rx);
+ urx += roffset;
+ urx <<= y;
+ urx -= roffset << y;
+ return static_cast<result_type>(urx);
+}
+
+#if MPT_COMPILER_SHIFT_SIGNED
+
+template <typename T>
+constexpr auto rshift_signed_undefined(T x, int y) noexcept -> decltype(x >> y) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::numeric_limits<T>::is_signed);
+ return x >> y;
+}
+
+template <typename T>
+constexpr auto lshift_signed_undefined(T x, int y) noexcept -> decltype(x << y) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::numeric_limits<T>::is_signed);
+ return x << y;
+}
+
+template <typename T>
+constexpr auto rshift_signed(T x, int y) noexcept -> decltype(x >> y) {
+ return mpt::rshift_signed_undefined(x, y);
+}
+
+template <typename T>
+constexpr auto lshift_signed(T x, int y) noexcept -> decltype(x << y) {
+ return mpt::lshift_signed_undefined(x, y);
+}
+
+#else
+
+template <typename T>
+constexpr auto rshift_signed(T x, int y) noexcept -> decltype(x >> y) {
+ return mpt::rshift_signed_standard(x, y);
+}
+
+template <typename T>
+constexpr auto lshift_signed(T x, int y) noexcept -> decltype(x << y) {
+ return mpt::lshift_signed_standard(x, y);
+}
+
+#endif
+
+template <typename T>
+constexpr auto arithmetic_shift_right(T x, int y) noexcept -> decltype(x >> y) {
+ return mpt::rshift_signed(x, y);
+}
+
+template <typename T>
+constexpr auto arithmetic_shift_right(T x, int y) noexcept -> decltype(x << y) {
+ return mpt::lshift_signed(x, y);
+}
+
+template <typename T>
+constexpr auto sar(T x, int y) noexcept -> decltype(x >> y) {
+ return mpt::rshift_signed(x, y);
+}
+
+template <typename T>
+constexpr auto sal(T x, int y) noexcept -> decltype(x << y) {
+ return mpt::lshift_signed(x, y);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ARITHMETIC_SHIFT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/array.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/array.hpp
new file mode 100644
index 00000000..255aff46
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/array.hpp
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_ARRAY_HPP
+#define MPT_BASE_ARRAY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#include <type_traits>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename T>
+struct stdarray_extent : std::integral_constant<std::size_t, 0> { };
+
+template <typename T, std::size_t N>
+struct stdarray_extent<std::array<T, N>> : std::integral_constant<std::size_t, N> { };
+
+template <typename T>
+struct is_stdarray : std::false_type { };
+
+template <typename T, std::size_t N>
+struct is_stdarray<std::array<T, N>> : std::true_type { };
+
+// mpt::extent is the same as std::extent,
+// but also works for std::array,
+// and asserts that the given type is actually an array type instead of returning 0.
+// use as:
+// mpt::extent<decltype(expr)>()
+// mpt::extent<decltype(variable)>()
+// mpt::extent<decltype(type)>()
+// mpt::extent<type>()
+template <typename T>
+constexpr std::size_t extent() noexcept {
+ using Tarray = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+ static_assert(std::is_array<Tarray>::value || mpt::is_stdarray<Tarray>::value);
+ if constexpr (mpt::is_stdarray<Tarray>::value) {
+ return mpt::stdarray_extent<Tarray>();
+ } else {
+ return std::extent<Tarray>();
+ }
+}
+
+template <typename>
+struct array_size;
+
+template <typename T, std::size_t N>
+struct array_size<std::array<T, N>> {
+ static constexpr std::size_t size = N;
+};
+
+template <typename T, std::size_t N>
+struct array_size<T[N]> {
+ static constexpr std::size_t size = N;
+};
+
+
+template <typename T, std::size_t N, typename Tx>
+constexpr std::array<T, N> init_array(const Tx & x) {
+ std::array<T, N> result{};
+ for (std::size_t i = 0; i < N; ++i) {
+ result[i] = x;
+ }
+ return result;
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ARRAY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/bit.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/bit.hpp
new file mode 100644
index 00000000..3bfa98eb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/bit.hpp
@@ -0,0 +1,400 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_BIT_HPP
+#define MPT_BASE_BIT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/macros.hpp"
+
+#if MPT_CXX_AT_LEAST(20)
+#include <bit>
+#else // !C++20
+#include <array>
+#include <limits>
+#endif // C++20
+#include <type_traits>
+
+#include <cstddef>
+#if MPT_CXX_BEFORE(20)
+#include <cstring>
+#endif // !C++20
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(14, 0, 0)
+using std::bit_cast;
+#else // !C++20
+// C++2a compatible bit_cast.
+// Not implementing constexpr because this is not easily possible pre C++20.
+template <typename Tdst, typename Tsrc>
+MPT_FORCEINLINE typename std::enable_if<(sizeof(Tdst) == sizeof(Tsrc)) && std::is_trivially_copyable<Tsrc>::value && std::is_trivially_copyable<Tdst>::value, Tdst>::type bit_cast(const Tsrc & src) noexcept {
+ Tdst dst{};
+ std::memcpy(&dst, &src, sizeof(Tdst));
+ return dst;
+}
+#endif // C++20
+
+
+
+#if MPT_CXX_AT_LEAST(20)
+
+using std::endian;
+
+static_assert(mpt::endian::big != mpt::endian::little, "platform with all scalar types having size 1 is not supported");
+
+constexpr mpt::endian get_endian() noexcept {
+ return mpt::endian::native;
+}
+
+constexpr bool endian_is_little() noexcept {
+ return get_endian() == mpt::endian::little;
+}
+
+constexpr bool endian_is_big() noexcept {
+ return get_endian() == mpt::endian::big;
+}
+
+constexpr bool endian_is_weird() noexcept {
+ return !endian_is_little() && !endian_is_big();
+}
+
+#else // !C++20
+
+#if !MPT_COMPILER_GENERIC
+
+#if MPT_COMPILER_MSVC
+#define MPT_PLATFORM_LITTLE_ENDIAN
+#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define MPT_PLATFORM_BIG_ENDIAN
+#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define MPT_PLATFORM_LITTLE_ENDIAN
+#endif
+#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && defined(__ORDER_LITTLE_ENDIAN__)
+#if __ORDER_BIG_ENDIAN__ != __ORDER_LITTLE_ENDIAN__
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define MPT_PLATFORM_BIG_ENDIAN
+#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define MPT_PLATFORM_LITTLE_ENDIAN
+#endif
+#endif
+#endif
+
+// fallback:
+#if !defined(MPT_PLATFORM_BIG_ENDIAN) && !defined(MPT_PLATFORM_LITTLE_ENDIAN)
+#if (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) \
+ || (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) \
+ || (defined(_STLP_BIG_ENDIAN) && !defined(_STLP_LITTLE_ENDIAN))
+#define MPT_PLATFORM_BIG_ENDIAN
+#elif (defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)) \
+ || (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) \
+ || (defined(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN))
+#define MPT_PLATFORM_LITTLE_ENDIAN
+#elif defined(__hpux) || defined(__hppa) \
+ || defined(_MIPSEB) \
+ || defined(__s390__)
+#define MPT_PLATFORM_BIG_ENDIAN
+#elif defined(__i386__) || defined(_M_IX86) \
+ || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \
+ || defined(__bfin__)
+#define MPT_PLATFORM_LITTLE_ENDIAN
+#endif
+#endif
+
+#endif // !MPT_COMPILER_GENERIC
+
+enum class endian {
+ little = 0x78563412u,
+ big = 0x12345678u,
+ weird = 1u,
+#if MPT_COMPILER_GENERIC
+ native = 0u,
+#elif defined(MPT_PLATFORM_LITTLE_ENDIAN)
+ native = little,
+#elif defined(MPT_PLATFORM_BIG_ENDIAN)
+ native = big,
+#else
+ native = 0u,
+#endif
+};
+
+static_assert(mpt::endian::big != mpt::endian::little, "platform with all scalar types having size 1 is not supported");
+
+MPT_FORCEINLINE mpt::endian endian_probe() noexcept {
+ using endian_probe_type = uint32;
+ static_assert(sizeof(endian_probe_type) == 4);
+ constexpr endian_probe_type endian_probe_big = 0x12345678u;
+ constexpr endian_probe_type endian_probe_little = 0x78563412u;
+ const std::array<std::byte, sizeof(endian_probe_type)> probe{
+ {std::byte{0x12}, std::byte{0x34}, std::byte{0x56}, std::byte{0x78}}
+ };
+ const endian_probe_type test = mpt::bit_cast<endian_probe_type>(probe);
+ mpt::endian result = mpt::endian::native;
+ switch (test) {
+ case endian_probe_big:
+ result = mpt::endian::big;
+ break;
+ case endian_probe_little:
+ result = mpt::endian::little;
+ break;
+ default:
+ result = mpt::endian::weird;
+ break;
+ }
+ return result;
+}
+
+MPT_FORCEINLINE mpt::endian get_endian() noexcept {
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 6285) // false-positive: (<non-zero constant> || <non-zero constant>) is always a non-zero constant.
+#endif // MPT_COMPILER_MSVC
+ if constexpr ((mpt::endian::native == mpt::endian::little) || (mpt::endian::native == mpt::endian::big)) {
+ return mpt::endian::native;
+ } else {
+ return mpt::endian_probe();
+ }
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif // MPT_COMPILER_MSVC
+}
+
+MPT_FORCEINLINE bool endian_is_little() noexcept {
+ return get_endian() == mpt::endian::little;
+}
+
+MPT_FORCEINLINE bool endian_is_big() noexcept {
+ return get_endian() == mpt::endian::big;
+}
+
+MPT_FORCEINLINE bool endian_is_weird() noexcept {
+ return !endian_is_little() && !endian_is_big();
+}
+
+#endif // C++20
+
+
+
+#if MPT_CXX_AT_LEAST(20) && MPT_MSVC_AT_LEAST(2022, 1) && !MPT_CLANG_BEFORE(12, 0, 0)
+
+// Disabled for VS2022.0 because of
+// <https://developercommunity.visualstudio.com/t/vs2022-cl-193030705-generates-non-universally-avai/1578571>
+// / <https://github.com/microsoft/STL/issues/2330> with fix already queued
+// (<https://github.com/microsoft/STL/pull/2333>).
+
+using std::bit_ceil;
+using std::bit_floor;
+using std::bit_width;
+using std::countl_one;
+using std::countl_zero;
+using std::countr_one;
+using std::countr_zero;
+using std::has_single_bit;
+using std::popcount;
+using std::rotl;
+using std::rotr;
+
+#else // !C++20
+
+// C++20 <bit> header.
+// Note that we do not use SFINAE here but instead rely on static_assert.
+
+template <typename T>
+constexpr int popcount(T val) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ int result = 0;
+ while (val > 0) {
+ if (val & 0x1) {
+ result++;
+ }
+ val >>= 1;
+ }
+ return result;
+}
+
+template <typename T>
+constexpr bool has_single_bit(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ return mpt::popcount(x) == 1;
+}
+
+template <typename T>
+constexpr T bit_ceil(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ T result = 1;
+ while (result < x) {
+ T newresult = result << 1;
+ if (newresult < result) {
+ return 0;
+ }
+ result = newresult;
+ }
+ return result;
+}
+
+template <typename T>
+constexpr T bit_floor(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ if (x == 0) {
+ return 0;
+ }
+ T result = 1;
+ do {
+ T newresult = result << 1;
+ if (newresult < result) {
+ return result;
+ }
+ result = newresult;
+ } while (result <= x);
+ return result >> 1;
+}
+
+template <typename T>
+constexpr T bit_width(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ T result = 0;
+ while (x > 0) {
+ x >>= 1;
+ result += 1;
+ }
+ return result;
+}
+
+template <typename T>
+constexpr int countl_zero(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ int count = 0;
+ for (int bit = std::numeric_limits<T>::digits - 1; bit >= 0; --bit) {
+ if ((x & (1u << bit)) == 0u) {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+}
+
+template <typename T>
+constexpr int countl_one(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ int count = 0;
+ for (int bit = std::numeric_limits<T>::digits - 1; bit >= 0; --bit) {
+ if ((x & (1u << bit)) != 0u) {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+}
+
+template <typename T>
+constexpr int countr_zero(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ int count = 0;
+ for (int bit = 0; bit < std::numeric_limits<T>::digits; ++bit) {
+ if ((x & (1u << bit)) == 0u) {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+}
+
+template <typename T>
+constexpr int countr_one(T x) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ int count = 0;
+ for (int bit = 0; bit < std::numeric_limits<T>::digits; ++bit) {
+ if ((x & (1u << bit)) != 0u) {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+}
+
+template <typename T>
+constexpr T rotl_impl(T x, int r) noexcept {
+ auto N = std::numeric_limits<T>::digits;
+ return (x >> (N - r)) | (x << r);
+}
+
+template <typename T>
+constexpr T rotr_impl(T x, int r) noexcept {
+ auto N = std::numeric_limits<T>::digits;
+ return (x << (N - r)) | (x >> r);
+}
+
+template <typename T>
+constexpr T rotl(T x, int s) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ auto N = std::numeric_limits<T>::digits;
+ auto r = s % N;
+ return (s < 0) ? mpt::rotr_impl(x, -s) : ((x >> (N - r)) | (x << r));
+}
+
+template <typename T>
+constexpr T rotr(T x, int s) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(std::is_unsigned<T>::value);
+ auto N = std::numeric_limits<T>::digits;
+ auto r = s % N;
+ return (s < 0) ? mpt::rotl_impl(x, -s) : ((x << (N - r)) | (x >> r));
+}
+
+#endif // C++20
+
+
+
+template <typename T>
+constexpr int lower_bound_entropy_bits(T x_) {
+ typename std::make_unsigned<T>::type x = static_cast<typename std::make_unsigned<T>::type>(x_);
+ return mpt::bit_width(x) == static_cast<typename std::make_unsigned<T>::type>(mpt::popcount(x)) ? mpt::bit_width(x) : mpt::bit_width(x) - 1;
+}
+
+
+template <typename T>
+constexpr bool is_mask(T x) {
+ static_assert(std::is_integral<T>::value);
+ typedef typename std::make_unsigned<T>::type unsigned_T;
+ unsigned_T ux = static_cast<unsigned_T>(x);
+ unsigned_T mask = 0;
+ for (std::size_t bits = 0; bits <= (sizeof(unsigned_T) * 8); ++bits) {
+ mask = (mask << 1) | 1u;
+ if (ux == mask) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_BIT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/check_platform.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/check_platform.hpp
new file mode 100644
index 00000000..04f7f53b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/check_platform.hpp
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_CHECK_PLATFORM_HPP
+#define MPT_BASE_CHECK_PLATFORM_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/pointer.hpp"
+
+#include <limits>
+
+#include <cstddef>
+#include <cstdint>
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+static_assert(sizeof(std::uintptr_t) == sizeof(void *));
+static_assert(std::numeric_limits<unsigned char>::digits == 8);
+
+static_assert(sizeof(char) == 1);
+
+static_assert(sizeof(std::byte) == 1);
+static_assert(alignof(std::byte) == 1);
+
+static_assert(mpt::arch_bits == static_cast<int>(mpt::pointer_size) * 8);
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_CHECK_PLATFORM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/compiletime_warning.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/compiletime_warning.hpp
new file mode 100644
index 00000000..5f450021
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/compiletime_warning.hpp
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_COMPILETIME_WARNING_HPP
+#define MPT_BASE_COMPILETIME_WARNING_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/preprocessor.hpp"
+
+
+
+#if MPT_COMPILER_MSVC
+
+#define MPT_WARNING(text) __pragma(message(__FILE__ "(" MPT_PP_DEFER(MPT_PP_STRINGIFY, __LINE__) "): Warning: " text))
+#define MPT_WARNING_STATEMENT(text) __pragma(message(__FILE__ "(" MPT_PP_DEFER(MPT_PP_STRINGIFY, __LINE__) "): Warning: " text))
+
+#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG
+
+#define MPT_WARNING(text) _Pragma(MPT_PP_STRINGIFY(GCC warning text))
+#define MPT_WARNING_STATEMENT(text) _Pragma(MPT_PP_STRINGIFY(GCC warning text))
+
+#else
+
+// portable #pragma message or #warning replacement
+#define MPT_WARNING(text) \
+ static inline int MPT_PP_UNIQUE_IDENTIFIER(MPT_WARNING_NAME)() noexcept { \
+ int warning [[deprecated("Warning: " text)]] = 0; \
+ return warning; \
+ } \
+/**/
+#define MPT_WARNING_STATEMENT(text) \
+ int MPT_PP_UNIQUE_IDENTIFIER(MPT_WARNING_NAME) = []() { \
+ int warning [[deprecated("Warning: " text)]] = 0; \
+ return warning; \
+ }() /**/
+
+#endif
+
+
+
+#endif // MPT_BASE_COMPILETIME_WARNING_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/constexpr_throw.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/constexpr_throw.hpp
new file mode 100644
index 00000000..961268f2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/constexpr_throw.hpp
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_CONSTEXPR_THROW_HPP
+#define MPT_BASE_CONSTEXPR_THROW_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <utility>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Work-around for the requirement of at least 1 non-throwing function argument combination in C++ (17,2a).
+
+template <typename Exception>
+constexpr bool constexpr_throw_helper(Exception && e, bool really = true) {
+ //return !really ? really : throw std::forward<Exception>(e);
+ if (really) {
+ throw std::forward<Exception>(e);
+ }
+ // cppcheck-suppress identicalConditionAfterEarlyExit
+ return really;
+}
+
+template <typename Exception>
+constexpr bool constexpr_throw(Exception && e) {
+ return mpt::constexpr_throw_helper(std::forward<Exception>(e));
+}
+
+template <typename T, typename Exception>
+constexpr T constexpr_throw_helper(Exception && e, bool really = true) {
+ //return !really ? really : throw std::forward<Exception>(e);
+ if (really) {
+ throw std::forward<Exception>(e);
+ }
+ return T{};
+}
+
+template <typename T, typename Exception>
+constexpr T constexpr_throw(Exception && e) {
+ return mpt::constexpr_throw_helper<T>(std::forward<Exception>(e));
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_CONSTEXPR_THROW_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect.hpp
new file mode 100644
index 00000000..c7b1c15c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect.hpp
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_HPP
+#define MPT_BASE_DETECT_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_libc.hpp"
+#include "mpt/base/detect_libcxx.hpp"
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/detect_quirks.hpp"
+
+
+
+#endif // MPT_BASE_DETECT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_compiler.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_compiler.hpp
new file mode 100644
index 00000000..93628670
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_compiler.hpp
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_COMPILER_HPP
+#define MPT_BASE_DETECT_COMPILER_HPP
+
+
+
+#define MPT_COMPILER_MAKE_VERSION2(version, sp) ((version)*100 + (sp))
+#define MPT_COMPILER_MAKE_VERSION3(major, minor, patch) ((major)*10000 + (minor)*100 + (patch))
+
+
+
+#if defined(MPT_COMPILER_GENERIC)
+
+#undef MPT_COMPILER_GENERIC
+#define MPT_COMPILER_GENERIC 1
+
+#elif defined(__clang__) && defined(_MSC_VER) && defined(__c2__)
+
+#error "Clang/C2 is not supported. Please use Clang/LLVM for Windows instead."
+
+#elif defined(__clang__)
+
+#define MPT_COMPILER_CLANG 1
+#define MPT_COMPILER_CLANG_VERSION MPT_COMPILER_MAKE_VERSION3(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#define MPT_CLANG_AT_LEAST(major, minor, patch) (MPT_COMPILER_CLANG_VERSION >= MPT_COMPILER_MAKE_VERSION3((major), (minor), (patch)))
+#define MPT_CLANG_BEFORE(major, minor, patch) (MPT_COMPILER_CLANG_VERSION < MPT_COMPILER_MAKE_VERSION3((major), (minor), (patch)))
+
+#if MPT_CLANG_BEFORE(7, 0, 0)
+#error "clang version 7 required"
+#endif
+
+#if defined(__clang_analyzer__)
+#ifndef MPT_BUILD_ANALYZED
+#define MPT_BUILD_ANALYZED
+#endif
+#endif
+
+#elif defined(__GNUC__)
+
+#define MPT_COMPILER_GCC 1
+#define MPT_COMPILER_GCC_VERSION MPT_COMPILER_MAKE_VERSION3(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#define MPT_GCC_AT_LEAST(major, minor, patch) (MPT_COMPILER_GCC_VERSION >= MPT_COMPILER_MAKE_VERSION3((major), (minor), (patch)))
+#define MPT_GCC_BEFORE(major, minor, patch) (MPT_COMPILER_GCC_VERSION < MPT_COMPILER_MAKE_VERSION3((major), (minor), (patch)))
+
+#if MPT_GCC_BEFORE(8, 1, 0)
+#error "GCC version 8.1 required"
+#endif
+
+#elif defined(_MSC_VER)
+
+#define MPT_COMPILER_MSVC 1
+#if (_MSC_VER >= 1933)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 3)
+#elif (_MSC_VER >= 1932)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 2)
+#elif (_MSC_VER >= 1931)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 1)
+#elif (_MSC_VER >= 1930)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 0)
+#elif (_MSC_VER >= 1929)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 10)
+#elif (_MSC_VER >= 1928)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 8)
+#elif (_MSC_VER >= 1927)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 7)
+#elif (_MSC_VER >= 1926)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 6)
+#elif (_MSC_VER >= 1925)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 5)
+#elif (_MSC_VER >= 1924)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 4)
+#elif (_MSC_VER >= 1923)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 3)
+#elif (_MSC_VER >= 1922)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 2)
+#elif (_MSC_VER >= 1921)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 1)
+#elif (_MSC_VER >= 1920)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2019, 0)
+#elif (_MSC_VER >= 1916)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017, 9)
+#elif (_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)
+#elif (_MSC_VER >= 1912)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017, 5)
+#elif (_MSC_VER >= 1911)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017, 3)
+#elif (_MSC_VER >= 1910)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2017, 0)
+#elif (_MSC_VER >= 1900) && defined(_MSVC_LANG)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2015, 3)
+#elif (_MSC_VER >= 1900)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2015, 0)
+#elif (_MSC_VER >= 1800)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2013, 0)
+#elif (_MSC_VER >= 1700)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2012, 0)
+#elif (_MSC_VER >= 1600)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2010, 0)
+#elif (_MSC_VER >= 1500)
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2008, 0)
+#else
+#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2005, 0)
+#endif
+#define MPT_MSVC_AT_LEAST(version, sp) (MPT_COMPILER_MSVC_VERSION >= MPT_COMPILER_MAKE_VERSION2((version), (sp)))
+#define MPT_MSVC_BEFORE(version, sp) (MPT_COMPILER_MSVC_VERSION < MPT_COMPILER_MAKE_VERSION2((version), (sp)))
+
+#if MPT_MSVC_BEFORE(2017, 9)
+#error "MSVC version 2017 15.9 required"
+#endif
+
+#if defined(_PREFAST_)
+#ifndef MPT_BUILD_ANALYZED
+#define MPT_BUILD_ANALYZED
+#endif
+#endif
+
+#else
+
+#define MPT_COMPILER_GENERIC 1
+
+#endif
+
+
+
+#ifndef MPT_COMPILER_GENERIC
+#define MPT_COMPILER_GENERIC 0
+#endif
+#ifndef MPT_COMPILER_CLANG
+#define MPT_COMPILER_CLANG 0
+#define MPT_CLANG_AT_LEAST(major, minor, patch) 0
+#define MPT_CLANG_BEFORE(major, minor, patch) 0
+#endif
+#ifndef MPT_COMPILER_GCC
+#define MPT_COMPILER_GCC 0
+#define MPT_GCC_AT_LEAST(major, minor, patch) 0
+#define MPT_GCC_BEFORE(major, minor, patch) 0
+#endif
+#ifndef MPT_COMPILER_MSVC
+#define MPT_COMPILER_MSVC 0
+#define MPT_MSVC_AT_LEAST(version, sp) 0
+#define MPT_MSVC_BEFORE(version, sp) 0
+#endif
+
+
+
+#if MPT_COMPILER_GENERIC || MPT_COMPILER_GCC || MPT_COMPILER_CLANG
+
+#if (__cplusplus >= 202002)
+// Support for C++20 is lacking across all compilers.
+// Only assume C++17 for non-MSVC, even when in C++20 mode.
+#define MPT_CXX 17
+#elif (__cplusplus >= 201703)
+#define MPT_CXX 17
+#else
+#define MPT_CXX 17
+#endif
+
+#elif MPT_COMPILER_MSVC
+
+#if MPT_MSVC_AT_LEAST(2019, 10) && (_MSVC_LANG >= 201705)
+#define MPT_CXX 20
+#elif (_MSVC_LANG >= 201703)
+#define MPT_CXX 17
+#else
+#define MPT_CXX 17
+#endif
+
+#else
+
+#define MPT_CXX 17
+
+#endif
+
+// MPT_CXX is stricter than just using __cplusplus directly.
+// We will only claim a language version as supported IFF all core language and
+// library fatures that we need are actually supported AND working correctly
+// (to our needs).
+
+#define MPT_CXX_AT_LEAST(version) (MPT_CXX >= (version))
+#define MPT_CXX_BEFORE(version) (MPT_CXX < (version))
+
+
+
+#endif // MPT_BASE_DETECT_COMPILER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libc.hpp
new file mode 100644
index 00000000..4cc811e7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libc.hpp
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_LIBC_HPP
+#define MPT_BASE_DETECT_LIBC_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_os.hpp"
+
+#include <cstddef>
+
+
+
+// order of checks is important!
+#if MPT_COMPILER_GENERIC
+#define MPT_LIBC_GENERIC 1
+#elif MPT_COMPILER_GCC && (defined(__MINGW32__) || defined(__MINGW64__))
+#define MPT_LIBC_MS 1
+#elif defined(__GLIBC__)
+#define MPT_LIBC_GLIBC 1
+#elif MPT_COMPILER_MSVC
+#define MPT_LIBC_MS 1
+#elif MPT_COMPILER_CLANG && MPT_OS_WINDOWS
+#define MPT_LIBC_MS 1
+#else
+#define MPT_LIBC_GENERIC 1
+#endif
+
+#ifndef MPT_LIBC_GENERIC
+#define MPT_LIBC_GENERIC 0
+#endif
+#ifndef MPT_LIBC_GLIBC
+#define MPT_LIBC_GLIBC 0
+#endif
+#ifndef MPT_LIBC_MS
+#define MPT_LIBC_MS 0
+#endif
+
+
+
+#endif // MPT_BASE_DETECT_LIBC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libcxx.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libcxx.hpp
new file mode 100644
index 00000000..7f21564f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_libcxx.hpp
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_LIBCXX_HPP
+#define MPT_BASE_DETECT_LIBCXX_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_os.hpp"
+
+#if MPT_CXX_AT_LEAST(20)
+#include <version>
+#else // !C++20
+#include <array>
+#endif // C++20
+
+
+
+// order of checks is important!
+#if MPT_COMPILER_GENERIC
+#define MPT_LIBCXX_GENERIC 1
+#elif defined(_LIBCPP_VERSION)
+#define MPT_LIBCXX_LLVM 1
+#elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
+#define MPT_LIBCXX_GNU 1
+#elif MPT_COMPILER_MSVC
+#define MPT_LIBCXX_MS 1
+#elif MPT_COMPILER_CLANG && MPT_OS_WINDOWS
+#define MPT_LIBCXX_MS 1
+#else
+#define MPT_LIBCXX_GENERIC 1
+#endif
+
+#ifndef MPT_LIBCXX_GENERIC
+#define MPT_LIBCXX_GENERIC 0
+#endif
+#ifndef MPT_LIBCXX_LLVM
+#define MPT_LIBCXX_LLVM 0
+#endif
+#ifndef MPT_LIBCXX_GNU
+#define MPT_LIBCXX_GNU 0
+#endif
+#ifndef MPT_LIBCXX_MS
+#define MPT_LIBCXX_MS 0
+#endif
+
+
+
+#endif // MPT_BASE_DETECT_LIBCXX_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_os.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_os.hpp
new file mode 100644
index 00000000..507cce88
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_os.hpp
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_OS_HPP
+#define MPT_BASE_DETECT_OS_HPP
+
+
+
+// The order of the checks matters!
+#if defined(__DJGPP__)
+#define MPT_OS_DJGPP 1
+#elif defined(__EMSCRIPTEN__)
+#define MPT_OS_EMSCRIPTEN 1
+#if !defined(__EMSCRIPTEN_major__) || !defined(__EMSCRIPTEN_minor__) || !defined(__EMSCRIPTEN_tiny__)
+#include <emscripten/version.h>
+#endif
+#if defined(__EMSCRIPTEN_major__) && defined(__EMSCRIPTEN_minor__)
+#if (__EMSCRIPTEN_major__ > 1)
+// ok
+#elif (__EMSCRIPTEN_major__ == 1) && (__EMSCRIPTEN_minor__ > 39)
+// ok
+#elif (__EMSCRIPTEN_major__ == 1) && (__EMSCRIPTEN_minor__ == 39) && (__EMSCRIPTEN_tiny__ >= 7)
+// ok
+#else
+#error "Emscripten >= 1.39.7 is required."
+#endif
+#endif
+#elif defined(_WIN32)
+#define MPT_OS_WINDOWS 1
+#if defined(WINAPI_FAMILY)
+#include <winapifamily.h>
+#if (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+#define MPT_OS_WINDOWS_WINRT 0
+#else
+#define MPT_OS_WINDOWS_WINRT 1
+#endif
+#else // !WINAPI_FAMILY
+#define MPT_OS_WINDOWS_WINRT 0
+#endif // WINAPI_FAMILY
+#elif defined(__APPLE__)
+#define MPT_OS_MACOSX_OR_IOS 1
+#include <TargetConditionals.h>
+#if defined(TARGET_OS_OSX)
+#if (TARGET_OS_OSX != 0)
+#include <AvailabilityMacros.h>
+#endif
+#endif
+//#if TARGET_IPHONE_SIMULATOR
+//#elif TARGET_OS_IPHONE
+//#elif TARGET_OS_MAC
+//#else
+//#endif
+#elif defined(__HAIKU__)
+#define MPT_OS_HAIKU 1
+#elif defined(__ANDROID__) || defined(ANDROID)
+#define MPT_OS_ANDROID 1
+#elif defined(__linux__)
+#define MPT_OS_LINUX 1
+#elif defined(__DragonFly__)
+#define MPT_OS_DRAGONFLYBSD 1
+#elif defined(__FreeBSD__)
+#define MPT_OS_FREEBSD 1
+#elif defined(__OpenBSD__)
+#define MPT_OS_OPENBSD 1
+#elif defined(__NetBSD__)
+#define MPT_OS_NETBSD 1
+#elif defined(__unix__)
+#define MPT_OS_GENERIC_UNIX 1
+#else
+#define MPT_OS_UNKNOWN 1
+#endif
+
+#ifndef MPT_OS_DJGPP
+#define MPT_OS_DJGPP 0
+#endif
+#ifndef MPT_OS_EMSCRIPTEN
+#define MPT_OS_EMSCRIPTEN 0
+#endif
+#ifndef MPT_OS_WINDOWS
+#define MPT_OS_WINDOWS 0
+#endif
+#ifndef MPT_OS_WINDOWS_WINRT
+#define MPT_OS_WINDOWS_WINRT 0
+#endif
+#ifndef MPT_OS_MACOSX_OR_IOS
+#define MPT_OS_MACOSX_OR_IOS 0
+#endif
+#ifndef MPT_OS_HAIKU
+#define MPT_OS_HAIKU 0
+#endif
+#ifndef MPT_OS_ANDROID
+#define MPT_OS_ANDROID 0
+#endif
+#ifndef MPT_OS_LINUX
+#define MPT_OS_LINUX 0
+#endif
+#ifndef MPT_OS_DRAGONFLYBSD
+#define MPT_OS_DRAGONFLYBSD 0
+#endif
+#ifndef MPT_OS_FREEBSD
+#define MPT_OS_FREEBSD 0
+#endif
+#ifndef MPT_OS_OPENBSD
+#define MPT_OS_OPENBSD 0
+#endif
+#ifndef MPT_OS_NETBSD
+#define MPT_OS_NETBSD 0
+#endif
+#ifndef MPT_OS_GENERIC_UNIX
+#define MPT_OS_GENERIC_UNIX 0
+#endif
+#ifndef MPT_OS_UNKNOWN
+#define MPT_OS_UNKNOWN 0
+#endif
+
+
+
+#endif // MPT_BASE_DETECT_OS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_quirks.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_quirks.hpp
new file mode 100644
index 00000000..ab7b076a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/detect_quirks.hpp
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_DETECT_QUIRKS_HPP
+#define MPT_BASE_DETECT_QUIRKS_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_libcxx.hpp"
+#include "mpt/base/detect_os.hpp"
+
+
+
+#if MPT_COMPILER_MSVC
+// Compiler has multiplication/division semantics when shifting signed integers.
+#define MPT_COMPILER_SHIFT_SIGNED 1
+#endif
+
+#ifndef MPT_COMPILER_SHIFT_SIGNED
+#define MPT_COMPILER_SHIFT_SIGNED 0
+#endif
+
+
+
+// This should really be based on __STDCPP_THREADS__, but that is not defined by
+// GCC or clang. Stupid.
+// Just assume multithreaded and disable for platforms we know are
+// singlethreaded later on.
+#define MPT_PLATFORM_MULTITHREADED 1
+
+#if MPT_OS_DJGPP
+#undef MPT_PLATFORM_MULTITHREADED
+#define MPT_PLATFORM_MULTITHREADED 0
+#endif
+
+#if (MPT_OS_EMSCRIPTEN && !defined(__EMSCRIPTEN_PTHREADS__))
+#undef MPT_PLATFORM_MULTITHREADED
+#define MPT_PLATFORM_MULTITHREADED 0
+#endif
+
+
+
+#if MPT_OS_WINDOWS && MPT_COMPILER_MSVC
+#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600) // _WIN32_WINNT_VISTA
+#define MPT_COMPILER_QUIRK_COMPLEX_STD_MUTEX
+#endif
+#endif
+
+
+
+#if MPT_OS_EMSCRIPTEN && defined(MPT_BUILD_AUDIOWORKLETPROCESSOR)
+#define MPT_COMPILER_QUIRK_CHRONO_NO_HIGH_RESOLUTION_CLOCK
+#endif
+
+
+
+#if MPT_OS_EMSCRIPTEN && defined(MPT_BUILD_AUDIOWORKLETPROCESSOR)
+#define MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+#endif
+
+
+
+#if MPT_OS_DJGPP
+#define MPT_COMPILER_QUIRK_NO_WCHAR
+#endif
+
+
+
+#if MPT_OS_WINDOWS && MPT_GCC_BEFORE(9, 1, 0)
+// GCC C++ library has no wchar_t overloads
+#define MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR
+#endif
+
+
+
+#if defined(__arm__)
+
+#if defined(__SOFTFP__)
+#define MPT_COMPILER_QUIRK_FLOAT_EMULATED 1
+#else
+#define MPT_COMPILER_QUIRK_FLOAT_EMULATED 0
+#endif
+#if defined(__VFP_FP__)
+// native-endian IEEE754
+#define MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN 0
+#define MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754 0
+#elif defined(__MAVERICK__)
+// little-endian IEEE754, we assume native-endian though
+#define MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN 1
+#define MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754 0
+#else
+// not IEEE754
+#define MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN 1
+#define MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754 1
+#endif
+
+#elif defined(__mips__)
+
+#if defined(__mips_soft_float)
+#define MPT_COMPILER_QUIRK_FLOAT_EMULATED 1
+#else
+#define MPT_COMPILER_QUIRK_FLOAT_EMULATED 0
+#endif
+
+#endif
+
+#if MPT_OS_EMSCRIPTEN
+#define MPT_COMPILER_QUIRK_FLOAT_PREFER64 1
+#endif
+
+#ifndef MPT_COMPILER_QUIRK_FLOAT_PREFER32
+#define MPT_COMPILER_QUIRK_FLOAT_PREFER32 0
+#endif
+#ifndef MPT_COMPILER_QUIRK_FLOAT_PREFER64
+#define MPT_COMPILER_QUIRK_FLOAT_PREFER64 0
+#endif
+#ifndef MPT_COMPILER_QUIRK_FLOAT_EMULATED
+#define MPT_COMPILER_QUIRK_FLOAT_EMULATED 0
+#endif
+#ifndef MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN
+#define MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN 0
+#endif
+#ifndef MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754
+#define MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754 0
+#endif
+
+
+
+#if MPT_OS_MACOSX_OR_IOS
+#if defined(TARGET_OS_OSX)
+#if TARGET_OS_OSX
+#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_15)
+#define MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT
+#endif
+#endif
+#endif
+#endif
+
+
+
+#if (MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2019, 4) || !MPT_COMPILER_MSVC)) || (MPT_LIBCXX_GNU && (MPT_GCC_BEFORE(11, 0, 0) || !MPT_COMPILER_GCC)) || MPT_LIBCXX_LLVM || MPT_LIBCXX_GENERIC
+#define MPT_LIBCXX_QUIRK_NO_TO_CHARS_FLOAT
+#endif
+
+
+
+#endif // MPT_BASE_DETECT_QUIRKS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/floatingpoint.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/floatingpoint.hpp
new file mode 100644
index 00000000..efce0ab2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/floatingpoint.hpp
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_FLOATINGPOINT_HPP
+#define MPT_BASE_FLOATINGPOINT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <limits>
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// fp half
+// n/a
+
+// fp single
+using single = float;
+namespace float_literals {
+constexpr single operator"" _fs(long double lit) noexcept {
+ return static_cast<single>(lit);
+}
+} // namespace float_literals
+
+// fp double
+namespace float_literals {
+constexpr double operator"" _fd(long double lit) noexcept {
+ return static_cast<double>(lit);
+}
+} // namespace float_literals
+
+// fp extended
+namespace float_literals {
+constexpr long double operator"" _fe(long double lit) noexcept {
+ return static_cast<long double>(lit);
+}
+} // namespace float_literals
+
+// fp quad
+// n/a
+
+using float32 = std::conditional<sizeof(float) == 4, float, std::conditional<sizeof(double) == 4, double, std::conditional<sizeof(long double) == 4, long double, float>::type>::type>::type;
+namespace float_literals {
+constexpr float32 operator"" _f32(long double lit) noexcept {
+ return static_cast<float32>(lit);
+}
+} // namespace float_literals
+
+using float64 = std::conditional<sizeof(float) == 8, float, std::conditional<sizeof(double) == 8, double, std::conditional<sizeof(long double) == 8, long double, double>::type>::type>::type;
+namespace float_literals {
+constexpr float64 operator"" _f64(long double lit) noexcept {
+ return static_cast<float64>(lit);
+}
+} // namespace float_literals
+
+template <typename T>
+struct float_traits {
+ static constexpr bool is_float = !std::numeric_limits<T>::is_integer;
+ static constexpr bool is_hard = is_float && !MPT_COMPILER_QUIRK_FLOAT_EMULATED;
+ static constexpr bool is_soft = is_float && MPT_COMPILER_QUIRK_FLOAT_EMULATED;
+ static constexpr bool is_float32 = is_float && (sizeof(T) == 4);
+ static constexpr bool is_float64 = is_float && (sizeof(T) == 8);
+ static constexpr bool is_native_endian = is_float && !MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN;
+ static constexpr bool is_ieee754_binary = is_float && std::numeric_limits<T>::is_iec559 && !MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754;
+ static constexpr bool is_ieee754_binary32 = is_float && is_ieee754_binary && is_float32;
+ static constexpr bool is_ieee754_binary64 = is_float && is_ieee754_binary && is_float64;
+ static constexpr bool is_ieee754_binary32ne = is_float && is_ieee754_binary && is_float32 && is_native_endian;
+ static constexpr bool is_ieee754_binary64ne = is_float && is_ieee754_binary && is_float64 && is_native_endian;
+ static constexpr bool is_preferred = is_float && ((is_float32 && MPT_COMPILER_QUIRK_FLOAT_PREFER32) || (is_float64 && MPT_COMPILER_QUIRK_FLOAT_PREFER64));
+};
+
+// prefer smaller floats, but try to use IEEE754 floats
+using nativefloat =
+ std::conditional<mpt::float_traits<float32>::is_preferred, float32, std::conditional<mpt::float_traits<float64>::is_preferred, float64, std::conditional<std::numeric_limits<float>::is_iec559, float, std::conditional<std::numeric_limits<double>::is_iec559, double, std::conditional<std::numeric_limits<long double>::is_iec559, long double, float>::type>::type>::type>::type>::type;
+namespace float_literals {
+constexpr nativefloat operator"" _nf(long double lit) noexcept {
+ return static_cast<nativefloat>(lit);
+}
+} // namespace float_literals
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_FLOATINGPOINT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/integer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/integer.hpp
new file mode 100644
index 00000000..0e681fe5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/integer.hpp
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_INTEGER_HPP
+#define MPT_BASE_INTEGER_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include <cstdint>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+using int8 = std::int8_t;
+using int16 = std::int16_t;
+using int32 = std::int32_t;
+using int64 = std::int64_t;
+using uint8 = std::uint8_t;
+using uint16 = std::uint16_t;
+using uint32 = std::uint32_t;
+using uint64 = std::uint64_t;
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_INTEGER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/macros.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/macros.hpp
new file mode 100644
index 00000000..c54ebf92
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/macros.hpp
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_MACROS_HPP
+#define MPT_BASE_MACROS_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+
+#include <type_traits>
+
+#if MPT_COMPILER_MSVC && MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_COMPILER_MSVC && MPT_OS_WINDOWS
+
+
+
+// Advanced inline attributes
+#if MPT_COMPILER_MSVC
+#define MPT_FORCEINLINE __forceinline
+#define MPT_NOINLINE __declspec(noinline)
+#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG
+#define MPT_FORCEINLINE __attribute__((always_inline)) inline
+#define MPT_NOINLINE __attribute__((noinline))
+#else
+#define MPT_FORCEINLINE inline
+#define MPT_NOINLINE
+#endif
+
+
+
+// constexpr
+#define MPT_CONSTEXPRINLINE constexpr MPT_FORCEINLINE
+#if MPT_CXX_AT_LEAST(20)
+#define MPT_CONSTEXPR20_FUN constexpr MPT_FORCEINLINE
+#define MPT_CONSTEXPR20_VAR constexpr
+#else // !C++20
+#define MPT_CONSTEXPR20_FUN MPT_FORCEINLINE
+#define MPT_CONSTEXPR20_VAR const
+#endif // C++20
+
+
+
+#define MPT_FORCE_CONSTEXPR(expr) [&]() { \
+ constexpr auto x = (expr); \
+ return x; \
+}()
+
+
+
+#if MPT_CXX_AT_LEAST(20)
+#define MPT_IS_CONSTANT_EVALUATED20() std::is_constant_evaluated()
+#define MPT_IS_CONSTANT_EVALUATED() std::is_constant_evaluated()
+#else // !C++20
+#define MPT_IS_CONSTANT_EVALUATED20() false
+// this pessimizes the case for C++17 by always assuming constexpr context, which implies always running constexpr-friendly code
+#define MPT_IS_CONSTANT_EVALUATED() true
+#endif // C++20
+
+
+
+#if MPT_COMPILER_MSVC
+#define MPT_MAYBE_CONSTANT_IF(x) \
+ __pragma(warning(push)) \
+ __pragma(warning(disable : 4127)) \
+ if (x) \
+ __pragma(warning(pop)) \
+/**/
+#endif
+
+#if MPT_COMPILER_GCC
+#define MPT_MAYBE_CONSTANT_IF(x) \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
+ if (x) \
+ _Pragma("GCC diagnostic pop") \
+/**/
+#endif
+
+#if MPT_COMPILER_CLANG
+#define MPT_MAYBE_CONSTANT_IF(x) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") \
+ _Pragma("clang diagnostic ignored \"-Wtype-limits\"") \
+ _Pragma("clang diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"") \
+ if (x) \
+ _Pragma("clang diagnostic pop") \
+/**/
+#endif
+
+#if !defined(MPT_MAYBE_CONSTANT_IF)
+// MPT_MAYBE_CONSTANT_IF disables compiler warnings for conditions that may in some case be either always false or always true (this may turn out to be useful in ASSERTions in some cases).
+#define MPT_MAYBE_CONSTANT_IF(x) if (x)
+#endif
+
+
+
+#if MPT_COMPILER_MSVC && MPT_OS_WINDOWS
+#define MPT_UNUSED(x) UNREFERENCED_PARAMETER(x)
+#else
+#define MPT_UNUSED(x) static_cast<void>(x)
+#endif
+
+
+
+#define MPT_DISCARD(expr) static_cast<void>(expr)
+
+
+
+// Use MPT_RESTRICT to indicate that a pointer is guaranteed to not be aliased.
+#if MPT_COMPILER_MSVC || MPT_COMPILER_GCC || MPT_COMPILER_CLANG
+#define MPT_RESTRICT __restrict
+#else
+#define MPT_RESTRICT
+#endif
+
+
+
+#endif // MPT_BASE_MACROS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/math.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/math.hpp
new file mode 100644
index 00000000..23e50d73
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/math.hpp
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_EMPTY_HPP
+#define MPT_BASE_EMPTY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <algorithm>
+#include <type_traits>
+
+#include <cmath>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_OS_DJGPP
+
+inline long double log2(const long double val) {
+ return static_cast<long double>(::log2(static_cast<double>(val)));
+}
+
+inline double log2(const double val) {
+ return ::log2(val);
+}
+
+inline float log2(const float val) {
+ return ::log2f(val);
+}
+
+#else // !MPT_OS_DJGPP
+
+// C++11 std::log2
+using std::log2;
+
+#endif // MPT_OS_DJGPP
+
+
+#if MPT_OS_DJGPP
+
+inline long double round(const long double val) {
+ return ::roundl(val);
+}
+
+inline double round(const double val) {
+ return ::round(val);
+}
+
+inline float round(const float val) {
+ return ::roundf(val);
+}
+
+#else // !MPT_OS_DJGPP
+
+// C++11 std::round
+using std::round;
+
+#endif // MPT_OS_DJGPP
+
+
+template <typename T>
+inline T sanitize_nan(T val) {
+ static_assert(std::is_floating_point<T>::value);
+ if (std::isnan(val)) {
+ return T(0.0);
+ }
+ return val;
+}
+
+
+template <typename T>
+inline T safe_clamp(T v, T lo, T hi) {
+ static_assert(std::is_floating_point<T>::value);
+ return std::clamp(mpt::sanitize_nan(v), lo, hi);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_EMPTY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/memory.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/memory.hpp
new file mode 100644
index 00000000..1ee93378
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/memory.hpp
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_MEMORY_HPP
+#define MPT_BASE_MEMORY_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/span.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+using byte_span = mpt::span<std::byte>;
+using const_byte_span = mpt::span<const std::byte>;
+
+
+
+// Tell which types are safe for mpt::byte_cast.
+// signed char is actually not allowed to alias into an object representation,
+// which means that, if the actual type is not itself signed char but char or
+// unsigned char instead, dereferencing the signed char pointer is undefined
+// behaviour.
+template <typename T>
+struct is_byte_castable : public std::false_type { };
+template <>
+struct is_byte_castable<char> : public std::true_type { };
+template <>
+struct is_byte_castable<unsigned char> : public std::true_type { };
+template <>
+struct is_byte_castable<std::byte> : public std::true_type { };
+template <>
+struct is_byte_castable<const char> : public std::true_type { };
+template <>
+struct is_byte_castable<const unsigned char> : public std::true_type { };
+template <>
+struct is_byte_castable<const std::byte> : public std::true_type { };
+
+
+template <typename T>
+struct is_byte : public std::false_type { };
+template <>
+struct is_byte<std::byte> : public std::true_type { };
+template <>
+struct is_byte<const std::byte> : public std::true_type { };
+
+
+template <typename T>
+constexpr bool declare_binary_safe(const T &) noexcept {
+ return false;
+}
+
+constexpr bool declare_binary_safe(const char &) noexcept {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint8 &) noexcept {
+ return true;
+}
+constexpr bool declare_binary_safe(const int8 &) noexcept {
+ return true;
+}
+constexpr bool declare_binary_safe(const std::byte &) noexcept {
+ return true;
+}
+
+// Tell which types are safe to binary write into files.
+// By default, no types are safe.
+// When a safe type gets defined,
+// also specialize this template so that IO functions will work.
+template <typename T>
+struct is_binary_safe : public std::conditional<declare_binary_safe(T{}), std::true_type, std::false_type>::type { };
+
+// Generic Specialization for arrays.
+template <typename T, std::size_t N>
+struct is_binary_safe<T[N]> : public is_binary_safe<T> { };
+template <typename T, std::size_t N>
+struct is_binary_safe<const T[N]> : public is_binary_safe<T> { };
+template <typename T, std::size_t N>
+struct is_binary_safe<std::array<T, N>> : public is_binary_safe<T> { };
+template <typename T, std::size_t N>
+struct is_binary_safe<const std::array<T, N>> : public is_binary_safe<T> { };
+
+
+template <typename T>
+constexpr bool check_binary_size(std::size_t size) noexcept {
+ return true
+ && (sizeof(T) == size)
+ && (alignof(T) == 1)
+ && std::is_standard_layout<T>::value
+ && std::has_unique_object_representations<T>::value
+ && mpt::is_binary_safe<T>::value;
+}
+
+
+template <typename Tdst, typename Tsrc>
+struct byte_cast_impl {
+ inline Tdst operator()(Tsrc src) const noexcept {
+ static_assert(sizeof(Tsrc) == sizeof(std::byte));
+ static_assert(sizeof(Tdst) == sizeof(std::byte));
+ // not checking is_byte_castable here because we are actually
+ // doing a static_cast and converting the value
+ static_assert(std::is_integral<Tsrc>::value || mpt::is_byte<Tsrc>::value);
+ static_assert(std::is_integral<Tdst>::value || mpt::is_byte<Tdst>::value);
+ return static_cast<Tdst>(src);
+ }
+};
+
+template <typename Tdst, typename Tsrc>
+struct byte_cast_impl<mpt::span<Tdst>, mpt::span<Tsrc>> {
+ inline mpt::span<Tdst> operator()(mpt::span<Tsrc> src) const noexcept {
+ static_assert(sizeof(Tsrc) == sizeof(std::byte));
+ static_assert(sizeof(Tdst) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tsrc>::value);
+ static_assert(mpt::is_byte_castable<Tdst>::value);
+ static_assert(std::is_integral<Tsrc>::value || mpt::is_byte<Tsrc>::value);
+ static_assert(std::is_integral<Tdst>::value || mpt::is_byte<Tdst>::value);
+ return mpt::as_span(mpt::byte_cast_impl<Tdst *, Tsrc *>()(src.data()), mpt::byte_cast_impl<Tdst *, Tsrc *>()(src.data() + src.size()));
+ }
+};
+
+template <typename Tdst, typename Tsrc>
+struct byte_cast_impl<Tdst *, Tsrc *> {
+ inline Tdst * operator()(Tsrc * src) const noexcept {
+ static_assert(sizeof(Tsrc) == sizeof(std::byte));
+ static_assert(sizeof(Tdst) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tsrc>::value);
+ static_assert(mpt::is_byte_castable<Tdst>::value);
+ static_assert(std::is_integral<Tsrc>::value || mpt::is_byte<Tsrc>::value);
+ static_assert(std::is_integral<Tdst>::value || mpt::is_byte<Tdst>::value);
+ return reinterpret_cast<Tdst *>(src);
+ }
+};
+
+template <typename Tdst, typename Tsrc>
+struct void_cast_impl;
+
+template <typename Tdst>
+struct void_cast_impl<Tdst *, void *> {
+ inline Tdst * operator()(void * src) const noexcept {
+ static_assert(sizeof(Tdst) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tdst>::value);
+ static_assert(std::is_integral<Tdst>::value || mpt::is_byte<Tdst>::value);
+ return reinterpret_cast<Tdst *>(src);
+ }
+};
+
+template <typename Tdst>
+struct void_cast_impl<Tdst *, const void *> {
+ inline Tdst * operator()(const void * src) const noexcept {
+ static_assert(sizeof(Tdst) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tdst>::value);
+ static_assert(std::is_integral<Tdst>::value || mpt::is_byte<Tdst>::value);
+ return reinterpret_cast<Tdst *>(src);
+ }
+};
+
+template <typename Tsrc>
+struct void_cast_impl<void *, Tsrc *> {
+ inline void * operator()(Tsrc * src) const noexcept {
+ static_assert(sizeof(Tsrc) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tsrc>::value);
+ static_assert(std::is_integral<Tsrc>::value || mpt::is_byte<Tsrc>::value);
+ return reinterpret_cast<void *>(src);
+ }
+};
+
+template <typename Tsrc>
+struct void_cast_impl<const void *, Tsrc *> {
+ inline const void * operator()(Tsrc * src) const noexcept {
+ static_assert(sizeof(Tsrc) == sizeof(std::byte));
+ static_assert(mpt::is_byte_castable<Tsrc>::value);
+ static_assert(std::is_integral<Tsrc>::value || mpt::is_byte<Tsrc>::value);
+ return reinterpret_cast<const void *>(src);
+ }
+};
+
+// casts between different byte (char) types or pointers to these types
+template <typename Tdst, typename Tsrc>
+inline Tdst byte_cast(Tsrc src) noexcept {
+ return byte_cast_impl<Tdst, Tsrc>()(src);
+}
+
+// casts between pointers to void and pointers to byte
+template <typename Tdst, typename Tsrc>
+inline Tdst void_cast(Tsrc src) noexcept {
+ return void_cast_impl<Tdst, Tsrc>()(src);
+}
+
+
+
+template <typename T>
+MPT_CONSTEXPRINLINE std::byte as_byte(T src) noexcept {
+ static_assert(std::is_integral<T>::value);
+ return static_cast<std::byte>(static_cast<uint8>(src));
+}
+
+
+
+template <typename T>
+struct as_raw_memory_impl {
+ inline mpt::const_byte_span operator()(const T & v) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<const std::byte *>(&v), sizeof(T));
+ }
+ inline mpt::byte_span operator()(T & v) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<std::byte *>(&v), sizeof(T));
+ }
+};
+
+template <typename T, std::size_t N>
+struct as_raw_memory_impl<T[N]> {
+ inline mpt::const_byte_span operator()(const T (&v)[N]) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<const std::byte *>(v), N * sizeof(T));
+ }
+ inline mpt::byte_span operator()(T (&v)[N]) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<std::byte *>(v), N * sizeof(T));
+ }
+};
+
+template <typename T, std::size_t N>
+struct as_raw_memory_impl<const T[N]> {
+ inline mpt::const_byte_span operator()(const T (&v)[N]) const {
+ static_assert(mpt::is_binary_safe<typename std::remove_const<T>::type>::value);
+ return mpt::as_span(reinterpret_cast<const std::byte *>(v), N * sizeof(T));
+ }
+};
+
+// In order to be able to partially specialize it,
+// as_raw_memory is implemented via a class template.
+// Do not overload or specialize as_raw_memory directly.
+// Using a wrapper (by default just around a cast to const std::byte *),
+// allows for implementing raw memory access
+// via on-demand generating a cached serialized representation.
+template <typename T>
+inline mpt::const_byte_span as_raw_memory(const T & v) {
+ return mpt::as_raw_memory_impl<T>()(v);
+}
+template <typename T>
+inline mpt::byte_span as_raw_memory(T & v) {
+ return mpt::as_raw_memory_impl<T>()(v);
+}
+
+
+
+template <class T>
+inline void memclear(T & x) {
+ static_assert(std::is_standard_layout<T>::value);
+ static_assert((std::is_trivially_default_constructible<T>::value && std::is_trivially_copyable<T>::value) || mpt::is_binary_safe<T>::value);
+ std::memset(&x, 0, sizeof(T));
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_MEMORY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/namespace.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/namespace.hpp
new file mode 100644
index 00000000..36b7e5cc
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/namespace.hpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_NAMESPACE_HPP
+#define MPT_BASE_NAMESPACE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/version.hpp"
+#include "mpt/base/compiletime_warning.hpp"
+
+
+
+#if !defined(MPT_INLINE_NS)
+
+#define MPT_BUILD_VERSION_NAMESPACE_IMPL(a, b, c, d) v##a##_##b##_##c##_##d
+#define MPT_BUILD_VERSION_NAMESPACE(a, b, c, d) MPT_BUILD_VERSION_NAMESPACE_IMPL(a, b, c, d)
+
+#define MPT_VERSION_NAMESPACE MPT_BUILD_VERSION_NAMESPACE(MPT_VERSION_MAJOR, MPT_VERSION_MINOR, MPT_VERSION_PATCH, MPT_VERSION_BUILD)
+
+#if MPT_OS_WINDOWS
+#ifdef UNICODE
+#define MPT_VERSION_ABI_OS u
+#else
+#define MPT_VERSION_ABI_OS 8
+#endif
+#else
+#define MPT_VERSION_ABI_OS _
+#endif
+
+#if MPT_LIBC_GENERIC
+#define MPT_VERSION_ABI_LIBC _
+#elif MPT_LIBC_MS
+#ifdef _DLL
+#ifdef _DEBUG
+#define MPT_VERSION_ABI_LIBC MDd
+#else
+#define MPT_VERSION_ABI_LIBC MDr
+#endif
+#else
+#ifdef _DEBUG
+#define MPT_VERSION_ABI_LIBC MTd
+#else
+#define MPT_VERSION_ABI_LIBC MTr
+#endif
+#endif
+#elif MPT_LIBC_GLIBC
+#define MPT_VERSION_ABI_LIBC G
+#else
+#define MPT_VERSION_ABI_LIBC _
+#endif
+
+#define MPT_BUILD_ABI_NAMESPACE_IMPL(a, b) ABI_##a##_##b
+#define MPT_BUILD_ABI_NAMESPACE(a, b) MPT_BUILD_ABI_NAMESPACE_IMPL(a, b)
+
+#define MPT_ABI_NAMESPACE MPT_BUILD_ABI_NAMESPACE(MPT_VERSION_ABI_OS, MPT_VERSION_ABI_LIBC)
+
+#if !defined(MPT_PROJECT_NAMESPACE)
+MPT_WARNING("Please #define MPT_PROJECT_NAMESPACE or #define MPT_INLINE_NS in build configuration.")
+#define MPT_PROJECT_NAMESPACE x
+#endif // !MPT_PROJECT_NAMESPACE
+
+#define MPT_BUILD_INLINE_NS_IMPL(a, b, c) a##_##b##_##c
+#define MPT_BUILD_INLINE_NS(a, b, c) MPT_BUILD_INLINE_NS_IMPL(a, b, c)
+
+#define MPT_INLINE_NS MPT_BUILD_INLINE_NS(MPT_VERSION_NAMESPACE, MPT_ABI_NAMESPACE, MPT_PROJECT_NAMESPACE)
+
+#endif // !MPT_INLINE_NS
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_NAMESPACE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/numbers.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/numbers.hpp
new file mode 100644
index 00000000..a69f10ee
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/numbers.hpp
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_NUMBRES_HPP
+#define MPT_BASE_NUMBRES_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+
+#if MPT_CXX_AT_LEAST(20)
+#include <numbers>
+#else
+#include <type_traits>
+#include <cmath>
+#include <math.h>
+#endif
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace numbers {
+
+#if MPT_CXX_AT_LEAST(20)
+
+template <typename T>
+inline constexpr T e_v = std::numbers::e_v<T>;
+template <typename T>
+inline constexpr T log2e_v = std::numbers::log2e_v<T>;
+template <typename T>
+inline constexpr T log10e_v = std::numbers::log10e_v<T>;
+template <typename T>
+inline constexpr T pi_v = std::numbers::pi_v<T>;
+template <typename T>
+inline constexpr T inv_pi_v = std::numbers::inv_pi_v<T>;
+template <typename T>
+inline constexpr T inv_sqrtpi_v = std::numbers::inv_sqrtpi_v<T>;
+template <typename T>
+inline constexpr T ln2_v = std::numbers::ln2_v<T>;
+template <typename T>
+inline constexpr T ln10_v = std::numbers::ln10_v<T>;
+template <typename T>
+inline constexpr T sqrt2_v = std::numbers::sqrt2_v<T>;
+template <typename T>
+inline constexpr T sqrt3_v = std::numbers::sqrt3_v<T>;
+template <typename T>
+inline constexpr T inv_sqrt3_v = std::numbers::inv_sqrt3_v<T>;
+template <typename T>
+inline constexpr T egamma_v = std::numbers::egamma_v<T>;
+template <typename T>
+inline constexpr T phi_v = std::numbers::phi_v<T>;
+
+inline constexpr double e = e_v<double>;
+inline constexpr double log2e = log2e_v<double>;
+inline constexpr double log10e = log10e_v<double>;
+inline constexpr double pi = pi_v<double>;
+inline constexpr double inv_pi = inv_pi_v<double>;
+inline constexpr double inv_sqrtpi = inv_sqrtpi_v<double>;
+inline constexpr double ln2 = ln2_v<double>;
+inline constexpr double ln10 = ln10_v<double>;
+inline constexpr double sqrt2 = sqrt2_v<double>;
+inline constexpr double sqrt3 = sqrt3_v<double>;
+inline constexpr double inv_sqrt3 = inv_sqrt3_v<double>;
+inline constexpr double egamma = egamma_v<double>;
+inline constexpr double phi = phi_v<double>;
+
+#else
+
+#ifdef M_E
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T e_v = static_cast<T>(M_E);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T e_v = static_cast<T>(2.71828182845904523536);
+#endif
+
+#ifdef M_LOG2E
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T log2e_v = static_cast<T>(M_LOG2E);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T log2e_v = static_cast<T>(1.44269504088896340736);
+#endif
+
+#ifdef M_LOG10E
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T log10e_v = static_cast<T>(M_LOG10E);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T log10e_v = static_cast<T>(0.434294481903251827651);
+#endif
+
+#ifdef M_PI
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T pi_v = static_cast<T>(M_PI);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T pi_v = static_cast<T>(3.14159265358979323846);
+#endif
+
+#ifdef M_1_PI
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T inv_pi_v = static_cast<T>(M_1_PI);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T inv_pi_v = static_cast<T>(0.318309886183790671538);
+#endif
+
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T inv_sqrtpi_v = static_cast<T>(0.564189583547756286948079451560772586);
+
+#ifdef M_LN2
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T ln2_v = static_cast<T>(M_LN2);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T ln2_v = static_cast<T>(0.693147180559945309417);
+#endif
+
+#ifdef M_LN10
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T ln10_v = static_cast<T>(M_LN10);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T ln10_v = static_cast<T>(2.30258509299404568402);
+#endif
+
+#ifdef M_SQRT2
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T sqrt2_v = static_cast<T>(M_SQRT2);
+#else
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T sqrt2_v = static_cast<T>(1.41421356237309504880);
+#endif
+
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T sqrt3_v = static_cast<T>(1.732050807568877293527446341505872367);
+
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T inv_sqrt3_v = static_cast<T>(0.577350269189625764509148780501957456);
+
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T egamma_v = static_cast<T>(0.577215664901532860606512090082402431);
+
+template <typename T, typename std::enable_if<std::is_floating_point<T>::value, bool>::type = true>
+inline constexpr T phi_v = static_cast<T>(1.618033988749894848204586834365638118);
+
+inline constexpr double e = e_v<double>;
+inline constexpr double log2e = log2e_v<double>;
+inline constexpr double log10e = log10e_v<double>;
+inline constexpr double pi = pi_v<double>;
+inline constexpr double inv_pi = inv_pi_v<double>;
+inline constexpr double inv_sqrtpi = inv_sqrtpi_v<double>;
+inline constexpr double ln2 = ln2_v<double>;
+inline constexpr double ln10 = ln10_v<double>;
+inline constexpr double sqrt2 = sqrt2_v<double>;
+inline constexpr double sqrt3 = sqrt3_v<double>;
+inline constexpr double inv_sqrt3 = inv_sqrt3_v<double>;
+inline constexpr double egamma = egamma_v<double>;
+inline constexpr double phi = phi_v<double>;
+
+#endif
+
+} // namespace numbers
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_NUMBRES_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/numeric.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/numeric.hpp
new file mode 100644
index 00000000..014f06fa
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/numeric.hpp
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_NUMERIC_HPP
+#define MPT_BASE_NUMERIC_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/saturate_cast.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename Tmod, Tmod m>
+struct ModIfNotZeroImpl {
+ template <typename Tval>
+ constexpr Tval mod(Tval x) {
+ static_assert(std::numeric_limits<Tmod>::is_integer);
+ static_assert(!std::numeric_limits<Tmod>::is_signed);
+ static_assert(std::numeric_limits<Tval>::is_integer);
+ static_assert(!std::numeric_limits<Tval>::is_signed);
+ return static_cast<Tval>(x % m);
+ }
+};
+template <>
+struct ModIfNotZeroImpl<uint8, 0> {
+ template <typename Tval>
+ constexpr Tval mod(Tval x) {
+ return x;
+ }
+};
+template <>
+struct ModIfNotZeroImpl<uint16, 0> {
+ template <typename Tval>
+ constexpr Tval mod(Tval x) {
+ return x;
+ }
+};
+template <>
+struct ModIfNotZeroImpl<uint32, 0> {
+ template <typename Tval>
+ constexpr Tval mod(Tval x) {
+ return x;
+ }
+};
+template <>
+struct ModIfNotZeroImpl<uint64, 0> {
+ template <typename Tval>
+ constexpr Tval mod(Tval x) {
+ return x;
+ }
+};
+
+// Returns x % m if m != 0, x otherwise.
+// i.e. "return (m == 0) ? x : (x % m);", but without causing a warning with stupid older compilers
+template <typename Tmod, Tmod m, typename Tval>
+constexpr Tval modulo_if_not_zero(Tval x) {
+ return ModIfNotZeroImpl<Tmod, m>().mod(x);
+}
+
+// rounds x up to multiples of target
+template <typename T>
+constexpr T align_up(T x, T target) {
+ return ((x + (target - 1)) / target) * target;
+}
+
+// rounds x down to multiples of target
+template <typename T>
+constexpr T align_down(T x, T target) {
+ return (x / target) * target;
+}
+
+// Returns sign of a number (-1 for negative numbers, 1 for positive numbers, 0 for 0)
+template <class T>
+constexpr int signum(T value) {
+ return (value > T(0)) - (value < T(0));
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_ALGORITHM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/pointer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/pointer.hpp
new file mode 100644
index 00000000..b6fd3f28
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/pointer.hpp
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_POINTER_HPP
+#define MPT_BASE_POINTER_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+inline constexpr int arch_bits = sizeof(void *) * 8;
+inline constexpr std::size_t pointer_size = sizeof(void *);
+
+
+template <typename Tdst, typename Tsrc>
+struct pointer_cast_helper {
+ static constexpr Tdst cast(const Tsrc & src) noexcept {
+ return src;
+ }
+};
+
+template <typename Tdst, typename Tptr>
+struct pointer_cast_helper<Tdst, const Tptr *> {
+ static constexpr Tdst cast(const Tptr * const & src) noexcept {
+ return reinterpret_cast<const Tdst>(src);
+ }
+};
+template <typename Tdst, typename Tptr>
+struct pointer_cast_helper<Tdst, Tptr *> {
+ static constexpr Tdst cast(const Tptr * const & src) noexcept {
+ return reinterpret_cast<const Tdst>(src);
+ }
+};
+
+
+template <typename Tdst, typename Tsrc>
+constexpr Tdst pointer_cast(const Tsrc & src) noexcept {
+ return pointer_cast_helper<Tdst, Tsrc>::cast(src);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_POINTER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/preprocessor.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/preprocessor.hpp
new file mode 100644
index 00000000..fe400102
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/preprocessor.hpp
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_PREPROCESSOR_HPP
+#define MPT_BASE_PREPROCESSOR_HPP
+
+
+
+#define MPT_PP_DEFER(m, ...) m(__VA_ARGS__)
+
+#define MPT_PP_STRINGIFY(x) #x
+
+#define MPT_PP_JOIN_HELPER(a, b) a##b
+#define MPT_PP_JOIN(a, b) MPT_PP_JOIN_HELPER(a, b)
+
+#define MPT_PP_UNIQUE_IDENTIFIER(prefix) MPT_PP_JOIN(prefix, __LINE__)
+
+
+
+#endif // MPT_BASE_PREPROCESSOR_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_cast.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_cast.hpp
new file mode 100644
index 00000000..6ddd604c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_cast.hpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SATURATE_CAST_HPP
+#define MPT_BASE_SATURATE_CAST_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Saturate the value of src to the domain of Tdst
+template <typename Tdst, typename Tsrc>
+constexpr Tdst saturate_cast(Tsrc src) noexcept {
+ // This code tries not only to obviously avoid overflows but also to avoid signed/unsigned comparison warnings and type truncation warnings (which in fact would be safe here) by explicit casting.
+ static_assert(std::numeric_limits<Tdst>::is_integer);
+ static_assert(std::numeric_limits<Tsrc>::is_integer);
+ if constexpr (std::numeric_limits<Tdst>::is_signed && std::numeric_limits<Tsrc>::is_signed) {
+ if constexpr (sizeof(Tdst) >= sizeof(Tsrc)) {
+ return static_cast<Tdst>(src);
+ } else {
+ return static_cast<Tdst>(std::max(static_cast<Tsrc>(std::numeric_limits<Tdst>::min()), std::min(src, static_cast<Tsrc>(std::numeric_limits<Tdst>::max()))));
+ }
+ } else if constexpr (!std::numeric_limits<Tdst>::is_signed && !std::numeric_limits<Tsrc>::is_signed) {
+ if constexpr (sizeof(Tdst) >= sizeof(Tsrc)) {
+ return static_cast<Tdst>(src);
+ } else {
+ return static_cast<Tdst>(std::min(src, static_cast<Tsrc>(std::numeric_limits<Tdst>::max())));
+ }
+ } else if constexpr (std::numeric_limits<Tdst>::is_signed && !std::numeric_limits<Tsrc>::is_signed) {
+ if constexpr (sizeof(Tdst) > sizeof(Tsrc)) {
+ return static_cast<Tdst>(src);
+ } else if constexpr (sizeof(Tdst) == sizeof(Tsrc)) {
+ return static_cast<Tdst>(std::min(src, static_cast<Tsrc>(std::numeric_limits<Tdst>::max())));
+ } else {
+ return static_cast<Tdst>(std::min(src, static_cast<Tsrc>(std::numeric_limits<Tdst>::max())));
+ }
+ } else { // Tdst unsigned, Tsrc signed
+ if constexpr (sizeof(Tdst) >= sizeof(Tsrc)) {
+ return static_cast<Tdst>(std::max(static_cast<Tsrc>(0), src));
+ } else {
+ return static_cast<Tdst>(std::max(static_cast<Tsrc>(0), std::min(src, static_cast<Tsrc>(std::numeric_limits<Tdst>::max()))));
+ }
+ }
+}
+
+template <typename Tdst>
+constexpr Tdst saturate_cast(double src) {
+ if (src >= static_cast<double>(std::numeric_limits<Tdst>::max())) {
+ return std::numeric_limits<Tdst>::max();
+ }
+ if (src <= static_cast<double>(std::numeric_limits<Tdst>::min())) {
+ return std::numeric_limits<Tdst>::min();
+ }
+ return static_cast<Tdst>(src);
+}
+
+template <typename Tdst>
+constexpr Tdst saturate_cast(float src) {
+ if (src >= static_cast<float>(std::numeric_limits<Tdst>::max())) {
+ return std::numeric_limits<Tdst>::max();
+ }
+ if (src <= static_cast<float>(std::numeric_limits<Tdst>::min())) {
+ return std::numeric_limits<Tdst>::min();
+ }
+ return static_cast<Tdst>(src);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SATURATE_CAST_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_round.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_round.hpp
new file mode 100644
index 00000000..7680608b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/saturate_round.hpp
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SATURATE_ROUND_HPP
+#define MPT_BASE_SATURATE_ROUND_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/math.hpp"
+#include "mpt/base/saturate_cast.hpp"
+
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Rounds given double value to nearest integer value of type T.
+// Out-of-range values are saturated to the specified integer type's limits.
+
+template <typename T>
+inline T saturate_round(float val) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::saturate_cast<T>(mpt::round(val));
+}
+
+template <typename T>
+inline T saturate_round(double val) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::saturate_cast<T>(mpt::round(val));
+}
+
+template <typename T>
+inline T saturate_round(long double val) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::saturate_cast<T>(mpt::round(val));
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SATURATE_ROUND_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/secure.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/secure.hpp
new file mode 100644
index 00000000..80b3519d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/secure.hpp
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SECURE_HPP
+#define MPT_BASE_SECURE_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <atomic>
+#include <utility>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace secure {
+
+
+
+inline MPT_NOINLINE void memzero(std::byte * const dst, std::size_t const len) noexcept {
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ volatile std::byte * volatile p = static_cast<volatile std::byte *>(dst);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ for (volatile std::size_t i = 0; i < len; ++i) {
+ p[i] = std::byte{0};
+ }
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+inline MPT_NOINLINE void memzero(void * const dst, std::size_t const len) noexcept {
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ volatile std::byte * volatile p = static_cast<volatile std::byte *>(dst);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ for (volatile std::size_t i = 0; i < len; ++i) {
+ p[i] = std::byte{0};
+ }
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+inline MPT_NOINLINE void memzero(char * const dst, std::size_t const len) noexcept {
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ volatile std::byte * volatile p = reinterpret_cast<volatile std::byte *>(dst);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ for (volatile std::size_t i = 0; i < len; ++i) {
+ p[i] = std::byte{0};
+ }
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+inline MPT_NOINLINE void memzero(uint8 * const dst, std::size_t const len) noexcept {
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ volatile std::byte * volatile p = reinterpret_cast<volatile std::byte *>(dst);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ for (volatile std::size_t i = 0; i < len; ++i) {
+ p[i] = std::byte{0};
+ }
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+
+
+template <typename T>
+inline MPT_NOINLINE void clear(T & val) {
+ std::atomic_signal_fence(std::memory_order_seq_cst);
+ volatile T * volatile v = &val;
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ *v = T{};
+ std::atomic_signal_fence(std::memory_order_seq_cst);
+}
+
+
+
+class byte {
+private:
+ std::byte value;
+
+public:
+ byte() noexcept
+ : value(std::byte{0}) {
+ return;
+ }
+ explicit byte(std::byte value) noexcept
+ : value(value) {
+ return;
+ }
+ byte(const byte & other) noexcept
+ : value(other.value) {
+ return;
+ }
+ byte(byte && other) noexcept
+ : value(std::move(other.value)) {
+ mpt::secure::clear(other.value);
+ }
+ byte & operator=(const byte & other) noexcept {
+ if (&other == this) {
+ return *this;
+ }
+ value = other.value;
+ return *this;
+ }
+ byte & operator==(byte && other) noexcept {
+ if (&other == this) {
+ return *this;
+ }
+ value = std::move(other.value);
+ mpt::secure::clear(other.value);
+ return *this;
+ }
+ explicit operator std::byte() const noexcept {
+ return value;
+ }
+ ~byte() {
+ mpt::secure::clear(value);
+ }
+};
+
+
+
+class buffer {
+private:
+ std::vector<std::byte> m_data;
+
+public:
+ buffer()
+ : m_data(0) {
+ return;
+ }
+ explicit buffer(const std::vector<std::byte> & data)
+ : m_data(data) {
+ return;
+ }
+ explicit buffer(const std::byte * beg, const std::byte * end)
+ : m_data(beg, end) {
+ return;
+ }
+ buffer(const buffer & other)
+ : m_data(other.m_data) {
+ return;
+ }
+ buffer(buffer && other) noexcept
+ : m_data(std::move(other.m_data)) {
+ mpt::secure::memzero(other.m_data.data(), other.m_data.size());
+ }
+ buffer & operator=(const buffer & other) {
+ if (&other == this) {
+ return *this;
+ }
+ m_data = other.m_data;
+ return *this;
+ }
+ buffer & operator=(buffer && other) noexcept {
+ if (&other == this) {
+ return *this;
+ }
+ m_data = std::move(other.m_data);
+ mpt::secure::memzero(other.m_data.data(), other.m_data.size());
+ return *this;
+ }
+ ~buffer() {
+ mpt::secure::memzero(m_data.data(), m_data.size());
+ m_data.resize(0);
+ m_data.shrink_to_fit();
+ }
+ explicit operator std::vector<std::byte>() const {
+ return m_data;
+ }
+ const std::byte * data() const {
+ return m_data.data();
+ }
+ std::byte * data() {
+ return m_data.data();
+ }
+ std::size_t size() const {
+ return m_data.size();
+ }
+};
+
+
+
+} // namespace secure
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SECURE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/semantic_version.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/semantic_version.hpp
new file mode 100644
index 00000000..826ac059
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/semantic_version.hpp
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SEMANTIC_VERSION_HPP
+#define MPT_BASE_SEMANTIC_VERSION_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/version.hpp"
+
+
+
+#include <tuple>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+struct semantic_version {
+ unsigned long long major = 0;
+ unsigned long long minor = 0;
+ unsigned long long patch = 0;
+ constexpr std::tuple<unsigned long long, unsigned long long, unsigned long long> as_tuple() const noexcept {
+ return std::make_tuple(major, minor, patch);
+ }
+};
+
+constexpr bool operator==(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() == b.as_tuple();
+}
+constexpr bool operator!=(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() != b.as_tuple();
+}
+constexpr bool operator<(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() < b.as_tuple();
+}
+constexpr bool operator>(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() > b.as_tuple();
+}
+constexpr bool operator<=(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() <= b.as_tuple();
+}
+constexpr bool operator>=(const semantic_version a, const semantic_version b) noexcept {
+ return a.as_tuple() >= b.as_tuple();
+}
+
+struct version_info {
+ semantic_version semver{};
+ unsigned long long build = 0;
+ constexpr std::tuple<std::tuple<unsigned long long, unsigned long long, unsigned long long>, unsigned long long> as_tuple() const noexcept {
+ return std::make_tuple(semver.as_tuple(), build);
+ }
+ template <typename Tostream>
+ friend Tostream & operator<<(Tostream & os, const version_info vi) {
+ if (vi.build > 0) {
+ os << vi.semver.major << "." << vi.semver.minor << "." << vi.semver.patch << "+build." << vi.build;
+ } else {
+ os << vi.semver.major << "." << vi.semver.minor << "." << vi.semver.patch;
+ }
+ return os;
+ }
+};
+
+constexpr bool operator==(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() == b.as_tuple();
+}
+constexpr bool operator!=(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() != b.as_tuple();
+}
+constexpr bool operator<(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() < b.as_tuple();
+}
+constexpr bool operator>(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() > b.as_tuple();
+}
+constexpr bool operator<=(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() <= b.as_tuple();
+}
+constexpr bool operator>=(const version_info a, const version_info b) noexcept {
+ return a.as_tuple() >= b.as_tuple();
+}
+
+constexpr inline version_info Version = {
+ {MPT_VERSION_MAJOR, MPT_VERSION_MINOR, MPT_VERSION_PATCH},
+ MPT_VERSION_BUILD
+};
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SEMANTIC_VERSION_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/source_location.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/source_location.hpp
new file mode 100644
index 00000000..90be04bc
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/source_location.hpp
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SOURCE_LOCATION_HPP
+#define MPT_BASE_SOURCE_LOCATION_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+
+#if MPT_CXX_AT_LEAST(20) && !MPT_MSVC_BEFORE(2022, 0) && !MPT_COMPILER_CLANG
+#include <source_location>
+#endif // C++20
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_CXX_AT_LEAST(20) && !MPT_MSVC_BEFORE(2022, 0) && !MPT_COMPILER_CLANG
+
+using std::source_location;
+
+#define MPT_SOURCE_LOCATION_CURRENT() std::source_location::current()
+
+#else // !C++20
+
+#if MPT_COMPILER_MSVC && MPT_MSVC_AT_LEAST(2019, 6)
+
+#define MPT_SOURCE_LOCATION_FILE __builtin_FILE()
+#define MPT_SOURCE_LOCATION_FUNCTION __builtin_FUNCTION()
+#define MPT_SOURCE_LOCATION_LINE __builtin_LINE()
+#define MPT_SOURCE_LOCATION_COLUMN __builtin_COLUMN()
+
+#elif MPT_COMPILER_GCC
+
+#define MPT_SOURCE_LOCATION_FILE __builtin_FILE()
+#define MPT_SOURCE_LOCATION_FUNCTION __builtin_FUNCTION()
+#define MPT_SOURCE_LOCATION_LINE __builtin_LINE()
+#define MPT_SOURCE_LOCATION_COLUMN 0
+
+#elif MPT_COMPILER_CLANG && MPT_CLANG_AT_LEAST(9, 0, 0)
+
+#define MPT_SOURCE_LOCATION_FILE __builtin_FILE()
+#define MPT_SOURCE_LOCATION_FUNCTION __builtin_FUNCTION()
+#define MPT_SOURCE_LOCATION_LINE __builtin_LINE()
+#define MPT_SOURCE_LOCATION_COLUMN __builtin_COLUMN()
+
+#else
+
+#define MPT_SOURCE_LOCATION_FILE __FILE__
+#define MPT_SOURCE_LOCATION_FUNCTION ""
+#define MPT_SOURCE_LOCATION_LINE __LINE__
+#define MPT_SOURCE_LOCATION_COLUMN 0
+
+#endif
+
+// compatible with C++20 std::source_location
+struct source_location {
+private:
+ const char * m_file_name;
+ const char * m_function_name;
+ uint32 m_line;
+ uint32 m_column;
+
+public:
+ constexpr source_location() noexcept
+ : m_file_name("")
+ , m_function_name("")
+ , m_line(0)
+ , m_column(0) {
+ }
+ constexpr source_location(const char * file, const char * function, uint32 line, uint32 column) noexcept
+ : m_file_name(file)
+ , m_function_name(function)
+ , m_line(line)
+ , m_column(column) {
+ }
+ source_location(const source_location &) = default;
+ source_location(source_location &&) = default;
+ static constexpr source_location current(const char * file = MPT_SOURCE_LOCATION_FILE, const char * function = MPT_SOURCE_LOCATION_FUNCTION, uint32 line = MPT_SOURCE_LOCATION_LINE, uint32 column = MPT_SOURCE_LOCATION_COLUMN) noexcept {
+ return source_location(file, function, line, column);
+ }
+ constexpr uint32 line() const noexcept {
+ return m_line;
+ }
+ constexpr uint32 column() const noexcept {
+ return m_column;
+ }
+ constexpr const char * file_name() const noexcept {
+ return m_file_name;
+ }
+ constexpr const char * function_name() const noexcept {
+ return m_function_name;
+ }
+};
+
+
+#if (MPT_COMPILER_MSVC && MPT_MSVC_AT_LEAST(2019, 6)) || MPT_COMPILER_GCC || (MPT_COMPILER_CLANG && MPT_CLANG_AT_LEAST(9, 0, 0))
+#define MPT_SOURCE_LOCATION_CURRENT() mpt::source_location::current()
+#else
+#define MPT_SOURCE_LOCATION_CURRENT() mpt::source_location::current(__FILE__, __func__, __LINE__, 0)
+#endif
+
+#endif // C++20
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SOURCE_LOCATION_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/span.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/span.hpp
new file mode 100644
index 00000000..4a4d9c66
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/span.hpp
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_SPAN_HPP
+#define MPT_BASE_SPAN_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#if MPT_CXX_AT_LEAST(20)
+#include <span>
+#else // !C++20
+#include <iterator>
+#include <limits>
+#include <type_traits>
+#endif // C++20
+
+#if MPT_CXX_BEFORE(20)
+#include <cstddef>
+#endif // !C++20
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_CXX_AT_LEAST(20)
+
+using std::dynamic_extent;
+using std::span;
+
+#else // !C++20
+
+// Simplified version of gsl::span.
+// Non-owning read-only or read-write view into a contiguous block of T
+// objects, i.e. equivalent to a (beg,end) or (data,size) tuple.
+// Can eventually be replaced without further modifications with a full C++20
+// std::span.
+
+inline constexpr std::size_t dynamic_extent = std::numeric_limits<std::size_t>::max();
+
+template <typename T>
+class span {
+
+public:
+ using element_type = T;
+ using value_type = typename std::remove_cv<T>::type;
+ using index_type = std::size_t;
+ using pointer = T *;
+ using const_pointer = const T *;
+ using reference = T &;
+ using const_reference = const T &;
+
+ using iterator = pointer;
+
+ using difference_type = typename std::iterator_traits<iterator>::difference_type;
+
+private:
+ T * m_data;
+ std::size_t m_size;
+
+public:
+ span() noexcept
+ : m_data(nullptr)
+ , m_size(0) {
+ }
+
+ span(pointer beg, pointer end)
+ : m_data(beg)
+ , m_size(end - beg) {
+ }
+
+ span(pointer data, index_type size)
+ : m_data(data)
+ , m_size(size) {
+ }
+
+ template <std::size_t N>
+ span(element_type (&arr)[N])
+ : m_data(arr)
+ , m_size(N) {
+ }
+
+ template <std::size_t N>
+ span(std::array<value_type, N> & arr)
+ : m_data(arr.data())
+ , m_size(arr.size()) {
+ }
+
+ template <std::size_t N>
+ span(const std::array<value_type, N> & arr)
+ : m_data(arr.data())
+ , m_size(arr.size()) {
+ }
+
+ span(const span & other) noexcept = default;
+
+ template <typename U>
+ span(const span<U> & other)
+ : m_data(other.data())
+ , m_size(other.size()) {
+ }
+
+ span & operator=(const span & other) noexcept = default;
+
+ iterator begin() const {
+ return iterator(m_data);
+ }
+
+ iterator end() const {
+ return iterator(m_data + m_size);
+ }
+
+ reference operator[](index_type index) {
+ return m_data[index];
+ }
+
+ const_reference operator[](index_type index) const {
+ return m_data[index];
+ }
+
+ bool operator==(const span & other) const noexcept {
+ return size() == other.size() && (m_data == other.m_data || std::equal(begin(), end(), other.begin()));
+ }
+
+ bool operator!=(const span & other) const noexcept {
+ return !(*this == other);
+ }
+
+ pointer data() const noexcept {
+ return m_data;
+ }
+
+ bool empty() const noexcept {
+ return size() == 0;
+ }
+
+ index_type size() const noexcept {
+ return m_size;
+ }
+
+ index_type length() const noexcept {
+ return size();
+ }
+
+ span subspan(std::size_t offset, std::size_t count = mpt::dynamic_extent) const {
+ return span(data() + offset, (count == mpt::dynamic_extent) ? (size() - offset) : count);
+ }
+
+ span first(std::size_t count) const {
+ return span(data(), count);
+ }
+
+ span last(std::size_t count) const {
+ return span(data() + (size() - count), count);
+ }
+
+}; // class span
+
+#endif // C++20
+
+template <typename T>
+inline span<T> as_span(T * beg, T * end) {
+ return span<T>(beg, end);
+}
+
+template <typename T>
+inline span<T> as_span(T * data, std::size_t size) {
+ return span<T>(data, size);
+}
+
+template <typename T, std::size_t N>
+inline span<T> as_span(T (&arr)[N]) {
+ return span<T>(std::begin(arr), std::end(arr));
+}
+
+template <typename T, std::size_t N>
+inline span<T> as_span(std::array<T, N> & cont) {
+ return span<T>(cont);
+}
+
+template <typename T, std::size_t N>
+inline span<const T> as_span(const std::array<T, N> & cont) {
+ return span<const T>(cont);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_SPAN_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_arithmetic_shift.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_arithmetic_shift.hpp
new file mode 100644
index 00000000..d0a4330b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_arithmetic_shift.hpp
@@ -0,0 +1,328 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_ARITHMETIC_SHIFT_HPP
+#define MPT_BASE_TESTS_ARITHMETIC_SHIFT_HPP
+
+
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace arithmetic_shift {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/arithmetic_shift")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 1), mpt::rshift_signed_standard<int16>(-32768, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 1), mpt::rshift_signed_standard<int16>(-32767, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 1), mpt::rshift_signed_standard<int16>(-32766, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 1), mpt::rshift_signed_standard<int16>(-2, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 1), mpt::rshift_signed_standard<int16>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 1), mpt::rshift_signed_standard<int16>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 1), mpt::rshift_signed_standard<int16>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 1), mpt::rshift_signed_standard<int16>(2, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 1), mpt::rshift_signed_standard<int16>(32766, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 1), mpt::rshift_signed_standard<int16>(32767, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 14), mpt::rshift_signed_standard<int16>(-32768, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 14), mpt::rshift_signed_standard<int16>(-32767, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 14), mpt::rshift_signed_standard<int16>(-32766, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 14), mpt::rshift_signed_standard<int16>(-2, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 14), mpt::rshift_signed_standard<int16>(-1, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 14), mpt::rshift_signed_standard<int16>(0, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 14), mpt::rshift_signed_standard<int16>(1, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 14), mpt::rshift_signed_standard<int16>(2, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 14), mpt::rshift_signed_standard<int16>(32766, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 14), mpt::rshift_signed_standard<int16>(32767, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 15), mpt::rshift_signed_standard<int16>(-32768, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 15), mpt::rshift_signed_standard<int16>(-32767, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 15), mpt::rshift_signed_standard<int16>(-32766, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 15), mpt::rshift_signed_standard<int16>(-2, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 15), mpt::rshift_signed_standard<int16>(-1, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 15), mpt::rshift_signed_standard<int16>(0, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 15), mpt::rshift_signed_standard<int16>(1, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 15), mpt::rshift_signed_standard<int16>(2, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 15), mpt::rshift_signed_standard<int16>(32766, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 15), mpt::rshift_signed_standard<int16>(32767, 15));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 1), mpt::lshift_signed_standard<int16>(-32768, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 1), mpt::lshift_signed_standard<int16>(-32767, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 1), mpt::lshift_signed_standard<int16>(-32766, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 1), mpt::lshift_signed_standard<int16>(-2, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 1), mpt::lshift_signed_standard<int16>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 1), mpt::lshift_signed_standard<int16>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 1), mpt::lshift_signed_standard<int16>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 1), mpt::lshift_signed_standard<int16>(2, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 1), mpt::lshift_signed_standard<int16>(32766, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 1), mpt::lshift_signed_standard<int16>(32767, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 14), mpt::lshift_signed_standard<int16>(-32768, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 14), mpt::lshift_signed_standard<int16>(-32767, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 14), mpt::lshift_signed_standard<int16>(-32766, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 14), mpt::lshift_signed_standard<int16>(-2, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 14), mpt::lshift_signed_standard<int16>(-1, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 14), mpt::lshift_signed_standard<int16>(0, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 14), mpt::lshift_signed_standard<int16>(1, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 14), mpt::lshift_signed_standard<int16>(2, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 14), mpt::lshift_signed_standard<int16>(32766, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 14), mpt::lshift_signed_standard<int16>(32767, 14));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 15), mpt::lshift_signed_standard<int16>(-32768, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 15), mpt::lshift_signed_standard<int16>(-32767, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 15), mpt::lshift_signed_standard<int16>(-32766, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 15), mpt::lshift_signed_standard<int16>(-2, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 15), mpt::lshift_signed_standard<int16>(-1, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 15), mpt::lshift_signed_standard<int16>(0, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 15), mpt::lshift_signed_standard<int16>(1, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 15), mpt::lshift_signed_standard<int16>(2, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 15), mpt::lshift_signed_standard<int16>(32766, 15));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 15), mpt::lshift_signed_standard<int16>(32767, 15));
+
+#if MPT_COMPILER_SHIFT_SIGNED
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 1), (-32768) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 1), (-32767) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 1), (-32766) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 1), (-2) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 1), (-1) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 1), (0) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 1), (1) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 1), (2) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 1), (32766) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 1), (32767) >> 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 14), (-32768) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 14), (-32767) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 14), (-32766) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 14), (-2) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 14), (-1) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 14), (0) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 14), (1) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 14), (2) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 14), (32766) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 14), (32767) >> 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32768, 15), (-32768) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32767, 15), (-32767) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-32766, 15), (-32766) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-2, 15), (-2) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(-1, 15), (-1) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(0, 15), (0) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(1, 15), (1) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(2, 15), (2) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32766, 15), (32766) >> 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int16>(32767, 15), (32767) >> 15);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 1), (-32768) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 1), (-32767) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 1), (-32766) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 1), (-2) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 1), (-1) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 1), (0) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 1), (1) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 1), (2) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 1), (32766) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 1), (32767) << 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 14), (-32768) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 14), (-32767) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 14), (-32766) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 14), (-2) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 14), (-1) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 14), (0) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 14), (1) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 14), (2) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 14), (32766) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 14), (32767) << 14);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32768, 15), (-32768) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32767, 15), (-32767) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-32766, 15), (-32766) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-2, 15), (-2) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(-1, 15), (-1) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(0, 15), (0) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(1, 15), (1) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(2, 15), (2) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32766, 15), (32766) << 15);
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int16>(32767, 15), (32767) << 15);
+
+#endif
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0 - 0x80000000, 1), mpt::rshift_signed_standard<int32>(0 - 0x80000000, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7fffffff, 1), mpt::rshift_signed_standard<int32>(-0x7fffffff, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7ffffffe, 1), mpt::rshift_signed_standard<int32>(-0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-1, 1), mpt::rshift_signed_standard<int32>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0, 1), mpt::rshift_signed_standard<int32>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(1, 1), mpt::rshift_signed_standard<int32>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7ffffffe, 1), mpt::rshift_signed_standard<int32>(0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7fffffff, 1), mpt::rshift_signed_standard<int32>(0x7fffffff, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0 - 0x80000000, 31), mpt::rshift_signed_standard<int32>(0 - 0x80000000, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7fffffff, 31), mpt::rshift_signed_standard<int32>(-0x7fffffff, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7ffffffe, 31), mpt::rshift_signed_standard<int32>(-0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-1, 31), mpt::rshift_signed_standard<int32>(-1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0, 31), mpt::rshift_signed_standard<int32>(0, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(1, 31), mpt::rshift_signed_standard<int32>(1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7ffffffe, 31), mpt::rshift_signed_standard<int32>(0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7fffffff, 31), mpt::rshift_signed_standard<int32>(0x7fffffff, 31));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0 - 0x80000000, 1), mpt::lshift_signed_standard<int32>(0 - 0x80000000, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7fffffff, 1), mpt::lshift_signed_standard<int32>(-0x7fffffff, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7ffffffe, 1), mpt::lshift_signed_standard<int32>(-0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-1, 1), mpt::lshift_signed_standard<int32>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0, 1), mpt::lshift_signed_standard<int32>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(1, 1), mpt::lshift_signed_standard<int32>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7ffffffe, 1), mpt::lshift_signed_standard<int32>(0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7fffffff, 1), mpt::lshift_signed_standard<int32>(0x7fffffff, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0 - 0x80000000, 31), mpt::lshift_signed_standard<int32>(0 - 0x80000000, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7fffffff, 31), mpt::lshift_signed_standard<int32>(-0x7fffffff, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7ffffffe, 31), mpt::lshift_signed_standard<int32>(-0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-1, 31), mpt::lshift_signed_standard<int32>(-1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0, 31), mpt::lshift_signed_standard<int32>(0, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(1, 31), mpt::lshift_signed_standard<int32>(1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7ffffffe, 31), mpt::lshift_signed_standard<int32>(0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7fffffff, 31), mpt::lshift_signed_standard<int32>(0x7fffffff, 31));
+
+#if MPT_COMPILER_SHIFT_SIGNED
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0 - 0x80000000, 1), mpt::rshift_signed_undefined<int32>(0 - 0x80000000, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7fffffff, 1), mpt::rshift_signed_undefined<int32>(-0x7fffffff, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7ffffffe, 1), mpt::rshift_signed_undefined<int32>(-0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-1, 1), mpt::rshift_signed_undefined<int32>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0, 1), mpt::rshift_signed_undefined<int32>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(1, 1), mpt::rshift_signed_undefined<int32>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7ffffffe, 1), mpt::rshift_signed_undefined<int32>(0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7fffffff, 1), mpt::rshift_signed_undefined<int32>(0x7fffffff, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0 - 0x80000000, 31), mpt::rshift_signed_undefined<int32>(0 - 0x80000000, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7fffffff, 31), mpt::rshift_signed_undefined<int32>(-0x7fffffff, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-0x7ffffffe, 31), mpt::rshift_signed_undefined<int32>(-0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(-1, 31), mpt::rshift_signed_undefined<int32>(-1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0, 31), mpt::rshift_signed_undefined<int32>(0, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(1, 31), mpt::rshift_signed_undefined<int32>(1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7ffffffe, 31), mpt::rshift_signed_undefined<int32>(0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int32>(0x7fffffff, 31), mpt::rshift_signed_undefined<int32>(0x7fffffff, 31));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0 - 0x80000000, 1), mpt::lshift_signed_undefined<int32>(0 - 0x80000000, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7fffffff, 1), mpt::lshift_signed_undefined<int32>(-0x7fffffff, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7ffffffe, 1), mpt::lshift_signed_undefined<int32>(-0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-1, 1), mpt::lshift_signed_undefined<int32>(-1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0, 1), mpt::lshift_signed_undefined<int32>(0, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(1, 1), mpt::lshift_signed_undefined<int32>(1, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7ffffffe, 1), mpt::lshift_signed_undefined<int32>(0x7ffffffe, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7fffffff, 1), mpt::lshift_signed_undefined<int32>(0x7fffffff, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0 - 0x80000000, 31), mpt::lshift_signed_undefined<int32>(0 - 0x80000000, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7fffffff, 31), mpt::lshift_signed_undefined<int32>(-0x7fffffff, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-0x7ffffffe, 31), mpt::lshift_signed_undefined<int32>(-0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(-1, 31), mpt::lshift_signed_undefined<int32>(-1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0, 31), mpt::lshift_signed_undefined<int32>(0, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(1, 31), mpt::lshift_signed_undefined<int32>(1, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7ffffffe, 31), mpt::lshift_signed_undefined<int32>(0x7ffffffe, 31));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int32>(0x7fffffff, 31), mpt::lshift_signed_undefined<int32>(0x7fffffff, 31));
+
+#endif
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ull - 0x8000000000000000ull, 1), mpt::rshift_signed_standard<int64>(0ull - 0x8000000000000000ull, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7fffffffffffffffll, 1), mpt::rshift_signed_standard<int64>(-0x7fffffffffffffffll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7ffffffffffffffell, 1), mpt::rshift_signed_standard<int64>(-0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-1ll, 1), mpt::rshift_signed_standard<int64>(-1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ll, 1), mpt::rshift_signed_standard<int64>(0ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(1ll, 1), mpt::rshift_signed_standard<int64>(1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7ffffffffffffffell, 1), mpt::rshift_signed_standard<int64>(0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7fffffffffffffffll, 1), mpt::rshift_signed_standard<int64>(0x7fffffffffffffffll, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ull - 0x8000000000000000ull, 63), mpt::rshift_signed_standard<int64>(0ull - 0x8000000000000000ull, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7fffffffffffffffll, 63), mpt::rshift_signed_standard<int64>(-0x7fffffffffffffffll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7ffffffffffffffell, 63), mpt::rshift_signed_standard<int64>(-0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-1ll, 63), mpt::rshift_signed_standard<int64>(-1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ll, 63), mpt::rshift_signed_standard<int64>(0ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(1ll, 63), mpt::rshift_signed_standard<int64>(1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7ffffffffffffffell, 63), mpt::rshift_signed_standard<int64>(0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7fffffffffffffffll, 63), mpt::rshift_signed_standard<int64>(0x7fffffffffffffffll, 63));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ull - 0x8000000000000000ull, 1), mpt::lshift_signed_standard<int64>(0ull - 0x8000000000000000ull, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7fffffffffffffffll, 1), mpt::lshift_signed_standard<int64>(-0x7fffffffffffffffll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7ffffffffffffffell, 1), mpt::lshift_signed_standard<int64>(-0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-1ll, 1), mpt::lshift_signed_standard<int64>(-1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ll, 1), mpt::lshift_signed_standard<int64>(0ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(1ll, 1), mpt::lshift_signed_standard<int64>(1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7ffffffffffffffell, 1), mpt::lshift_signed_standard<int64>(0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7fffffffffffffffll, 1), mpt::lshift_signed_standard<int64>(0x7fffffffffffffffll, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ull - 0x8000000000000000ull, 63), mpt::lshift_signed_standard<int64>(0ull - 0x8000000000000000ull, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7fffffffffffffffll, 63), mpt::lshift_signed_standard<int64>(-0x7fffffffffffffffll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7ffffffffffffffell, 63), mpt::lshift_signed_standard<int64>(-0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-1ll, 63), mpt::lshift_signed_standard<int64>(-1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ll, 63), mpt::lshift_signed_standard<int64>(0ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(1ll, 63), mpt::lshift_signed_standard<int64>(1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7ffffffffffffffell, 63), mpt::lshift_signed_standard<int64>(0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7fffffffffffffffll, 63), mpt::lshift_signed_standard<int64>(0x7fffffffffffffffll, 63));
+
+#if MPT_COMPILER_SHIFT_SIGNED
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ull - 0x8000000000000000ull, 1), mpt::rshift_signed_undefined<int64>(0ull - 0x8000000000000000ull, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7fffffffffffffffll, 1), mpt::rshift_signed_undefined<int64>(-0x7fffffffffffffffll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7ffffffffffffffell, 1), mpt::rshift_signed_undefined<int64>(-0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-1ll, 1), mpt::rshift_signed_undefined<int64>(-1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ll, 1), mpt::rshift_signed_undefined<int64>(0ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(1ll, 1), mpt::rshift_signed_undefined<int64>(1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7ffffffffffffffell, 1), mpt::rshift_signed_undefined<int64>(0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7fffffffffffffffll, 1), mpt::rshift_signed_undefined<int64>(0x7fffffffffffffffll, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ull - 0x8000000000000000ull, 63), mpt::rshift_signed_undefined<int64>(0ull - 0x8000000000000000ull, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7fffffffffffffffll, 63), mpt::rshift_signed_undefined<int64>(-0x7fffffffffffffffll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-0x7ffffffffffffffell, 63), mpt::rshift_signed_undefined<int64>(-0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(-1ll, 63), mpt::rshift_signed_undefined<int64>(-1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0ll, 63), mpt::rshift_signed_undefined<int64>(0ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(1ll, 63), mpt::rshift_signed_undefined<int64>(1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7ffffffffffffffell, 63), mpt::rshift_signed_undefined<int64>(0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::rshift_signed<int64>(0x7fffffffffffffffll, 63), mpt::rshift_signed_undefined<int64>(0x7fffffffffffffffll, 63));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ull - 0x8000000000000000ull, 1), mpt::lshift_signed_undefined<int64>(0ull - 0x8000000000000000ull, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7fffffffffffffffll, 1), mpt::lshift_signed_undefined<int64>(-0x7fffffffffffffffll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7ffffffffffffffell, 1), mpt::lshift_signed_undefined<int64>(-0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-1ll, 1), mpt::lshift_signed_undefined<int64>(-1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ll, 1), mpt::lshift_signed_undefined<int64>(0ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(1ll, 1), mpt::lshift_signed_undefined<int64>(1ll, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7ffffffffffffffell, 1), mpt::lshift_signed_undefined<int64>(0x7ffffffffffffffell, 1));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7fffffffffffffffll, 1), mpt::lshift_signed_undefined<int64>(0x7fffffffffffffffll, 1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ull - 0x8000000000000000ull, 63), mpt::lshift_signed_undefined<int64>(0ull - 0x8000000000000000ull, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7fffffffffffffffll, 63), mpt::lshift_signed_undefined<int64>(-0x7fffffffffffffffll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-0x7ffffffffffffffell, 63), mpt::lshift_signed_undefined<int64>(-0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(-1ll, 63), mpt::lshift_signed_undefined<int64>(-1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0ll, 63), mpt::lshift_signed_undefined<int64>(0ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(1ll, 63), mpt::lshift_signed_undefined<int64>(1ll, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7ffffffffffffffell, 63), mpt::lshift_signed_undefined<int64>(0x7ffffffffffffffell, 63));
+ MPT_TEST_EXPECT_EQUAL(mpt::lshift_signed<int64>(0x7fffffffffffffffll, 63), mpt::lshift_signed_undefined<int64>(0x7fffffffffffffffll, 63));
+
+#endif
+}
+
+} // namespace arithmetic_shift
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_ARITHMETIC_SHIFT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_bit.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_bit.hpp
new file mode 100644
index 00000000..7aed082c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_bit.hpp
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_BASE_BIT_HPP
+#define MPT_BASE_TESTS_BASE_BIT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace bit {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/bit")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+#if MPT_CXX_BEFORE(20)
+ MPT_TEST_EXPECT_EQUAL(mpt::get_endian(), mpt::endian_probe());
+#endif
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_little()) {
+ MPT_TEST_EXPECT_EQUAL(mpt::get_endian(), mpt::endian::little);
+ MPT_MAYBE_CONSTANT_IF ((mpt::endian::native == mpt::endian::little) || (mpt::endian::native == mpt::endian::big)) {
+ MPT_TEST_EXPECT_EQUAL(mpt::endian::native, mpt::endian::little);
+ }
+#if MPT_CXX_BEFORE(20)
+ MPT_TEST_EXPECT_EQUAL(mpt::endian_probe(), mpt::endian::little);
+#endif
+ }
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_big()) {
+ MPT_TEST_EXPECT_EQUAL(mpt::get_endian(), mpt::endian::big);
+ MPT_MAYBE_CONSTANT_IF ((mpt::endian::native == mpt::endian::little) || (mpt::endian::native == mpt::endian::big)) {
+ MPT_TEST_EXPECT_EQUAL(mpt::endian::native, mpt::endian::big);
+ }
+#if MPT_CXX_BEFORE(20)
+ MPT_TEST_EXPECT_EQUAL(mpt::endian_probe(), mpt::endian::big);
+#endif
+ }
+
+ MPT_TEST_EXPECT_EQUAL(mpt::popcount(static_cast<uint32>(int32(-1))), 32);
+ MPT_TEST_EXPECT_EQUAL(mpt::popcount(0u), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::popcount(1u), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::popcount(2u), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::popcount(3u), 2);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(0u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(1u), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(2u), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(3u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(4u), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(5u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(6u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(7u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(8u), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(9u), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(uint32(0x7fffffffu)), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(uint32(0x80000000u)), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(uint32(0x80000001u)), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(uint32(0xfffffffeu)), false);
+ MPT_TEST_EXPECT_EQUAL(mpt::has_single_bit(uint32(0xffffffffu)), false);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(0u), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(1u), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(2u), 2u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(3u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(4u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(5u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(6u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(7u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(8u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(9u), 16u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(uint32(0x7fffffffu)), 0x80000000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(uint32(0x80000000u)), 0x80000000u);
+ //MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(uint32(0x80000001u)), 0u);
+ //MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(uint32(0xfffffffeu)), 0u);
+ //MPT_TEST_EXPECT_EQUAL(mpt::bit_ceil(uint32(0xffffffffu)), 0u);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(0u), 0u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(1u), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(2u), 2u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(3u), 2u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(4u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(5u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(6u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(7u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(8u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(9u), 8u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(uint32(0x7fffffffu)), 0x40000000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(uint32(0x80000000u)), 0x80000000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(uint32(0x80000001u)), 0x80000000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(uint32(0xfffffffeu)), 0x80000000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_floor(uint32(0xffffffffu)), 0x80000000u);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(0u), 0u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(1u), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(2u), 2u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(3u), 2u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(4u), 3u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(5u), 3u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(6u), 3u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(7u), 3u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(8u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(9u), 4u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(uint32(0x7fffffffu)), 31u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(uint32(0x80000000u)), 32u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(uint32(0x80000001u)), 32u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(uint32(0xfffffffeu)), 32u);
+ MPT_TEST_EXPECT_EQUAL(mpt::bit_width(uint32(0xffffffffu)), 32u);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00000001)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00000011)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00000111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00001111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00011111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b01111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11111111)), 8);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11111110)), 7);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11111100)), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11111000)), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11110000)), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11100000)), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b11000000)), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b10000000)), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_one(uint8(0b00000000)), 0);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00000000)), 8);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00000001)), 7);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00000011)), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00000111)), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00001111)), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00011111)), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00111111)), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b01111111)), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11111110)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11111100)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11111000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11110000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11100000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b11000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b10000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countl_zero(uint8(0b00000000)), 8);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00000001)), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00000011)), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00000111)), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00001111)), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00011111)), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00111111)), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b01111111)), 7);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11111111)), 8);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11111110)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11111100)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11111000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11110000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11100000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b11000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b10000000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_one(uint8(0b00000000)), 0);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00000000)), 8);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00000001)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00000011)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00000111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00001111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00011111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b01111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11111111)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11111110)), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11111100)), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11111000)), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11110000)), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11100000)), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b11000000)), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b10000000)), 7);
+ MPT_TEST_EXPECT_EQUAL(mpt::countr_zero(uint8(0b00000000)), 8);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0xffffffffu), 32);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0xfffffffeu), 31);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x80000000u), 31);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x7fffffffu), 31);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x7ffffffeu), 30);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000007u), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000006u), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000005u), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000004u), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000003u), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000002u), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000001u), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::lower_bound_entropy_bits(0x00000000u), 0);
+}
+
+} // namespace bit
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_BASE_BIT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_math.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_math.hpp
new file mode 100644
index 00000000..ba22dc1b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_math.hpp
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_MATH_HPP
+#define MPT_BASE_TESTS_MATH_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace math {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/math")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::round(1.99), 2.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(1.5), 2.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(1.1), 1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(-0.1), 0.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(-0.5), -1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(-0.9), -1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(-1.4), -1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::round(-1.7), -2.0);
+}
+
+} // namespace math
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_MATH_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_cast.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_cast.hpp
new file mode 100644
index 00000000..e2d69ad9
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_cast.hpp
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_SATURATE_CAST_HPP
+#define MPT_BASE_TESTS_SATURATE_CAST_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace saturate_cast {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/saturate_cast")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ // trivials
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int>(-1), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int>(0), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int>(1), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int>(std::numeric_limits<int>::min()), std::numeric_limits<int>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int>(std::numeric_limits<int>::max()), std::numeric_limits<int>::max());
+
+ // signed / unsigned
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(std::numeric_limits<uint16>::min()), std::numeric_limits<uint16>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(std::numeric_limits<uint16>::max()), std::numeric_limits<int16>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(std::numeric_limits<uint32>::min()), (int32)std::numeric_limits<uint32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(std::numeric_limits<uint32>::max()), std::numeric_limits<int32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int64>(std::numeric_limits<uint64>::min()), (int64)std::numeric_limits<uint64>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int64>(std::numeric_limits<uint64>::max()), std::numeric_limits<int64>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(std::numeric_limits<int16>::min()), std::numeric_limits<uint16>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(std::numeric_limits<int16>::max()), std::numeric_limits<int16>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(std::numeric_limits<int32>::min()), std::numeric_limits<uint32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(std::numeric_limits<int32>::max()), (uint32)std::numeric_limits<int32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint64>(std::numeric_limits<int64>::min()), std::numeric_limits<uint64>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint64>(std::numeric_limits<int64>::max()), (uint64)std::numeric_limits<int64>::max());
+
+ // overflow
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(std::numeric_limits<int16>::min() - 1), std::numeric_limits<int16>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(std::numeric_limits<int16>::max() + 1), std::numeric_limits<int16>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(std::numeric_limits<int32>::min() - int64(1)), std::numeric_limits<int32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(std::numeric_limits<int32>::max() + int64(1)), std::numeric_limits<int32>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(std::numeric_limits<int16>::min() - 1), std::numeric_limits<uint16>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(std::numeric_limits<int16>::max() + 1), (uint16)std::numeric_limits<int16>::max() + 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(std::numeric_limits<int32>::min() - int64(1)), std::numeric_limits<uint32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(std::numeric_limits<int32>::max() + int64(1)), (uint32)std::numeric_limits<int32>::max() + 1);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int8>(int16(32000)), 127);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int8>(int16(-32000)), -128);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int8>(uint16(32000)), 127);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int8>(uint16(64000)), 127);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint8>(int16(32000)), 255);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint8>(int16(-32000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint8>(uint16(32000)), 255);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint8>(uint16(64000)), 255);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(int16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(int16(-32000)), -32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(uint16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int16>(uint16(64000)), 32767);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(int16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(int16(-32000)), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(uint16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint16>(uint16(64000)), 64000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(int16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(int16(-32000)), -32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(uint16(32000)), 32000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(uint16(64000)), 64000);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(int16(32000)), 32000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(int16(-32000)), 0u);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(uint16(32000)), 32000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(uint16(64000)), 64000u);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(std::numeric_limits<int64>::max() - 1), std::numeric_limits<uint32>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<int32>(std::numeric_limits<uint64>::max() - 1), std::numeric_limits<int32>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast<uint32>(static_cast<double>(std::numeric_limits<int64>::max())), std::numeric_limits<uint32>::max());
+}
+
+} // namespace saturate_cast
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_SATURATE_CAST_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_round.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_round.hpp
new file mode 100644
index 00000000..27f753e7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_saturate_round.hpp
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_SATURATE_ROUND_HPP
+#define MPT_BASE_TESTS_SATURATE_ROUND_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace saturate_round {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/saturate_round")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int32>(std::numeric_limits<int32>::max() + 0.1), std::numeric_limits<int32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int32>(std::numeric_limits<int32>::max() - 0.4), std::numeric_limits<int32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int32>(std::numeric_limits<int32>::min() + 0.1), std::numeric_limits<int32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int32>(std::numeric_limits<int32>::min() - 0.1), std::numeric_limits<int32>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<uint32>(std::numeric_limits<uint32>::max() + 0.499), std::numeric_limits<uint32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int8>(110.1), 110);
+ MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int8>(-110.1), -110);
+
+ // These should fail to compile
+ //mpt::saturate_round<std::string>(1.0);
+ //mpt::saturate_round<int64>(1.0);
+ //mpt::saturate_round<uint64>(1.0);
+
+ // This should trigger assert in Round.
+ //MPT_TEST_EXPECT_EQUAL(mpt::saturate_round<int8>(-129), 0);
+}
+
+} // namespace saturate_round
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_SATURATE_ROUND_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_wrapping_divide.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_wrapping_divide.hpp
new file mode 100644
index 00000000..02b07185
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/tests/tests_base_wrapping_divide.hpp
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_WRAPPING_DIVIDE_HPP
+#define MPT_BASE_TESTS_WRAPPING_DIVIDE_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/wrapping_divide.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace base {
+namespace wrapping_divide {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/base/wrapping_divide")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-25, 12), 11);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-24, 12), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-23, 12), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-8, 7), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-7, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-6, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-5, 7), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-4, 7), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-3, 7), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-2, 7), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(-1, 7), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(0, 12), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(0, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(1, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(2, 7), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(3, 7), 3);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(4, 7), 4);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(5, 7), 5);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(6, 7), 6);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(7, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(8, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(23, 12), 11);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(24, 12), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(25, 12), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(uint32(0x7fffffff), uint32(0x80000000)), uint32(0x7fffffff));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(0x7ffffffe), int32(0x7fffffff)), int32(0x7ffffffe));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), int32(1)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), int32(2)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), int32(1)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), int32(2)), int32(1));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), int32(1)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), int32(2)), int32(0));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), int32(0x7fffffff)), int32(0x7ffffffe));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), int32(0x7fffffff)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), int32(0x7fffffff)), int32(1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), int32(0x7ffffffe)), int32(0x7ffffffc));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), int32(0x7ffffffe)), int32(0x7ffffffd));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), int32(0x7ffffffe)), int32(0));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), int32(0x7ffffffd)), int32(0x7ffffffa));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), int32(0x7ffffffd)), int32(0x7ffffffb));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), int32(0x7ffffffd)), int32(0x7ffffffc));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(0), int32(0x7fffffff)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-1), int32(0x7fffffff)), int32(0x7ffffffe));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-2), int32(0x7fffffff)), int32(0x7ffffffd));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(0), int32(0x7ffffffe)), int32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-1), int32(0x7ffffffe)), int32(0x7ffffffd));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-2), int32(0x7ffffffe)), int32(0x7ffffffc));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(1)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(2)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(1)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(2)), uint32(1));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(1)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(2)), uint32(0));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x40000001), uint32(0xffffffff)), uint32(0xbffffffe));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x40000000), uint32(0xffffffff)), uint32(0xbfffffff));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x3fffffff), uint32(0xffffffff)), uint32(0xc0000000));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x80000000)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x80000000)), uint32(1));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x80000000)), uint32(2));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x80000001)), uint32(1));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x80000001)), uint32(2));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x80000001)), uint32(3));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x80000000)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x80000000)), uint32(1));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x80000000)), uint32(2));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x7fffffff)), uint32(0x7ffffffe));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x7fffffff)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x7fffffff)), uint32(1));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x7ffffffe)), uint32(0x7ffffffc));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x7ffffffe)), uint32(0x7ffffffd));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x7ffffffe)), uint32(0));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x80000000ll), uint32(0x7ffffffd)), uint32(0x7ffffffa));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7fffffff), uint32(0x7ffffffd)), uint32(0x7ffffffb));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-0x7ffffffe), uint32(0x7ffffffd)), uint32(0x7ffffffc));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(0), uint32(0x7fffffff)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-1), uint32(0x7fffffff)), uint32(0x7ffffffe));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-2), uint32(0x7fffffff)), uint32(0x7ffffffd));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(0), uint32(0x7ffffffe)), uint32(0));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-1), uint32(0x7ffffffe)), uint32(0x7ffffffd));
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_modulo(int32(-2), uint32(0x7ffffffe)), uint32(0x7ffffffc));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-15, 7), -3);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-14, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-13, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-12, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-11, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-10, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-9, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-8, 7), -2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-7, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-6, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-5, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-4, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-3, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-2, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(-1, 7), -1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(0, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(1, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(2, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(3, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(4, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(5, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(6, 7), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(7, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(8, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(9, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(10, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(11, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(12, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(13, 7), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(14, 7), 2);
+ MPT_TEST_EXPECT_EQUAL(mpt::wrapping_divide(15, 7), 2);
+}
+
+} // namespace wrapping_divide
+} // namespace base
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_WRAPPING_DIVIDE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/utility.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/utility.hpp
new file mode 100644
index 00000000..7087a6a2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/utility.hpp
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_UTILITY_HPP
+#define MPT_BASE_UTILITY_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+
+#if MPT_CXX_BEFORE(20)
+#include "mpt/base/saturate_cast.hpp"
+#endif
+
+#include <type_traits>
+#include <utility>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(13, 0, 0)
+
+using std::in_range;
+
+#else
+
+// Returns true iff Tdst can represent the value val.
+// Use as if(mpt::in_range<uint8>(-1)).
+template <typename Tdst, typename Tsrc>
+constexpr bool in_range(Tsrc val) {
+ return (static_cast<Tsrc>(mpt::saturate_cast<Tdst>(val)) == val);
+}
+
+#endif
+
+
+#if MPT_CXX_AT_LEAST(23)
+
+using std::to_underlying;
+
+#else
+
+template <typename T>
+constexpr std::underlying_type_t<T> to_underlying(T value) noexcept {
+ return static_cast<typename std::underlying_type<T>::type>(value);
+}
+
+#endif
+
+
+
+template <typename T>
+struct value_initializer {
+ inline void operator()(T & x) {
+ x = T{};
+ }
+};
+
+template <typename T, std::size_t N>
+struct value_initializer<T[N]> {
+ inline void operator()(T (&a)[N]) {
+ for (auto & e : a)
+ {
+ value_initializer<T>{}(e);
+ }
+ }
+};
+
+template <typename T>
+inline void reset(T & x) {
+ value_initializer<T>{}(x);
+}
+
+
+
+#if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(13, 0, 0)
+
+using std::cmp_equal;
+using std::cmp_greater;
+using std::cmp_greater_equal;
+using std::cmp_less;
+using std::cmp_less_equal;
+using std::cmp_not_equal;
+
+#else
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_equal(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a == b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? false : static_cast<UTa>(a) == b;
+ } else {
+ return (b < 0) ? false : a == static_cast<UTb>(b);
+ }
+}
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_not_equal(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a != b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? true : static_cast<UTa>(a) != b;
+ } else {
+ return (b < 0) ? true : a != static_cast<UTb>(b);
+ }
+}
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_less(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a < b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? true : static_cast<UTa>(a) < b;
+ } else {
+ return (b < 0) ? false : a < static_cast<UTb>(b);
+ }
+}
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_greater(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a > b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? false : static_cast<UTa>(a) > b;
+ } else {
+ return (b < 0) ? true : a > static_cast<UTb>(b);
+ }
+}
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_less_equal(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a <= b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? true : static_cast<UTa>(a) <= b;
+ } else {
+ return (b < 0) ? false : a <= static_cast<UTb>(b);
+ }
+}
+
+template <typename Ta, typename Tb>
+constexpr bool cmp_greater_equal(Ta a, Tb b) noexcept {
+ using UTa = typename std::make_unsigned<Ta>::type;
+ using UTb = typename std::make_unsigned<Tb>::type;
+ if constexpr (std::is_signed<Ta>::value == std::is_signed<Tb>::value) {
+ return a >= b;
+ } else if constexpr (std::is_signed<Ta>::value) {
+ return (a < 0) ? false : static_cast<UTa>(a) >= b;
+ } else {
+ return (b < 0) ? true : a >= static_cast<UTb>(b);
+ }
+}
+
+#endif
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_UTILITY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/version.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/version.hpp
new file mode 100644
index 00000000..1ac14ed9
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/version.hpp
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_VERSION_HPP
+#define MPT_BASE_VERSION_HPP
+
+
+
+#define MPT_VERSION_MAJOR 0
+#define MPT_VERSION_MINOR 0
+#define MPT_VERSION_PATCH 0
+#define MPT_VERSION_BUILD 0
+
+
+
+#endif // MPT_BASE_VERSION_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/base/wrapping_divide.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/base/wrapping_divide.hpp
new file mode 100644
index 00000000..a0c4acdb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/base/wrapping_divide.hpp
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_WRAPPING_DIVIDE_HPP
+#define MPT_BASE_WRAPPING_DIVIDE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Modulo with more intuitive behaviour for some contexts:
+// Instead of being symmetrical around 0, the pattern for positive numbers is repeated in the negative range.
+// For example, wrapping_modulo(-1, m) == (m - 1).
+// Behaviour is undefined if m<=0.
+template <typename T, typename M>
+constexpr auto wrapping_modulo(T x, M m) -> decltype(x % m) {
+ return (x >= 0) ? (x % m) : (m - 1 - ((-1 - x) % m));
+}
+
+template <typename T, typename D>
+constexpr auto wrapping_divide(T x, D d) -> decltype(x / d) {
+ return (x >= 0) ? (x / d) : (((x + 1) / d) - 1);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_WRAPPING_DIVIDE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64.hpp
new file mode 100644
index 00000000..579f6710
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64.hpp
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BINARY_BASE64_HPP
+#define MPT_BINARY_BASE64_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+
+#include <array>
+#include <stdexcept>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+class base64_parse_error : public std::runtime_error {
+public:
+ base64_parse_error()
+ : std::runtime_error("invalid Base64 encoding") {
+ }
+};
+
+
+inline constexpr std::array<mpt::uchar, 64> base64 = {
+ {MPT_UCHAR('A'), MPT_UCHAR('B'), MPT_UCHAR('C'), MPT_UCHAR('D'), MPT_UCHAR('E'), MPT_UCHAR('F'), MPT_UCHAR('G'), MPT_UCHAR('H'), MPT_UCHAR('I'), MPT_UCHAR('J'), MPT_UCHAR('K'), MPT_UCHAR('L'), MPT_UCHAR('M'), MPT_UCHAR('N'), MPT_UCHAR('O'), MPT_UCHAR('P'),
+ MPT_UCHAR('Q'), MPT_UCHAR('R'), MPT_UCHAR('S'), MPT_UCHAR('T'), MPT_UCHAR('U'), MPT_UCHAR('V'), MPT_UCHAR('W'), MPT_UCHAR('X'), MPT_UCHAR('Y'), MPT_UCHAR('Z'), MPT_UCHAR('a'), MPT_UCHAR('b'), MPT_UCHAR('c'), MPT_UCHAR('d'), MPT_UCHAR('e'), MPT_UCHAR('f'),
+ MPT_UCHAR('g'), MPT_UCHAR('h'), MPT_UCHAR('i'), MPT_UCHAR('j'), MPT_UCHAR('k'), MPT_UCHAR('l'), MPT_UCHAR('m'), MPT_UCHAR('n'), MPT_UCHAR('o'), MPT_UCHAR('p'), MPT_UCHAR('q'), MPT_UCHAR('r'), MPT_UCHAR('s'), MPT_UCHAR('t'), MPT_UCHAR('u'), MPT_UCHAR('v'),
+ MPT_UCHAR('w'), MPT_UCHAR('x'), MPT_UCHAR('y'), MPT_UCHAR('z'), MPT_UCHAR('0'), MPT_UCHAR('1'), MPT_UCHAR('2'), MPT_UCHAR('3'), MPT_UCHAR('4'), MPT_UCHAR('5'), MPT_UCHAR('6'), MPT_UCHAR('7'), MPT_UCHAR('8'), MPT_UCHAR('9'), MPT_UCHAR('+'), MPT_UCHAR('/')}
+};
+
+
+template <typename Tbyte>
+inline mpt::ustring encode_base64(mpt::span<Tbyte> src_) {
+ mpt::const_byte_span src = mpt::byte_cast<mpt::const_byte_span>(src_);
+ mpt::ustring result;
+ result.reserve(4 * ((src.size() + 2) / 3));
+ uint32 bits = 0;
+ std::size_t bytes = 0;
+ for (std::byte byte : src) {
+ bits <<= 8;
+ bits |= mpt::byte_cast<uint8>(byte);
+ bytes++;
+ if (bytes == 3) {
+ result.push_back(base64[(bits >> 18) & 0x3f]);
+ result.push_back(base64[(bits >> 12) & 0x3f]);
+ result.push_back(base64[(bits >> 6) & 0x3f]);
+ result.push_back(base64[(bits >> 0) & 0x3f]);
+ bits = 0;
+ bytes = 0;
+ }
+ }
+ std::size_t padding = 0;
+ while (bytes != 0) {
+ bits <<= 8;
+ padding++;
+ bytes++;
+ if (bytes == 3) {
+ result.push_back(base64[(bits >> 18) & 0x3f]);
+ result.push_back(base64[(bits >> 12) & 0x3f]);
+ if (padding > 1) {
+ result.push_back(MPT_UCHAR('='));
+ } else {
+ result.push_back(base64[(bits >> 6) & 0x3f]);
+ }
+ if (padding > 0) {
+ result.push_back(MPT_UCHAR('='));
+ } else {
+ result.push_back(base64[(bits >> 0) & 0x3f]);
+ }
+ bits = 0;
+ bytes = 0;
+ }
+ }
+ return result;
+}
+
+inline uint8 decode_base64_bits(mpt::uchar c) {
+ for (uint8 i = 0; i < 64; ++i) {
+ if (base64[i] == c) {
+ return i;
+ }
+ }
+ throw base64_parse_error();
+}
+
+
+inline std::vector<std::byte> decode_base64(const mpt::ustring & src) {
+ std::vector<std::byte> result;
+ result.reserve(3 * (src.length() / 4));
+ uint32 bits = 0;
+ std::size_t chars = 0;
+ std::size_t padding = 0;
+ for (mpt::uchar c : src) {
+ bits <<= 6;
+ if (c == MPT_UCHAR('=')) {
+ padding++;
+ } else {
+ bits |= decode_base64_bits(c);
+ }
+ chars++;
+ if (chars == 4) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 16) & 0xff)));
+ if (padding < 2) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 8) & 0xff)));
+ }
+ if (padding < 1) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 0) & 0xff)));
+ }
+ bits = 0;
+ chars = 0;
+ padding = 0;
+ }
+ }
+ if (chars != 0) {
+ throw base64_parse_error();
+ }
+ return result;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BINARY_BASE64_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64url.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64url.hpp
new file mode 100644
index 00000000..04859f87
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/base64url.hpp
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BINARY_BASE64URL_HPP
+#define MPT_BINARY_BASE64URL_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+
+#include <array>
+#include <stdexcept>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+class base64url_parse_error : public std::runtime_error {
+public:
+ base64url_parse_error()
+ : std::runtime_error("invalid Base64URL encoding") {
+ }
+};
+
+
+inline constexpr std::array<mpt::uchar, 64> base64url = {
+ {MPT_UCHAR('A'), MPT_UCHAR('B'), MPT_UCHAR('C'), MPT_UCHAR('D'), MPT_UCHAR('E'), MPT_UCHAR('F'), MPT_UCHAR('G'), MPT_UCHAR('H'), MPT_UCHAR('I'), MPT_UCHAR('J'), MPT_UCHAR('K'), MPT_UCHAR('L'), MPT_UCHAR('M'), MPT_UCHAR('N'), MPT_UCHAR('O'), MPT_UCHAR('P'),
+ MPT_UCHAR('Q'), MPT_UCHAR('R'), MPT_UCHAR('S'), MPT_UCHAR('T'), MPT_UCHAR('U'), MPT_UCHAR('V'), MPT_UCHAR('W'), MPT_UCHAR('X'), MPT_UCHAR('Y'), MPT_UCHAR('Z'), MPT_UCHAR('a'), MPT_UCHAR('b'), MPT_UCHAR('c'), MPT_UCHAR('d'), MPT_UCHAR('e'), MPT_UCHAR('f'),
+ MPT_UCHAR('g'), MPT_UCHAR('h'), MPT_UCHAR('i'), MPT_UCHAR('j'), MPT_UCHAR('k'), MPT_UCHAR('l'), MPT_UCHAR('m'), MPT_UCHAR('n'), MPT_UCHAR('o'), MPT_UCHAR('p'), MPT_UCHAR('q'), MPT_UCHAR('r'), MPT_UCHAR('s'), MPT_UCHAR('t'), MPT_UCHAR('u'), MPT_UCHAR('v'),
+ MPT_UCHAR('w'), MPT_UCHAR('x'), MPT_UCHAR('y'), MPT_UCHAR('z'), MPT_UCHAR('0'), MPT_UCHAR('1'), MPT_UCHAR('2'), MPT_UCHAR('3'), MPT_UCHAR('4'), MPT_UCHAR('5'), MPT_UCHAR('6'), MPT_UCHAR('7'), MPT_UCHAR('8'), MPT_UCHAR('9'), MPT_UCHAR('-'), MPT_UCHAR('_')}
+};
+
+template <typename Tbyte>
+inline mpt::ustring encode_base64url(mpt::span<Tbyte> src_) {
+ mpt::const_byte_span src = mpt::byte_cast<mpt::const_byte_span>(src_);
+ mpt::ustring result;
+ result.reserve(4 * ((src.size() + 2) / 3));
+ uint32 bits = 0;
+ std::size_t bytes = 0;
+ for (std::byte byte : src) {
+ bits <<= 8;
+ bits |= mpt::byte_cast<uint8>(byte);
+ bytes++;
+ if (bytes == 3) {
+ result.push_back(base64url[(bits >> 18) & 0x3f]);
+ result.push_back(base64url[(bits >> 12) & 0x3f]);
+ result.push_back(base64url[(bits >> 6) & 0x3f]);
+ result.push_back(base64url[(bits >> 0) & 0x3f]);
+ bits = 0;
+ bytes = 0;
+ }
+ }
+ std::size_t padding = 0;
+ while (bytes != 0) {
+ bits <<= 8;
+ padding++;
+ bytes++;
+ if (bytes == 3) {
+ result.push_back(base64url[(bits >> 18) & 0x3f]);
+ result.push_back(base64url[(bits >> 12) & 0x3f]);
+ if (padding <= 1) {
+ result.push_back(base64url[(bits >> 6) & 0x3f]);
+ }
+ if (padding <= 0) {
+ result.push_back(base64url[(bits >> 0) & 0x3f]);
+ }
+ bits = 0;
+ bytes = 0;
+ }
+ }
+ return result;
+}
+
+inline uint8 decode_base64url_bits(mpt::uchar c) {
+ for (uint8 i = 0; i < 64; ++i)
+ {
+ if (base64url[i] == c)
+ {
+ return i;
+ }
+ }
+ throw base64url_parse_error();
+}
+
+inline std::vector<std::byte> decode_base64url(const mpt::ustring & src) {
+ std::vector<std::byte> result;
+ result.reserve(3 * ((src.length() + 2) / 4));
+ uint32 bits = 0;
+ std::size_t chars = 0;
+ for (mpt::uchar c : src) {
+ bits <<= 6;
+ bits |= decode_base64url_bits(c);
+ chars++;
+ if (chars == 4) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 16) & 0xff)));
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 8) & 0xff)));
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 0) & 0xff)));
+ bits = 0;
+ chars = 0;
+ }
+ }
+ uint32 padding = 0;
+ if (chars != 0 && chars < 2) {
+ throw base64url_parse_error();
+ }
+ while (chars != 0) {
+ bits <<= 6;
+ padding++;
+ chars++;
+ if (chars == 4) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 16) & 0xff)));
+ if (padding < 2) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 8) & 0xff)));
+ }
+ if (padding < 1) {
+ result.push_back(mpt::byte_cast<std::byte>(static_cast<uint8>((bits >> 0) & 0xff)));
+ }
+ bits = 0;
+ chars = 0;
+ padding = 0;
+ }
+ }
+ return result;
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BINARY_BASE64URL_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/binary/hex.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/hex.hpp
new file mode 100644
index 00000000..09a518b4
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/hex.hpp
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BINARY_HEX_HPP
+#define MPT_BINARY_HEX_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+
+#include <array>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+inline constexpr std::array<mpt::uchar, 16> encode_nibble = {
+ {MPT_UCHAR('0'), MPT_UCHAR('1'), MPT_UCHAR('2'), MPT_UCHAR('3'),
+ MPT_UCHAR('4'), MPT_UCHAR('5'), MPT_UCHAR('6'), MPT_UCHAR('7'),
+ MPT_UCHAR('8'), MPT_UCHAR('9'), MPT_UCHAR('A'), MPT_UCHAR('B'),
+ MPT_UCHAR('C'), MPT_UCHAR('D'), MPT_UCHAR('E'), MPT_UCHAR('F')}
+};
+
+
+inline bool decode_byte(uint8 & byte, mpt::uchar c1, mpt::uchar c2) {
+ byte = 0;
+ if (MPT_UCHAR('0') <= c1 && c1 <= MPT_UCHAR('9')) {
+ byte += static_cast<uint8>((c1 - MPT_UCHAR('0')) << 4);
+ } else if (MPT_UCHAR('A') <= c1 && c1 <= MPT_UCHAR('F')) {
+ byte += static_cast<uint8>((c1 - MPT_UCHAR('A') + 10) << 4);
+ } else if (MPT_UCHAR('a') <= c1 && c1 <= MPT_UCHAR('f')) {
+ byte += static_cast<uint8>((c1 - MPT_UCHAR('a') + 10) << 4);
+ } else {
+ return false;
+ }
+ if (MPT_UCHAR('0') <= c2 && c2 <= MPT_UCHAR('9')) {
+ byte += static_cast<uint8>(c2 - MPT_UCHAR('0'));
+ } else if (MPT_UCHAR('A') <= c2 && c2 <= MPT_UCHAR('F')) {
+ byte += static_cast<uint8>(c2 - MPT_UCHAR('A') + 10);
+ } else if (MPT_UCHAR('a') <= c2 && c2 <= MPT_UCHAR('f')) {
+ byte += static_cast<uint8>(c2 - MPT_UCHAR('a') + 10);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+
+template <typename Tbyte>
+inline mpt::ustring encode_hex(mpt::span<Tbyte> src_) {
+ mpt::const_byte_span src = mpt::byte_cast<mpt::const_byte_span>(src_);
+ mpt::ustring result;
+ result.reserve(src.size() * 2);
+ for (std::byte byte : src) {
+ result.push_back(encode_nibble[(mpt::byte_cast<uint8>(byte) & 0xf0) >> 4]);
+ result.push_back(encode_nibble[mpt::byte_cast<uint8>(byte) & 0x0f]);
+ }
+ return result;
+}
+
+inline std::vector<std::byte> decode_hex(const mpt::ustring & src) {
+ std::vector<std::byte> result;
+ result.reserve(src.size() / 2);
+ for (std::size_t i = 0; (i + 1) < src.size(); i += 2) {
+ uint8 byte = 0;
+ if (!decode_byte(byte, src[i], src[i + 1])) {
+ return result;
+ }
+ result.push_back(mpt::byte_cast<std::byte>(byte));
+ }
+ return result;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BINARY_HEX_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/binary/tests/tests_binary.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/tests/tests_binary.hpp
new file mode 100644
index 00000000..daf9c979
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/binary/tests/tests_binary.hpp
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_BINARY_HPP
+#define MPT_BASE_TESTS_BINARY_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/binary/base64.hpp"
+#include "mpt/binary/base64url.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace binary {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/binary")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ {
+ std::string expecteds = std::string("pleasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64(mpt::as_span(expected)), MPT_USTRING("cGxlYXN1cmUu"));
+ }
+ {
+ std::string expecteds = std::string("leasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64(mpt::as_span(expected)), MPT_USTRING("bGVhc3VyZS4="));
+ }
+ {
+ std::string expecteds = std::string("easure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64(mpt::as_span(expected)), MPT_USTRING("ZWFzdXJlLg=="));
+ }
+ {
+ std::string expecteds = std::string("pleasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64(MPT_USTRING("cGxlYXN1cmUu")));
+ }
+ {
+ std::string expecteds = std::string("leasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64(MPT_USTRING("bGVhc3VyZS4=")));
+ }
+ {
+ std::string expecteds = std::string("easure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64(MPT_USTRING("ZWFzdXJlLg==")));
+ }
+
+ {
+ std::string expecteds = std::string("pleasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64url(mpt::as_span(expected)), MPT_USTRING("cGxlYXN1cmUu"));
+ }
+ {
+ std::string expecteds = std::string("leasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64url(mpt::as_span(expected)), MPT_USTRING("bGVhc3VyZS4"));
+ }
+ {
+ std::string expecteds = std::string("easure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(mpt::encode_base64url(mpt::as_span(expected)), MPT_USTRING("ZWFzdXJlLg"));
+ }
+ {
+ std::string expecteds = std::string("pleasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64url(MPT_USTRING("cGxlYXN1cmUu")));
+ }
+ {
+ std::string expecteds = std::string("leasure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64url(MPT_USTRING("bGVhc3VyZS4")));
+ }
+ {
+ std::string expecteds = std::string("easure.");
+ std::vector<std::byte> expected(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data(), mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).data() + mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(expecteds)).size());
+ MPT_TEST_EXPECT_EQUAL(expected, mpt::decode_base64url(MPT_USTRING("ZWFzdXJlLg")));
+ }
+}
+
+} // namespace binary
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_BINARY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/check/libc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/check/libc.hpp
new file mode 100644
index 00000000..89a97834
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/check/libc.hpp
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CHECK_LIBC_HPP
+#define MPT_CHECK_LIBC_HPP
+
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/compiletime_warning.hpp"
+
+#ifndef __STDC_CONSTANT_MACROS
+#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_STDC_CONSTANT_MACROS
+MPT_WARNING("C stdlib does not provide constant macros. Please #define __STDC_CONSTANT_MACROS.")
+#endif
+#endif
+
+#ifndef __STDC_FORMAT_MACROS
+#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_STDC_FORMAT_MACROS
+MPT_WARNING("C stdlib does not provide limit macros. Please #define __STDC_FORMAT_MACROS.")
+#endif
+#endif
+
+#ifndef __STDC_LIMIT_MACROS
+#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_STDC_LIMIT_MACROS
+MPT_WARNING("C stdlib does not provide limit macros. Please #define __STDC_LIMIT_MACROS.")
+#endif
+#endif
+
+#ifndef _USE_MATH_DEFINES
+#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_USE_MATH_DEFINES
+MPT_WARNING("C stdlib does not provide math constants. Please #define _USE_MATH_DEFINES.")
+#endif
+#endif
+
+#if MPT_LIBC_GLIBC
+#if !defined(_FILE_OFFSET_BITS)
+#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_FILE_OFFSET_BITS
+MPT_WARNING("C stdlib may not provide 64bit std::FILE access. Please #define _FILE_OFFSET_BITS=64.")
+#endif
+#endif
+#endif
+
+#endif // MPT_CHECK_LIBC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/check/mfc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/check/mfc.hpp
new file mode 100644
index 00000000..a0f7014d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/check/mfc.hpp
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CHECK_MFC_HPP
+#define MPT_CHECK_MFC_HPP
+
+#include "mpt/base/compiletime_warning.hpp"
+#include "mpt/detect/mfc.hpp"
+
+#if MPT_DETECTED_MFC
+
+#ifndef _CSTRING_DISABLE_NARROW_WIDE_CONVERSION
+#ifndef MPT_CHECK_MFC_IGNORE_WARNING_NO_CSTRING_DISABLE_NARROW_WIDE_CONVERSION
+MPT_WARNING("MFC uses CString with automatic encoding conversions. Please #define _CSTRING_DISABLE_NARROW_WIDE_CONVERSION.")
+#endif
+#endif
+
+#endif // MPT_DETECTED_MFC
+
+#endif // MPT_CHECK_MFC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/check/windows.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/check/windows.hpp
new file mode 100644
index 00000000..b6a05f92
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/check/windows.hpp
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CHECK_WINDOWS_HPP
+#define MPT_CHECK_WINDOWS_HPP
+
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/compiletime_warning.hpp"
+
+#if MPT_OS_WINDOWS
+
+#ifndef UNICODE
+#ifndef MPT_CHECK_WINDOWS_IGNORE_WARNING_NO_UNICODE
+MPT_WARNING("windows.h uses MBCS TCHAR. Please #define UNICODE.")
+#endif
+#endif
+
+#ifndef NOMINMAX
+#ifndef MPT_CHECK_WINDOWS_IGNORE_WARNING_NO_NOMINMAX
+MPT_WARNING("windows.h defines min and max which conflicts with C++. Please #define NOMINMAX.")
+#endif
+#endif
+
+#endif // MPT_OS_WINDOWS
+
+#endif // MPT_CHECK_WINDOWS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crc/crc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crc/crc.hpp
new file mode 100644
index 00000000..5d6e851f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crc/crc.hpp
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CRC_CRC_HPP
+#define MPT_CRC_CRC_HPP
+
+
+
+#include "mpt/base/array.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+
+#include <array>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename T, T polynomial, T initial, T resultXOR, bool reverseData>
+class crc {
+
+public:
+ using self_type = crc;
+ using value_type = T;
+ using byte_type = uint8;
+
+ static constexpr std::size_t size_bytes = sizeof(value_type);
+ static constexpr std::size_t size_bits = sizeof(value_type) * 8;
+ static constexpr value_type top_bit = static_cast<value_type>(1) << ((sizeof(value_type) * 8) - 1);
+
+private:
+ template <typename Tint>
+ static constexpr Tint reverse(Tint value) noexcept {
+ const std::size_t bits = sizeof(Tint) * 8;
+ Tint result = 0;
+ for (std::size_t i = 0; i < bits; ++i) {
+ result <<= 1;
+ result |= static_cast<Tint>(value & 0x1);
+ value >>= 1;
+ }
+ return result;
+ }
+
+ static constexpr value_type calculate_table_entry(byte_type pos) noexcept {
+ value_type value = 0;
+ value = (static_cast<value_type>(reverseData ? reverse(pos) : pos) << (size_bits - 8));
+ for (std::size_t bit = 0; bit < 8; ++bit) {
+ if (value & top_bit) {
+ value = (value << 1) ^ polynomial;
+ } else {
+ value = (value << 1);
+ }
+ }
+ value = (reverseData ? reverse(value) : value);
+ return value;
+ }
+
+private:
+ static constexpr std::array<value_type, 256> calculate_table() noexcept {
+ std::array<value_type, 256> t = mpt::init_array<value_type, 256>(value_type{});
+ for (std::size_t i = 0; i < 256; ++i) {
+ t[i] = calculate_table_entry(static_cast<byte_type>(i));
+ }
+ return t;
+ }
+
+ static constexpr std::array<value_type, 256> table = calculate_table();
+
+private:
+ constexpr value_type read_table(byte_type pos) const noexcept {
+ return table[pos];
+ }
+
+private:
+ value_type value;
+
+public:
+ constexpr crc() noexcept
+ : value(initial) {
+ return;
+ }
+
+ constexpr void processByte(byte_type byte) noexcept {
+ if constexpr (reverseData) {
+ value = (value >> 8) ^ read_table(static_cast<byte_type>((value & 0xff) ^ byte));
+ } else {
+ value = (value << 8) ^ read_table(static_cast<byte_type>(((value >> (size_bits - 8)) & 0xff) ^ byte));
+ }
+ }
+
+ constexpr value_type result() const noexcept {
+ return (value ^ resultXOR);
+ }
+
+public:
+ constexpr operator value_type() const noexcept {
+ return result();
+ }
+
+ inline crc & process(char c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & process(signed char c) noexcept {
+ processByte(static_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & process(unsigned char c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & process(std::byte c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ template <typename InputIt>
+ inline crc & process(InputIt beg, InputIt end) {
+ for (InputIt it = beg; it != end; ++it) {
+ static_assert(sizeof(*it) == 1, "1 byte type required");
+ process(*it);
+ }
+ return *this;
+ }
+
+ template <typename Container>
+ inline crc & process(const Container & data) {
+ operator()(data.begin(), data.end());
+ return *this;
+ }
+
+ inline crc & operator()(char c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & operator()(signed char c) noexcept {
+ processByte(static_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & operator()(unsigned char c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ inline crc & operator()(std::byte c) noexcept {
+ processByte(mpt::byte_cast<byte_type>(c));
+ return *this;
+ }
+
+ template <typename InputIt>
+ crc & operator()(InputIt beg, InputIt end) {
+ for (InputIt it = beg; it != end; ++it) {
+ static_assert(sizeof(*it) == 1, "1 byte type required");
+ operator()(*it);
+ }
+ return *this;
+ }
+
+ template <typename Container>
+ inline crc & operator()(const Container & data) {
+ operator()(data.begin(), data.end());
+ return *this;
+ }
+
+ template <typename InputIt>
+ crc(InputIt beg, InputIt end)
+ : value(initial) {
+ for (InputIt it = beg; it != end; ++it) {
+ static_assert(sizeof(*it) == 1, "1 byte type required");
+ process(*it);
+ }
+ }
+
+ template <typename Container>
+ inline crc(const Container & data)
+ : value(initial) {
+ process(data.begin(), data.end());
+ }
+};
+
+using crc16 = crc<uint16, 0x8005, 0, 0, true>;
+using crc32 = crc<uint32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, true>;
+using crc32_ogg = crc<uint32, 0x04C11DB7, 0, 0, false>;
+using crc32c = crc<uint32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true>;
+using crc64_jones = crc<uint64, 0xAD93D23594C935A9ull, 0xFFFFFFFFFFFFFFFFull, 0, true>;
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_CRC_CRC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crc/tests/tests_crc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crc/tests/tests_crc.hpp
new file mode 100644
index 00000000..c09616eb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crc/tests/tests_crc.hpp
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_CRC_HPP
+#define MPT_BASE_TESTS_CRC_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/crc/crc.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <string>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace crc {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/crc")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::crc32(std::string("123456789")), 0xCBF43926u);
+ MPT_TEST_EXPECT_EQUAL(mpt::crc32_ogg(std::string("123456789")), 0x89a1897fu);
+}
+
+} // namespace crc
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_CRC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/exception.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/exception.hpp
new file mode 100644
index 00000000..6e7de510
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/exception.hpp
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CRYPTO_EXCEPTION_HPP
+#define MPT_CRYPTO_EXCEPTION_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+
+#include <stdexcept>
+#include <string>
+
+#if MPT_OS_WINDOWS
+#include <windows.h> // must be before wincrypt.h for clang-cl
+#include <wincrypt.h> // must be before ncrypt.h
+#include <ncrypt.h>
+#endif // MPT_OS_WINDOWS
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+namespace crypto {
+
+
+
+#if MPT_OS_WINDOWS
+
+class exception
+ : public std::runtime_error {
+private:
+ NTSTATUS m_Status;
+
+public:
+ exception(NTSTATUS status)
+ : std::runtime_error(std::string("crypto error: NTSTATUS ") + mpt::format<std::string>::hex0<8>(static_cast<DWORD>(status)))
+ , m_Status(status) {
+ return;
+ }
+
+ exception(NTSTATUS status, const std::string & function)
+ : std::runtime_error(std::string("crypto error: ") + function + std::string(" NTSTATUS ") + mpt::format<std::string>::hex0<8>(static_cast<DWORD>(status)))
+ , m_Status(status) {
+ return;
+ }
+
+public:
+ NTSTATUS code() const noexcept {
+ return m_Status;
+ }
+};
+
+
+class security_exception
+ : public std::runtime_error {
+private:
+ SECURITY_STATUS m_Status;
+
+public:
+ security_exception(SECURITY_STATUS status)
+ : std::runtime_error(std::string("crypto error: SECURITY_STATUS ") + mpt::format<std::string>::hex0<8>(static_cast<DWORD>(status)))
+ , m_Status(status) {
+ return;
+ }
+
+ security_exception(SECURITY_STATUS status, const std::string & function)
+ : std::runtime_error(std::string("crypto error: ") + function + std::string(" SECURITY_STATUS ") + mpt::format<std::string>::hex0<8>(static_cast<DWORD>(status)))
+ , m_Status(status) {
+ return;
+ }
+
+public:
+ SECURITY_STATUS code() const noexcept {
+ return m_Status;
+ }
+};
+
+
+inline void CheckNTSTATUS(NTSTATUS status) {
+ if (status >= 0) {
+ return;
+ } else if (static_cast<DWORD>(status) == STATUS_NO_MEMORY) {
+ mpt::throw_out_of_memory();
+ } else {
+ throw exception(status);
+ }
+}
+
+
+inline void CheckNTSTATUS(NTSTATUS status, const std::string & function) {
+ if (status >= 0) {
+ return;
+ } else if (static_cast<DWORD>(status) == STATUS_NO_MEMORY) {
+ mpt::throw_out_of_memory();
+ } else {
+ throw exception(status, function);
+ }
+}
+
+
+inline void CheckSECURITY_STATUS(SECURITY_STATUS status) {
+ if (status == ERROR_SUCCESS) {
+ return;
+ } else if (status == NTE_NO_MEMORY) {
+ mpt::throw_out_of_memory();
+ } else {
+ throw security_exception(status);
+ }
+}
+
+
+inline void CheckSECURITY_STATUS(SECURITY_STATUS status, const std::string & function) {
+ if (status == ERROR_SUCCESS) {
+ return;
+ } else if (status == NTE_NO_MEMORY) {
+ mpt::throw_out_of_memory();
+ } else {
+ throw security_exception(status, function);
+ }
+}
+
+
+#endif // MPT_OS_WINDOWS
+
+
+
+} // namespace crypto
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_CRYPTO_EXCEPTION_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/hash.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/hash.hpp
new file mode 100644
index 00000000..e8b4d4cb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/hash.hpp
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CRYPTO_HASH_HPP
+#define MPT_CRYPTO_HASH_HPP
+
+
+
+#include "mpt/base/array.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/crypto/exception.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <windows.h> // must be before wincrypt.h for clang-cl
+#include <bcrypt.h>
+#endif // MPT_OS_WINDOWS
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+namespace crypto {
+
+
+
+#if MPT_OS_WINDOWS
+
+
+namespace hash {
+
+
+struct hash_traits_md5 {
+ static constexpr std::size_t output_bits = 128;
+ static constexpr std::size_t output_bytes = output_bits / 8;
+ static constexpr const wchar_t * bcrypt_name = BCRYPT_MD5_ALGORITHM;
+};
+
+struct hash_traits_sha1 {
+ static constexpr std::size_t output_bits = 160;
+ static constexpr std::size_t output_bytes = output_bits / 8;
+ static constexpr const wchar_t * bcrypt_name = BCRYPT_SHA1_ALGORITHM;
+};
+
+struct hash_traits_sha256 {
+ static constexpr std::size_t output_bits = 256;
+ static constexpr std::size_t output_bytes = output_bits / 8;
+ static constexpr const wchar_t * bcrypt_name = BCRYPT_SHA256_ALGORITHM;
+};
+
+struct hash_traits_sha512 {
+ static constexpr std::size_t output_bits = 512;
+ static constexpr std::size_t output_bytes = output_bits / 8;
+ static constexpr const wchar_t * bcrypt_name = BCRYPT_SHA512_ALGORITHM;
+};
+
+template <typename Traits>
+class hash_impl {
+
+public:
+ using traits = Traits;
+
+ using result_type = std::array<std::byte, traits::output_bytes>;
+
+private:
+ BCRYPT_ALG_HANDLE hAlg = NULL;
+ std::vector<BYTE> hashState;
+ std::vector<BYTE> hashResult;
+ BCRYPT_HASH_HANDLE hHash = NULL;
+
+private:
+ void init() {
+ CheckNTSTATUS(BCryptOpenAlgorithmProvider(&hAlg, traits::bcrypt_name, NULL, 0), "BCryptOpenAlgorithmProvider");
+ if (!hAlg) {
+ throw exception(0);
+ }
+ DWORD hashStateSize = 0;
+ DWORD hashStateSizeSize = 0;
+ CheckNTSTATUS(BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashStateSize, sizeof(DWORD), &hashStateSizeSize, 0), "BCryptGetProperty");
+ if (hashStateSizeSize != sizeof(DWORD)) {
+ throw exception(0);
+ }
+ if (hashStateSize <= 0) {
+ throw exception(0);
+ }
+ hashState.resize(hashStateSize);
+ DWORD hashResultSize = 0;
+ DWORD hashResultSizeSize = 0;
+ CheckNTSTATUS(BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&hashResultSize, sizeof(DWORD), &hashResultSizeSize, 0), "BCryptGetProperty");
+ if (hashResultSizeSize != sizeof(DWORD)) {
+ throw exception(0);
+ }
+ if (hashResultSize <= 0) {
+ throw exception(0);
+ }
+ if (hashResultSize != mpt::extent<result_type>()) {
+ throw exception(0);
+ }
+ hashResult.resize(hashResultSize);
+ CheckNTSTATUS(BCryptCreateHash(hAlg, &hHash, hashState.data(), hashStateSize, NULL, 0, 0), "BCryptCreateHash");
+ if (!hHash) {
+ throw exception(0);
+ }
+ }
+
+ void cleanup() {
+ if (hHash) {
+ BCryptDestroyHash(hHash);
+ hHash = NULL;
+ }
+ hashResult.resize(0);
+ hashResult.shrink_to_fit();
+ hashState.resize(0);
+ hashState.shrink_to_fit();
+ if (hAlg) {
+ BCryptCloseAlgorithmProvider(hAlg, 0);
+ hAlg = NULL;
+ }
+ }
+
+public:
+ hash_impl() {
+ try {
+ init();
+ } catch (...) {
+ cleanup();
+ throw;
+ }
+ }
+ hash_impl(const hash_impl &) = delete;
+ hash_impl & operator=(const hash_impl &) = delete;
+ ~hash_impl() {
+ cleanup();
+ }
+
+public:
+ hash_impl & process(mpt::const_byte_span data) {
+ CheckNTSTATUS(BCryptHashData(hHash, const_cast<UCHAR *>(mpt::byte_cast<const UCHAR *>(data.data())), mpt::saturate_cast<ULONG>(data.size()), 0), "BCryptHashData");
+ return *this;
+ }
+
+ result_type result() {
+ result_type res = mpt::init_array<std::byte, traits::output_bytes>(std::byte{0});
+ CheckNTSTATUS(BCryptFinishHash(hHash, hashResult.data(), mpt::saturate_cast<ULONG>(hashResult.size()), 0), "BCryptFinishHash");
+ assert(hashResult.size() == mpt::extent<result_type>());
+ std::transform(hashResult.begin(), hashResult.end(), res.begin(), [](BYTE b) { return mpt::as_byte(b); });
+ return res;
+ }
+};
+
+using MD5 = hash_impl<hash_traits_md5>;
+using SHA1 = hash_impl<hash_traits_sha1>;
+using SHA256 = hash_impl<hash_traits_sha256>;
+using SHA512 = hash_impl<hash_traits_sha512>;
+
+
+} // namespace hash
+
+
+#endif // MPT_OS_WINDOWS
+
+
+
+} // namespace crypto
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_CRYPTO_HASH_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/jwk.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/jwk.hpp
new file mode 100644
index 00000000..dd54cb04
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/jwk.hpp
@@ -0,0 +1,531 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_CRYPTO_JWK_HPP
+#define MPT_CRYPTO_JWK_HPP
+
+
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/binary/base64url.hpp"
+#include "mpt/crypto/exception.hpp"
+#include "mpt/crypto/hash.hpp"
+#include "mpt/detect/nlohmann_json.hpp"
+#include "mpt/json/json.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <algorithm>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#if MPT_OS_WINDOWS
+#include <windows.h> // must be before wincrypt.h for clang-cl
+#include <bcrypt.h>
+#include <wincrypt.h> // must be before ncrypt.h
+#include <ncrypt.h>
+#endif // MPT_OS_WINDOWS
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace crypto {
+
+
+
+#if MPT_OS_WINDOWS && MPT_DETECTED_NLOHMANN_JSON
+
+
+
+class keystore {
+public:
+ enum class domain {
+ system = 1,
+ user = 2,
+ };
+
+private:
+ NCRYPT_PROV_HANDLE hProv = NULL;
+ domain ProvDomain = domain::user;
+
+private:
+ void cleanup() {
+ if (hProv) {
+ NCryptFreeObject(hProv);
+ hProv = NULL;
+ }
+ }
+
+public:
+ keystore(domain d)
+ : ProvDomain(d) {
+ try {
+ CheckSECURITY_STATUS(NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0), "NCryptOpenStorageProvider");
+ } catch (...) {
+ cleanup();
+ throw;
+ }
+ }
+ ~keystore() {
+ return;
+ }
+ operator NCRYPT_PROV_HANDLE() {
+ return hProv;
+ }
+ keystore::domain store_domain() const {
+ return ProvDomain;
+ }
+};
+
+
+
+namespace asymmetric {
+
+
+
+class signature_verification_failed
+ : public std::runtime_error {
+public:
+ signature_verification_failed()
+ : std::runtime_error("Signature Verification failed.") {
+ return;
+ }
+};
+
+
+
+inline std::vector<mpt::ustring> jws_get_keynames(const mpt::ustring & jws_) {
+ std::vector<mpt::ustring> result;
+ nlohmann::json jws = nlohmann::json::parse(mpt::transcode<std::string>(mpt::common_encoding::utf8, jws_));
+ for (const auto & s : jws["signatures"]) {
+ result.push_back(s["header"]["kid"]);
+ }
+ return result;
+}
+
+
+
+struct RSASSA_PSS_SHA512_traits {
+ using hash_type = mpt::crypto::hash::SHA512;
+ static constexpr const char * jwk_alg = "PS512";
+};
+
+
+
+template <typename Traits = RSASSA_PSS_SHA512_traits, std::size_t keysize = 4096>
+class rsassa_pss {
+
+public:
+ using hash_type = typename Traits::hash_type;
+ static constexpr const char * jwk_alg = Traits::jwk_alg;
+
+ struct public_key_data {
+
+ mpt::ustring name;
+ uint32 length = 0;
+ std::vector<std::byte> public_exp;
+ std::vector<std::byte> modulus;
+
+ std::vector<std::byte> as_cng_blob() const {
+ BCRYPT_RSAKEY_BLOB rsakey_blob{};
+ rsakey_blob.Magic = BCRYPT_RSAPUBLIC_MAGIC;
+ rsakey_blob.BitLength = length;
+ rsakey_blob.cbPublicExp = mpt::saturate_cast<ULONG>(public_exp.size());
+ rsakey_blob.cbModulus = mpt::saturate_cast<ULONG>(modulus.size());
+ std::vector<std::byte> result(sizeof(BCRYPT_RSAKEY_BLOB) + public_exp.size() + modulus.size());
+ std::memcpy(result.data(), &rsakey_blob, sizeof(BCRYPT_RSAKEY_BLOB));
+ std::memcpy(result.data() + sizeof(BCRYPT_RSAKEY_BLOB), public_exp.data(), public_exp.size());
+ std::memcpy(result.data() + sizeof(BCRYPT_RSAKEY_BLOB) + public_exp.size(), modulus.data(), modulus.size());
+ return result;
+ }
+
+ mpt::ustring as_jwk() const {
+ nlohmann::json json = nlohmann::json::object();
+ json["kid"] = name;
+ json["kty"] = "RSA";
+ json["alg"] = jwk_alg;
+ json["use"] = "sig";
+ json["e"] = mpt::encode_base64url(mpt::as_span(public_exp));
+ json["n"] = mpt::encode_base64url(mpt::as_span(modulus));
+ return mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, json.dump());
+ }
+
+ static public_key_data from_jwk(const mpt::ustring & jwk) {
+ public_key_data result;
+ try {
+ nlohmann::json json = nlohmann::json::parse(mpt::transcode<std::string>(mpt::common_encoding::utf8, jwk));
+ if (json["kty"] != "RSA") {
+ throw std::runtime_error("Cannot parse RSA public key JWK.");
+ }
+ if (json["alg"] != jwk_alg) {
+ throw std::runtime_error("Cannot parse RSA public key JWK.");
+ }
+ if (json["use"] != "sig") {
+ throw std::runtime_error("Cannot parse RSA public key JWK.");
+ }
+ result.name = json["kid"].get<mpt::ustring>();
+ result.public_exp = mpt::decode_base64url(json["e"]);
+ result.modulus = mpt::decode_base64url(json["n"]);
+ result.length = mpt::saturate_cast<uint32>(result.modulus.size() * 8);
+ } catch (mpt::out_of_memory e) {
+ mpt::rethrow_out_of_memory(e);
+ } catch (...) {
+ throw std::runtime_error("Cannot parse RSA public key JWK.");
+ }
+ return result;
+ }
+
+ static public_key_data from_cng_blob(const mpt::ustring & name, const std::vector<std::byte> & blob) {
+ public_key_data result;
+ BCRYPT_RSAKEY_BLOB rsakey_blob{};
+ if (blob.size() < sizeof(BCRYPT_RSAKEY_BLOB)) {
+ throw std::runtime_error("Cannot parse RSA public key blob.");
+ }
+ std::memcpy(&rsakey_blob, blob.data(), sizeof(BCRYPT_RSAKEY_BLOB));
+ if (rsakey_blob.Magic != BCRYPT_RSAPUBLIC_MAGIC) {
+ throw std::runtime_error("Cannot parse RSA public key blob.");
+ }
+ if (blob.size() != sizeof(BCRYPT_RSAKEY_BLOB) + rsakey_blob.cbPublicExp + rsakey_blob.cbModulus) {
+ throw std::runtime_error("Cannot parse RSA public key blob.");
+ }
+ result.name = name;
+ result.length = rsakey_blob.BitLength;
+ result.public_exp = std::vector<std::byte>(blob.data() + sizeof(BCRYPT_RSAKEY_BLOB), blob.data() + sizeof(BCRYPT_RSAKEY_BLOB) + rsakey_blob.cbPublicExp);
+ result.modulus = std::vector<std::byte>(blob.data() + sizeof(BCRYPT_RSAKEY_BLOB) + rsakey_blob.cbPublicExp, blob.data() + sizeof(BCRYPT_RSAKEY_BLOB) + rsakey_blob.cbPublicExp + rsakey_blob.cbModulus);
+ return result;
+ }
+ };
+
+
+
+ static std::vector<public_key_data> parse_jwk_set(const mpt::ustring & jwk_set_) {
+ std::vector<public_key_data> result;
+ nlohmann::json jwk_set = nlohmann::json::parse(mpt::transcode<std::string>(mpt::common_encoding::utf8, jwk_set_));
+ for (const auto & k : jwk_set["keys"]) {
+ try {
+ result.push_back(public_key_data::from_jwk(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, k.dump())));
+ } catch (...) {
+ // nothing
+ }
+ }
+ return result;
+ }
+
+
+
+ class public_key {
+
+ private:
+ mpt::ustring name;
+ BCRYPT_ALG_HANDLE hSignAlg = NULL;
+ BCRYPT_KEY_HANDLE hKey = NULL;
+
+ private:
+ void cleanup() {
+ if (hKey) {
+ BCryptDestroyKey(hKey);
+ hKey = NULL;
+ }
+ if (hSignAlg) {
+ BCryptCloseAlgorithmProvider(hSignAlg, 0);
+ hSignAlg = NULL;
+ }
+ }
+
+ public:
+ public_key(const public_key_data & data) {
+ try {
+ name = data.name;
+ CheckNTSTATUS(BCryptOpenAlgorithmProvider(&hSignAlg, BCRYPT_RSA_ALGORITHM, NULL, 0), "BCryptOpenAlgorithmProvider");
+ std::vector<std::byte> blob = data.as_cng_blob();
+ CheckNTSTATUS(BCryptImportKeyPair(hSignAlg, NULL, BCRYPT_RSAPUBLIC_BLOB, &hKey, mpt::byte_cast<UCHAR *>(blob.data()), mpt::saturate_cast<ULONG>(blob.size()), 0), "BCryptImportKeyPair");
+ } catch (...) {
+ cleanup();
+ throw;
+ }
+ }
+
+ public_key(const public_key & other)
+ : public_key(other.get_public_key_data()) {
+ return;
+ }
+
+ public_key & operator=(const public_key & other) {
+ if (&other == this) {
+ return *this;
+ }
+ public_key copy(other);
+ {
+ using std::swap;
+ swap(copy.name, name);
+ swap(copy.hSignAlg, hSignAlg);
+ swap(copy.hKey, hKey);
+ }
+ return *this;
+ }
+
+ ~public_key() {
+ cleanup();
+ }
+
+ mpt::ustring get_name() const {
+ return name;
+ }
+
+ public_key_data get_public_key_data() const {
+ DWORD bytes = 0;
+ CheckNTSTATUS(BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, 0, &bytes, 0), "BCryptExportKey");
+ std::vector<std::byte> blob(bytes);
+ CheckNTSTATUS(BCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, mpt::byte_cast<BYTE *>(blob.data()), mpt::saturate_cast<DWORD>(blob.size()), &bytes, 0), "BCryptExportKey");
+ return public_key_data::from_cng_blob(name, blob);
+ }
+
+ void verify_hash(typename hash_type::result_type hash, std::vector<std::byte> signature) {
+ BCRYPT_PSS_PADDING_INFO paddinginfo;
+ paddinginfo.pszAlgId = hash_type::traits::bcrypt_name;
+ paddinginfo.cbSalt = mpt::saturate_cast<ULONG>(hash_type::traits::output_bytes);
+ NTSTATUS result = BCryptVerifySignature(hKey, &paddinginfo, mpt::byte_cast<UCHAR *>(hash.data()), mpt::saturate_cast<ULONG>(hash.size()), mpt::byte_cast<UCHAR *>(signature.data()), mpt::saturate_cast<ULONG>(signature.size()), BCRYPT_PAD_PSS);
+ if (result == 0x00000000 /*STATUS_SUCCESS*/) {
+ return;
+ }
+ if (result == 0xC000A000 /*STATUS_INVALID_SIGNATURE*/) {
+ throw signature_verification_failed();
+ }
+ CheckNTSTATUS(result, "BCryptVerifySignature");
+ throw signature_verification_failed();
+ }
+
+ void verify(mpt::const_byte_span payload, const std::vector<std::byte> & signature) {
+ verify_hash(hash_type().process(payload).result(), signature);
+ }
+
+ std::vector<std::byte> jws_verify(const mpt::ustring & jws_) {
+ nlohmann::json jws = nlohmann::json::parse(mpt::transcode<std::string>(mpt::common_encoding::utf8, jws_));
+ std::vector<std::byte> payload = mpt::decode_base64url(jws["payload"]);
+ nlohmann::json jsignature = nlohmann::json::object();
+ bool sigfound = false;
+ for (const auto & s : jws["signatures"]) {
+ if (s["header"]["kid"] == mpt::transcode<std::string>(mpt::common_encoding::utf8, name)) {
+ jsignature = s;
+ sigfound = true;
+ }
+ }
+ if (!sigfound) {
+ throw signature_verification_failed();
+ }
+ std::vector<std::byte> protectedheaderraw = mpt::decode_base64url(jsignature["protected"]);
+ std::vector<std::byte> signature = mpt::decode_base64url(jsignature["signature"]);
+ nlohmann::json header = nlohmann::json::parse(mpt::buffer_cast<std::string>(protectedheaderraw));
+ if (header["typ"] != "JWT") {
+ throw signature_verification_failed();
+ }
+ if (header["alg"] != jwk_alg) {
+ throw signature_verification_failed();
+ }
+ verify_hash(hash_type().process(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::encode_base64url(mpt::as_span(protectedheaderraw)) + MPT_USTRING(".") + mpt::encode_base64url(mpt::as_span(payload)))))).result(), signature);
+ return payload;
+ }
+
+ std::vector<std::byte> jws_compact_verify(const mpt::ustring & jws) {
+ std::vector<mpt::ustring> parts = mpt::split<mpt::ustring>(jws, MPT_USTRING("."));
+ if (parts.size() != 3) {
+ throw signature_verification_failed();
+ }
+ std::vector<std::byte> protectedheaderraw = mpt::decode_base64url(parts[0]);
+ std::vector<std::byte> payload = mpt::decode_base64url(parts[1]);
+ std::vector<std::byte> signature = mpt::decode_base64url(parts[2]);
+ nlohmann::json header = nlohmann::json::parse(mpt::buffer_cast<std::string>(protectedheaderraw));
+ if (header["typ"] != "JWT") {
+ throw signature_verification_failed();
+ }
+ if (header["alg"] != jwk_alg) {
+ throw signature_verification_failed();
+ }
+ verify_hash(hash_type().process(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::encode_base64url(mpt::as_span(protectedheaderraw)) + MPT_USTRING(".") + mpt::encode_base64url(mpt::as_span(payload)))))).result(), signature);
+ return payload;
+ }
+ };
+
+
+
+ static inline void jws_verify_at_least_one(std::vector<public_key> & keys, const std::vector<std::byte> & expectedPayload, const mpt::ustring & signature) {
+ std::vector<mpt::ustring> keynames = mpt::crypto::asymmetric::jws_get_keynames(signature);
+ bool sigchecked = false;
+ for (const auto & keyname : keynames) {
+ for (auto & key : keys) {
+ if (key.get_name() == keyname) {
+ if (expectedPayload != key.jws_verify(signature)) {
+ throw mpt::crypto::asymmetric::signature_verification_failed();
+ }
+ sigchecked = true;
+ }
+ }
+ }
+ if (!sigchecked) {
+ throw mpt::crypto::asymmetric::signature_verification_failed();
+ }
+ }
+
+
+
+ static inline std::vector<std::byte> jws_verify_at_least_one(std::vector<public_key> & keys, const mpt::ustring & signature) {
+ std::vector<mpt::ustring> keynames = mpt::crypto::asymmetric::jws_get_keynames(signature);
+ for (const auto & keyname : keynames) {
+ for (auto & key : keys) {
+ if (key.get_name() == keyname) {
+ return key.jws_verify(signature);
+ }
+ }
+ }
+ throw mpt::crypto::asymmetric::signature_verification_failed();
+ }
+
+
+
+ class managed_private_key {
+
+ private:
+ mpt::ustring name;
+ NCRYPT_KEY_HANDLE hKey = NULL;
+
+ private:
+ void cleanup() {
+ if (hKey) {
+ NCryptFreeObject(hKey);
+ hKey = NULL;
+ }
+ }
+
+ public:
+ managed_private_key() = delete;
+
+ managed_private_key(const managed_private_key &) = delete;
+
+ managed_private_key & operator=(const managed_private_key &) = delete;
+
+ managed_private_key(keystore & keystore) {
+ try {
+ CheckSECURITY_STATUS(NCryptCreatePersistedKey(keystore, &hKey, BCRYPT_RSA_ALGORITHM, NULL, 0, 0), "NCryptCreatePersistedKey");
+ } catch (...) {
+ cleanup();
+ throw;
+ }
+ }
+
+ managed_private_key(keystore & keystore, const mpt::ustring & name_)
+ : name(name_) {
+ try {
+ SECURITY_STATUS openKeyStatus = NCryptOpenKey(keystore, &hKey, mpt::transcode<std::wstring>(name).c_str(), 0, (keystore.store_domain() == keystore::domain::system ? NCRYPT_MACHINE_KEY_FLAG : 0));
+ if (openKeyStatus == NTE_BAD_KEYSET) {
+ CheckSECURITY_STATUS(NCryptCreatePersistedKey(keystore, &hKey, BCRYPT_RSA_ALGORITHM, mpt::transcode<std::wstring>(name).c_str(), 0, (keystore.store_domain() == keystore::domain::system ? NCRYPT_MACHINE_KEY_FLAG : 0)), "NCryptCreatePersistedKey");
+ DWORD length = mpt::saturate_cast<DWORD>(keysize);
+ CheckSECURITY_STATUS(NCryptSetProperty(hKey, NCRYPT_LENGTH_PROPERTY, (PBYTE)&length, mpt::saturate_cast<DWORD>(sizeof(DWORD)), 0), "NCryptSetProperty");
+ CheckSECURITY_STATUS(NCryptFinalizeKey(hKey, 0), "NCryptFinalizeKey");
+ } else {
+ CheckSECURITY_STATUS(openKeyStatus, "NCryptOpenKey");
+ }
+ } catch (...) {
+ cleanup();
+ throw;
+ }
+ }
+
+ ~managed_private_key() {
+ cleanup();
+ }
+
+ void destroy() {
+ CheckSECURITY_STATUS(NCryptDeleteKey(hKey, 0), "NCryptDeleteKey");
+ name = mpt::ustring();
+ hKey = NULL;
+ }
+
+ public:
+ public_key_data get_public_key_data() const {
+ DWORD bytes = 0;
+ CheckSECURITY_STATUS(NCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, NULL, 0, &bytes, 0), "NCryptExportKey");
+ std::vector<std::byte> blob(bytes);
+ CheckSECURITY_STATUS(NCryptExportKey(hKey, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, mpt::byte_cast<BYTE *>(blob.data()), mpt::saturate_cast<DWORD>(blob.size()), &bytes, 0), "NCryptExportKey");
+ return public_key_data::from_cng_blob(name, blob);
+ }
+
+ std::vector<std::byte> sign_hash(typename hash_type::result_type hash) {
+ BCRYPT_PSS_PADDING_INFO paddinginfo;
+ paddinginfo.pszAlgId = hash_type::traits::bcrypt_name;
+ paddinginfo.cbSalt = mpt::saturate_cast<ULONG>(hash_type::traits::output_bytes);
+ DWORD bytes = 0;
+ CheckSECURITY_STATUS(NCryptSignHash(hKey, &paddinginfo, mpt::byte_cast<BYTE *>(hash.data()), mpt::saturate_cast<DWORD>(hash.size()), NULL, 0, &bytes, BCRYPT_PAD_PSS), "NCryptSignHash");
+ std::vector<std::byte> result(bytes);
+ CheckSECURITY_STATUS(NCryptSignHash(hKey, &paddinginfo, mpt::byte_cast<BYTE *>(hash.data()), mpt::saturate_cast<DWORD>(hash.size()), mpt::byte_cast<BYTE *>(result.data()), mpt::saturate_cast<DWORD>(result.size()), &bytes, BCRYPT_PAD_PSS), "NCryptSignHash");
+ return result;
+ }
+
+ std::vector<std::byte> sign(mpt::const_byte_span payload) {
+ return sign_hash(hash_type().process(payload).result());
+ }
+
+ mpt::ustring jws_compact_sign(mpt::const_byte_span payload) {
+ nlohmann::json protectedheader = nlohmann::json::object();
+ protectedheader["typ"] = "JWT";
+ protectedheader["alg"] = jwk_alg;
+ std::string protectedheaderstring = protectedheader.dump();
+ std::vector<std::byte> signature = sign_hash(hash_type().process(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::encode_base64url(mpt::as_span(protectedheaderstring)) + MPT_USTRING(".") + mpt::encode_base64url(payload))))).result());
+ return mpt::encode_base64url(mpt::as_span(protectedheaderstring)) + MPT_USTRING(".") + mpt::encode_base64url(payload) + MPT_USTRING(".") + mpt::encode_base64url(mpt::as_span(signature));
+ }
+
+ mpt::ustring jws_sign(mpt::const_byte_span payload) {
+ nlohmann::json protectedheader = nlohmann::json::object();
+ protectedheader["typ"] = "JWT";
+ protectedheader["alg"] = jwk_alg;
+ std::string protectedheaderstring = protectedheader.dump();
+ nlohmann::json header = nlohmann::json::object();
+ header["kid"] = name;
+ std::vector<std::byte> signature = sign_hash(hash_type().process(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::encode_base64url(mpt::as_span(protectedheaderstring)) + MPT_USTRING(".") + mpt::encode_base64url(payload))))).result());
+ nlohmann::json jws = nlohmann::json::object();
+ jws["payload"] = mpt::encode_base64url(payload);
+ jws["signatures"] = nlohmann::json::array();
+ nlohmann::json jsignature = nlohmann::json::object();
+ jsignature["header"] = header;
+ jsignature["protected"] = mpt::encode_base64url(mpt::as_span(protectedheaderstring));
+ jsignature["signature"] = mpt::encode_base64url(mpt::as_span(signature));
+ jws["signatures"].push_back(jsignature);
+ return mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, jws.dump());
+ }
+ };
+
+}; // class rsassa_pss
+
+
+
+} // namespace asymmetric
+
+
+
+#endif // MPT_OS_WINDOWS && MPT_DETECTED_NLOHMANN_JSON
+
+
+
+} // namespace crypto
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_CRYPTO_JWK_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/tests/tests_crypto.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/tests/tests_crypto.hpp
new file mode 100644
index 00000000..97053777
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/crypto/tests/tests_crypto.hpp
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_CRYPTO_HPP
+#define MPT_BASE_TESTS_CRYPTO_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/crypto/exception.hpp"
+#include "mpt/crypto/hash.hpp"
+#include "mpt/crypto/jwk.hpp"
+#include "mpt/detect/nlohmann_json.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace crypto {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/crypto")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+#if MPT_OS_WINDOWS
+ mpt::crypto::hash::SHA512::result_type sha512_abc{
+ std::byte{0xdd}, std::byte{0xaf}, std::byte{0x35}, std::byte{0xa1}, std::byte{0x93}, std::byte{0x61}, std::byte{0x7a}, std::byte{0xba},
+ std::byte{0xcc}, std::byte{0x41}, std::byte{0x73}, std::byte{0x49}, std::byte{0xae}, std::byte{0x20}, std::byte{0x41}, std::byte{0x31},
+ std::byte{0x12}, std::byte{0xe6}, std::byte{0xfa}, std::byte{0x4e}, std::byte{0x89}, std::byte{0xa9}, std::byte{0x7e}, std::byte{0xa2},
+ std::byte{0x0a}, std::byte{0x9e}, std::byte{0xee}, std::byte{0xe6}, std::byte{0x4b}, std::byte{0x55}, std::byte{0xd3}, std::byte{0x9a},
+ std::byte{0x21}, std::byte{0x92}, std::byte{0x99}, std::byte{0x2a}, std::byte{0x27}, std::byte{0x4f}, std::byte{0xc1}, std::byte{0xa8},
+ std::byte{0x36}, std::byte{0xba}, std::byte{0x3c}, std::byte{0x23}, std::byte{0xa3}, std::byte{0xfe}, std::byte{0xeb}, std::byte{0xbd},
+ std::byte{0x45}, std::byte{0x4d}, std::byte{0x44}, std::byte{0x23}, std::byte{0x64}, std::byte{0x3c}, std::byte{0xe8}, std::byte{0x0e},
+ std::byte{0x2a}, std::byte{0x9a}, std::byte{0xc9}, std::byte{0x4f}, std::byte{0xa5}, std::byte{0x4c}, std::byte{0xa4}, std::byte{0x9f}};
+ MPT_TEST_EXPECT_EQUAL(mpt::crypto::hash::SHA512().process(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(std::string("abc")))).result(), sha512_abc);
+
+#endif // MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS && MPT_DETECTED_NLOHMANN_JSON
+
+ {
+
+ std::vector<std::byte> data = {std::byte{0x11}, std::byte{0x12}, std::byte{0x13}, std::byte{0x14}};
+
+ mpt::crypto::keystore keystore(mpt::crypto::keystore::domain::user);
+
+ mpt::crypto::asymmetric::rsassa_pss<>::managed_private_key key(keystore, U_("OpenMPT Test Key 1"));
+
+ auto publickeydata = key.get_public_key_data();
+
+ mpt::crypto::asymmetric::rsassa_pss<>::public_key pk{publickeydata};
+ mpt::crypto::asymmetric::rsassa_pss<>::public_key pk_copy{pk};
+ mpt::ustring jwk = publickeydata.as_jwk();
+
+ std::vector<std::byte> signature = key.sign(mpt::as_span(data));
+ mpt::ustring jws = key.jws_sign(mpt::as_span(data));
+ mpt::ustring jws_compact = key.jws_compact_sign(mpt::as_span(data));
+
+ try {
+ pk.verify(mpt::as_span(data), signature);
+ auto verifieddata1 = mpt::crypto::asymmetric::rsassa_pss<>::public_key(mpt::crypto::asymmetric::rsassa_pss<>::public_key_data::from_jwk(jwk)).jws_verify(jws);
+ auto verifieddata2 = mpt::crypto::asymmetric::rsassa_pss<>::public_key(mpt::crypto::asymmetric::rsassa_pss<>::public_key_data::from_jwk(jwk)).jws_compact_verify(jws_compact);
+ MPT_TEST_EXPECT_EQUAL(true, true);
+ MPT_TEST_EXPECT_EQUAL(data, verifieddata1);
+ MPT_TEST_EXPECT_EQUAL(data, verifieddata2);
+ } catch (const mpt::crypto::asymmetric::signature_verification_failed &) {
+ MPT_TEST_EXPECT_EQUAL(true, false);
+ }
+
+ key.destroy();
+ }
+
+#endif // MPT_OS_WINDOWS && MPT_DETECTED_NLOHMANN_JSON
+}
+
+
+} // namespace crypto
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_CRYPTO_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/detect/dl.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/dl.hpp
new file mode 100644
index 00000000..da24272e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/dl.hpp
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_DETECT_DL_HPP
+#define MPT_DETECT_DL_HPP
+
+#include "mpt/base/compiletime_warning.hpp"
+
+#if defined(MPT_WITH_DL)
+#if !defined(CPPCHECK)
+#if !__has_include(<dlfcn.h>)
+#error "MPT_WITH_DL defined but <dlfcn.h> not found."
+#endif
+#endif
+#define MPT_DETECTED_DL 1
+#else
+#if defined(CPPCHECK)
+#define MPT_DETECTED_DL 1
+#else
+#if __has_include(<dlfcn.h>)
+#define MPT_DETECTED_DL 1
+#else
+#define MPT_DETECTED_DL 0
+#endif
+#endif
+#endif
+
+#endif // MPT_DETECT_DL_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/detect/ltdl.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/ltdl.hpp
new file mode 100644
index 00000000..bdd1d986
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/ltdl.hpp
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_DETECT_LTDL_HPP
+#define MPT_DETECT_LTDL_HPP
+
+#include "mpt/base/compiletime_warning.hpp"
+
+#if defined(MPT_WITH_LTDL)
+#if !defined(CPPCHECK)
+#if !__has_include(<ltdl.h>)
+#error "MPT_WITH_LTDL defined but <ltdl.h> not found."
+#endif
+#endif
+#define MPT_DETECTED_LTDL 1
+#else
+#if defined(CPPCHECK)
+#define MPT_DETECTED_LTDL 1
+#else
+#if __has_include(<ltdl.h>)
+#define MPT_DETECTED_LTDL 1
+#else
+#define MPT_DETECTED_LTDL 0
+#endif
+#endif
+#endif
+
+#endif // MPT_DETECT_LTDL_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/detect/mfc.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/mfc.hpp
new file mode 100644
index 00000000..2cdfd149
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/mfc.hpp
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_DETECT_MFC_HPP
+#define MPT_DETECT_MFC_HPP
+
+#include "mpt/base/compiletime_warning.hpp"
+
+#if defined(MPT_WITH_MFC)
+#if !defined(CPPCHECK)
+#if !__has_include(<afx.h>)
+#error "MPT_WITH_MFC defined but <afx.h> not found."
+#endif
+#endif
+#if !MPT_COMPILER_GENERIC && !MPT_COMPILER_MSVC && !MPT_COMPILER_CLANG
+MPT_WARNING("Using MFC with unsupported compiler.")
+#endif
+#define MPT_DETECTED_MFC 1
+#else
+#define MPT_DETECTED_MFC 0
+#endif
+
+#endif // MPT_DETECT_MFC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/detect/nlohmann_json.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/nlohmann_json.hpp
new file mode 100644
index 00000000..299d6126
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/detect/nlohmann_json.hpp
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_DETECT_NLOHMANN_JSON_HPP
+#define MPT_DETECT_NLOHMANN_JSON_HPP
+
+#if defined(MPT_WITH_NLOHMANN_JSON)
+#if !defined(CPPCHECK)
+#if !__has_include(<nlohmann/json.hpp>)
+#error "MPT_WITH_NLOHMANN_JSON defined but <nlohmann/json.hpp> not found."
+#endif
+#endif
+#define MPT_DETECTED_NLOHMANN_JSON 1
+#else
+#if defined(CPPCHECK)
+#define MPT_DETECTED_NLOHMANN_JSON 1
+#else
+#if __has_include(<nlohmann/json.hpp>)
+#define MPT_DETECTED_NLOHMANN_JSON 1
+#else
+#define MPT_DETECTED_NLOHMANN_JSON 0
+#endif
+#endif
+#endif
+
+#endif // MPT_DETECT_NLOHMANN_JSON_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/endian/floatingpoint.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/floatingpoint.hpp
new file mode 100644
index 00000000..7eaff159
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/floatingpoint.hpp
@@ -0,0 +1,485 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENDIAN_FLOATINGPOINT_HPP
+#define MPT_ENDIAN_FLOATINGPOINT_HPP
+
+
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/floatingpoint.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <limits>
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+// 1.0f --> 0x3f800000u
+MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f) {
+ if constexpr (mpt::float_traits<float32>::is_ieee754_binary32ne) {
+ return mpt::bit_cast<uint32>(f);
+ } else {
+ int e = 0;
+ float m = std::frexp(f, &e);
+ if (e == 0 && std::fabs(m) == 0.0f) {
+ uint32 expo = 0u;
+ uint32 sign = std::signbit(m) ? 0x01u : 0x00u;
+ uint32 mant = 0u;
+ uint32 i = 0u;
+ i |= (mant << 0) & 0x007fffffu;
+ i |= (expo << 23) & 0x7f800000u;
+ i |= (sign << 31) & 0x80000000u;
+ return i;
+ } else {
+ uint32 expo = e + 127 - 1;
+ uint32 sign = std::signbit(m) ? 0x01u : 0x00u;
+ uint32 mant = static_cast<uint32>(std::fabs(std::ldexp(m, 24)));
+ uint32 i = 0u;
+ i |= (mant << 0) & 0x007fffffu;
+ i |= (expo << 23) & 0x7f800000u;
+ i |= (sign << 31) & 0x80000000u;
+ return i;
+ }
+ }
+}
+
+MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f) {
+ if constexpr (mpt::float_traits<float64>::is_ieee754_binary64ne) {
+ return mpt::bit_cast<uint64>(f);
+ } else {
+ int e = 0;
+ double m = std::frexp(f, &e);
+ if (e == 0 && std::fabs(m) == 0.0) {
+ uint64 expo = 0u;
+ uint64 sign = std::signbit(m) ? 0x01u : 0x00u;
+ uint64 mant = 0u;
+ uint64 i = 0u;
+ i |= (mant << 0) & 0x000fffffffffffffull;
+ i |= (expo << 52) & 0x7ff0000000000000ull;
+ i |= (sign << 63) & 0x8000000000000000ull;
+ return i;
+ } else {
+ uint64 expo = static_cast<int64>(e) + 1023 - 1;
+ uint64 sign = std::signbit(m) ? 0x01u : 0x00u;
+ uint64 mant = static_cast<uint64>(std::fabs(std::ldexp(m, 53)));
+ uint64 i = 0u;
+ i |= (mant << 0) & 0x000fffffffffffffull;
+ i |= (expo << 52) & 0x7ff0000000000000ull;
+ i |= (sign << 63) & 0x8000000000000000ull;
+ return i;
+ }
+ }
+}
+
+// 0x3f800000u --> 1.0f
+MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i) {
+ if constexpr (mpt::float_traits<float32>::is_ieee754_binary32ne) {
+ return mpt::bit_cast<float32>(i);
+ } else {
+ uint32 mant = (i & 0x007fffffu) >> 0;
+ uint32 expo = (i & 0x7f800000u) >> 23;
+ uint32 sign = (i & 0x80000000u) >> 31;
+ if (expo == 0) {
+ float m = sign ? -static_cast<float>(mant) : static_cast<float>(mant);
+ int e = static_cast<int>(expo) - 127 + 1 - 24;
+ float f = std::ldexp(m, e);
+ return static_cast<float32>(f);
+ } else {
+ mant |= 0x00800000u;
+ float m = sign ? -static_cast<float>(mant) : static_cast<float>(mant);
+ int e = static_cast<int>(expo) - 127 + 1 - 24;
+ float f = std::ldexp(m, e);
+ return static_cast<float32>(f);
+ }
+ }
+}
+
+MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i) {
+ if constexpr (mpt::float_traits<float64>::is_ieee754_binary64ne) {
+ return mpt::bit_cast<float64>(i);
+ } else {
+ uint64 mant = (i & 0x000fffffffffffffull) >> 0;
+ uint64 expo = (i & 0x7ff0000000000000ull) >> 52;
+ uint64 sign = (i & 0x8000000000000000ull) >> 63;
+ if (expo == 0) {
+ double m = sign ? -static_cast<double>(mant) : static_cast<double>(mant);
+ int e = static_cast<int>(expo) - 1023 + 1 - 53;
+ double f = std::ldexp(m, e);
+ return static_cast<float64>(f);
+ } else {
+ mant |= 0x0010000000000000ull;
+ double m = sign ? -static_cast<double>(mant) : static_cast<double>(mant);
+ int e = static_cast<int>(expo) - 1023 + 1 - 53;
+ double f = std::ldexp(m, e);
+ return static_cast<float64>(f);
+ }
+ }
+}
+
+
+// template parameters are byte indices corresponding to the individual bytes of iee754 in memory
+template <std::size_t hihi, std::size_t hilo, std::size_t lohi, std::size_t lolo>
+struct IEEE754binary32Emulated {
+public:
+ using self_t = IEEE754binary32Emulated<hihi, hilo, lohi, lolo>;
+ std::byte bytes[4];
+
+public:
+ MPT_FORCEINLINE std::byte GetByte(std::size_t i) const {
+ return bytes[i];
+ }
+ IEEE754binary32Emulated() = default;
+ MPT_FORCEINLINE explicit IEEE754binary32Emulated(float32 f) {
+ SetInt32(EncodeIEEE754binary32(f));
+ }
+ // b0...b3 are in memory order, i.e. depend on the endianness of this type
+ // little endian: (0x00,0x00,0x80,0x3f)
+ // big endian: (0x3f,0x80,0x00,0x00)
+ MPT_FORCEINLINE explicit IEEE754binary32Emulated(std::byte b0, std::byte b1, std::byte b2, std::byte b3) {
+ bytes[0] = b0;
+ bytes[1] = b1;
+ bytes[2] = b2;
+ bytes[3] = b3;
+ }
+ MPT_FORCEINLINE operator float32() const {
+ return DecodeIEEE754binary32(GetInt32());
+ }
+ MPT_FORCEINLINE self_t & SetInt32(uint32 i) {
+ bytes[hihi] = static_cast<std::byte>(i >> 24);
+ bytes[hilo] = static_cast<std::byte>(i >> 16);
+ bytes[lohi] = static_cast<std::byte>(i >> 8);
+ bytes[lolo] = static_cast<std::byte>(i >> 0);
+ return *this;
+ }
+ MPT_FORCEINLINE uint32 GetInt32() const {
+ return 0u
+ | (static_cast<uint32>(bytes[hihi]) << 24)
+ | (static_cast<uint32>(bytes[hilo]) << 16)
+ | (static_cast<uint32>(bytes[lohi]) << 8)
+ | (static_cast<uint32>(bytes[lolo]) << 0);
+ }
+ MPT_FORCEINLINE bool operator==(const self_t & cmp) const {
+ return true
+ && bytes[0] == cmp.bytes[0]
+ && bytes[1] == cmp.bytes[1]
+ && bytes[2] == cmp.bytes[2]
+ && bytes[3] == cmp.bytes[3];
+ }
+ MPT_FORCEINLINE bool operator!=(const self_t & cmp) const {
+ return !(*this == cmp);
+ }
+};
+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 {
+public:
+ using self_t = IEEE754binary64Emulated<hihihi, hihilo, hilohi, hilolo, lohihi, lohilo, lolohi, lololo>;
+ std::byte bytes[8];
+
+public:
+ MPT_FORCEINLINE std::byte GetByte(std::size_t i) const {
+ return bytes[i];
+ }
+ IEEE754binary64Emulated() = default;
+ MPT_FORCEINLINE explicit IEEE754binary64Emulated(float64 f) {
+ SetInt64(EncodeIEEE754binary64(f));
+ }
+ MPT_FORCEINLINE explicit IEEE754binary64Emulated(std::byte b0, std::byte b1, std::byte b2, std::byte b3, std::byte b4, std::byte b5, std::byte b6, std::byte b7) {
+ bytes[0] = b0;
+ bytes[1] = b1;
+ bytes[2] = b2;
+ bytes[3] = b3;
+ bytes[4] = b4;
+ bytes[5] = b5;
+ bytes[6] = b6;
+ bytes[7] = b7;
+ }
+ MPT_FORCEINLINE operator float64() const {
+ return DecodeIEEE754binary64(GetInt64());
+ }
+ MPT_FORCEINLINE self_t & SetInt64(uint64 i) {
+ bytes[hihihi] = static_cast<std::byte>(i >> 56);
+ bytes[hihilo] = static_cast<std::byte>(i >> 48);
+ bytes[hilohi] = static_cast<std::byte>(i >> 40);
+ bytes[hilolo] = static_cast<std::byte>(i >> 32);
+ bytes[lohihi] = static_cast<std::byte>(i >> 24);
+ bytes[lohilo] = static_cast<std::byte>(i >> 16);
+ bytes[lolohi] = static_cast<std::byte>(i >> 8);
+ bytes[lololo] = static_cast<std::byte>(i >> 0);
+ return *this;
+ }
+ MPT_FORCEINLINE uint64 GetInt64() const {
+ return 0u
+ | (static_cast<uint64>(bytes[hihihi]) << 56)
+ | (static_cast<uint64>(bytes[hihilo]) << 48)
+ | (static_cast<uint64>(bytes[hilohi]) << 40)
+ | (static_cast<uint64>(bytes[hilolo]) << 32)
+ | (static_cast<uint64>(bytes[lohihi]) << 24)
+ | (static_cast<uint64>(bytes[lohilo]) << 16)
+ | (static_cast<uint64>(bytes[lolohi]) << 8)
+ | (static_cast<uint64>(bytes[lololo]) << 0);
+ }
+ MPT_FORCEINLINE bool operator==(const self_t & cmp) const {
+ return true
+ && bytes[0] == cmp.bytes[0]
+ && bytes[1] == cmp.bytes[1]
+ && bytes[2] == cmp.bytes[2]
+ && bytes[3] == cmp.bytes[3]
+ && bytes[4] == cmp.bytes[4]
+ && bytes[5] == cmp.bytes[5]
+ && bytes[6] == cmp.bytes[6]
+ && bytes[7] == cmp.bytes[7];
+ }
+ MPT_FORCEINLINE bool operator!=(const self_t & cmp) const {
+ return !(*this == cmp);
+ }
+};
+
+using IEEE754binary32EmulatedBE = IEEE754binary32Emulated<0, 1, 2, 3>;
+using IEEE754binary32EmulatedLE = IEEE754binary32Emulated<3, 2, 1, 0>;
+using IEEE754binary64EmulatedBE = IEEE754binary64Emulated<0, 1, 2, 3, 4, 5, 6, 7>;
+using IEEE754binary64EmulatedLE = IEEE754binary64Emulated<7, 6, 5, 4, 3, 2, 1, 0>;
+
+constexpr bool declare_binary_safe(const IEEE754binary32EmulatedBE &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const IEEE754binary32EmulatedLE &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const IEEE754binary64EmulatedBE &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const IEEE754binary64EmulatedLE &) {
+ return true;
+}
+
+static_assert(mpt::check_binary_size<IEEE754binary32EmulatedBE>(4));
+static_assert(mpt::check_binary_size<IEEE754binary32EmulatedLE>(4));
+static_assert(mpt::check_binary_size<IEEE754binary64EmulatedBE>(8));
+static_assert(mpt::check_binary_size<IEEE754binary64EmulatedLE>(8));
+
+template <mpt::endian endian = mpt::endian::native>
+struct IEEE754binary32Native {
+public:
+ float32 value;
+
+public:
+ MPT_FORCEINLINE std::byte GetByte(std::size_t i) const {
+ static_assert(endian == mpt::endian::little || endian == mpt::endian::big);
+ if constexpr (endian == mpt::endian::little) {
+ return static_cast<std::byte>(EncodeIEEE754binary32(value) >> (i * 8));
+ }
+ if constexpr (endian == mpt::endian::big) {
+ return static_cast<std::byte>(EncodeIEEE754binary32(value) >> ((4 - 1 - i) * 8));
+ }
+ }
+ IEEE754binary32Native() = default;
+ MPT_FORCEINLINE explicit IEEE754binary32Native(float32 f) {
+ value = f;
+ }
+ // b0...b3 are in memory order, i.e. depend on the endianness of this type
+ // little endian: (0x00,0x00,0x80,0x3f)
+ // big endian: (0x3f,0x80,0x00,0x00)
+ MPT_FORCEINLINE explicit IEEE754binary32Native(std::byte b0, std::byte b1, std::byte b2, std::byte b3) {
+ static_assert(endian == mpt::endian::little || endian == mpt::endian::big);
+ if constexpr (endian == mpt::endian::little) {
+ value = DecodeIEEE754binary32(0u | (static_cast<uint32>(b0) << 0) | (static_cast<uint32>(b1) << 8) | (static_cast<uint32>(b2) << 16) | (static_cast<uint32>(b3) << 24));
+ }
+ if constexpr (endian == mpt::endian::big) {
+ value = DecodeIEEE754binary32(0u | (static_cast<uint32>(b0) << 24) | (static_cast<uint32>(b1) << 16) | (static_cast<uint32>(b2) << 8) | (static_cast<uint32>(b3) << 0));
+ }
+ }
+ MPT_FORCEINLINE operator float32() const {
+ return value;
+ }
+ MPT_FORCEINLINE IEEE754binary32Native & SetInt32(uint32 i) {
+ value = DecodeIEEE754binary32(i);
+ return *this;
+ }
+ MPT_FORCEINLINE uint32 GetInt32() const {
+ return EncodeIEEE754binary32(value);
+ }
+ MPT_FORCEINLINE bool operator==(const IEEE754binary32Native & cmp) const {
+ return value == cmp.value;
+ }
+ MPT_FORCEINLINE bool operator!=(const IEEE754binary32Native & cmp) const {
+ return value != cmp.value;
+ }
+};
+
+template <mpt::endian endian = mpt::endian::native>
+struct IEEE754binary64Native {
+public:
+ float64 value;
+
+public:
+ MPT_FORCEINLINE std::byte GetByte(std::size_t i) const {
+ static_assert(endian == mpt::endian::little || endian == mpt::endian::big);
+ if constexpr (endian == mpt::endian::little) {
+ return mpt::byte_cast<std::byte>(static_cast<uint8>(EncodeIEEE754binary64(value) >> (i * 8)));
+ }
+ if constexpr (endian == mpt::endian::big) {
+ return mpt::byte_cast<std::byte>(static_cast<uint8>(EncodeIEEE754binary64(value) >> ((8 - 1 - i) * 8)));
+ }
+ }
+ IEEE754binary64Native() = default;
+ MPT_FORCEINLINE explicit IEEE754binary64Native(float64 f) {
+ value = f;
+ }
+ MPT_FORCEINLINE explicit IEEE754binary64Native(std::byte b0, std::byte b1, std::byte b2, std::byte b3, std::byte b4, std::byte b5, std::byte b6, std::byte b7) {
+ static_assert(endian == mpt::endian::little || endian == mpt::endian::big);
+ if constexpr (endian == mpt::endian::little) {
+ value = DecodeIEEE754binary64(0ull | (static_cast<uint64>(b0) << 0) | (static_cast<uint64>(b1) << 8) | (static_cast<uint64>(b2) << 16) | (static_cast<uint64>(b3) << 24) | (static_cast<uint64>(b4) << 32) | (static_cast<uint64>(b5) << 40) | (static_cast<uint64>(b6) << 48) | (static_cast<uint64>(b7) << 56));
+ }
+ if constexpr (endian == mpt::endian::big) {
+ value = DecodeIEEE754binary64(0ull | (static_cast<uint64>(b0) << 56) | (static_cast<uint64>(b1) << 48) | (static_cast<uint64>(b2) << 40) | (static_cast<uint64>(b3) << 32) | (static_cast<uint64>(b4) << 24) | (static_cast<uint64>(b5) << 16) | (static_cast<uint64>(b6) << 8) | (static_cast<uint64>(b7) << 0));
+ }
+ }
+ MPT_FORCEINLINE operator float64() const {
+ return value;
+ }
+ MPT_FORCEINLINE IEEE754binary64Native & SetInt64(uint64 i) {
+ value = DecodeIEEE754binary64(i);
+ return *this;
+ }
+ MPT_FORCEINLINE uint64 GetInt64() const {
+ return EncodeIEEE754binary64(value);
+ }
+ MPT_FORCEINLINE bool operator==(const IEEE754binary64Native & cmp) const {
+ return value == cmp.value;
+ }
+ MPT_FORCEINLINE bool operator!=(const IEEE754binary64Native & cmp) const {
+ return value != cmp.value;
+ }
+};
+
+static_assert((sizeof(IEEE754binary32Native<>) == 4));
+static_assert((sizeof(IEEE754binary64Native<>) == 8));
+
+constexpr bool declare_binary_safe(const IEEE754binary32Native<> &) noexcept {
+ return true;
+}
+constexpr bool declare_binary_safe(const IEEE754binary64Native<> &) noexcept {
+ return true;
+}
+
+template <bool is_ieee754, mpt::endian endian = mpt::endian::native>
+struct IEEE754binary_types {
+ using IEEE754binary32LE = IEEE754binary32EmulatedLE;
+ using IEEE754binary32BE = IEEE754binary32EmulatedBE;
+ using IEEE754binary64LE = IEEE754binary64EmulatedLE;
+ using IEEE754binary64BE = IEEE754binary64EmulatedBE;
+};
+template <>
+struct IEEE754binary_types<true, mpt::endian::little> {
+ using IEEE754binary32LE = IEEE754binary32Native<>;
+ using IEEE754binary32BE = IEEE754binary32EmulatedBE;
+ using IEEE754binary64LE = IEEE754binary64Native<>;
+ using IEEE754binary64BE = IEEE754binary64EmulatedBE;
+};
+template <>
+struct IEEE754binary_types<true, mpt::endian::big> {
+ using IEEE754binary32LE = IEEE754binary32EmulatedLE;
+ using IEEE754binary32BE = IEEE754binary32Native<>;
+ using IEEE754binary64LE = IEEE754binary64EmulatedLE;
+ using IEEE754binary64BE = IEEE754binary64Native<>;
+};
+
+using IEEE754binary32LE = IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32LE;
+using IEEE754binary32BE = IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32BE;
+using IEEE754binary64LE = IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64LE;
+using IEEE754binary64BE = IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64BE;
+
+static_assert(sizeof(IEEE754binary32LE) == 4);
+static_assert(sizeof(IEEE754binary32BE) == 4);
+static_assert(sizeof(IEEE754binary64LE) == 8);
+static_assert(sizeof(IEEE754binary64BE) == 8);
+
+
+// unaligned
+
+using float32le = IEEE754binary32EmulatedLE;
+using float32be = IEEE754binary32EmulatedBE;
+using float64le = IEEE754binary64EmulatedLE;
+using float64be = IEEE754binary64EmulatedBE;
+
+static_assert(sizeof(float32le) == 4);
+static_assert(sizeof(float32be) == 4);
+static_assert(sizeof(float64le) == 8);
+static_assert(sizeof(float64be) == 8);
+
+
+// potentially aligned
+
+using float32le_fast = IEEE754binary32LE;
+using float32be_fast = IEEE754binary32BE;
+using float64le_fast = IEEE754binary64LE;
+using float64be_fast = IEEE754binary64BE;
+
+static_assert(sizeof(float32le_fast) == 4);
+static_assert(sizeof(float32be_fast) == 4);
+static_assert(sizeof(float64le_fast) == 8);
+static_assert(sizeof(float64be_fast) == 8);
+
+
+
+template <typename Tfloat>
+struct make_float_be {
+};
+
+template <>
+struct make_float_be<double> {
+ using type = IEEE754binary64BE;
+};
+
+template <>
+struct make_float_be<float> {
+ using type = IEEE754binary32BE;
+};
+
+template <typename Tfloat>
+struct make_float_le {
+};
+
+template <>
+struct make_float_le<double> {
+ using type = IEEE754binary64LE;
+};
+
+template <>
+struct make_float_le<float> {
+ using type = IEEE754binary32LE;
+};
+
+template <mpt::endian endian, typename T>
+struct make_float_endian {
+};
+
+template <typename T>
+struct make_float_endian<mpt::endian::little, T> {
+ using type = typename make_float_le<typename std::remove_const<T>::type>::type;
+};
+
+template <typename T>
+struct make_float_endian<mpt::endian::big, T> {
+ using type = typename make_float_be<typename std::remove_const<T>::type>::type;
+};
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_ENDIAN_FLOATINGPOINT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/endian/int24.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/int24.hpp
new file mode 100644
index 00000000..cdab8617
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/int24.hpp
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENDIAN_INT24_HPP
+#define MPT_ENDIAN_INT24_HPP
+
+
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#include <limits>
+#include <type_traits>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+struct uint24 {
+ std::array<std::byte, 3> bytes;
+ uint24() = default;
+ template <typename T, typename std::enable_if<std::is_integral<T>::value, bool>::type = true>
+ explicit uint24(T other) noexcept {
+ using Tunsigned = typename std::make_unsigned<T>::type;
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_big()) {
+ bytes[0] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 16) & 0xff));
+ bytes[1] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 8) & 0xff));
+ bytes[2] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 0) & 0xff));
+ } else {
+ bytes[0] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 0) & 0xff));
+ bytes[1] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 8) & 0xff));
+ bytes[2] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 16) & 0xff));
+ }
+ }
+ operator int() const noexcept {
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_big()) {
+ return (mpt::byte_cast<uint8>(bytes[0]) * 65536) + (mpt::byte_cast<uint8>(bytes[1]) * 256) + mpt::byte_cast<uint8>(bytes[2]);
+ } else {
+ return (mpt::byte_cast<uint8>(bytes[2]) * 65536) + (mpt::byte_cast<uint8>(bytes[1]) * 256) + mpt::byte_cast<uint8>(bytes[0]);
+ }
+ }
+};
+
+static_assert(sizeof(uint24) == 3);
+
+
+struct int24 {
+ std::array<std::byte, 3> bytes;
+ int24() = default;
+ template <typename T, typename std::enable_if<std::is_integral<T>::value, bool>::type = true>
+ explicit int24(T other) noexcept {
+ using Tunsigned = typename std::make_unsigned<T>::type;
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_big()) {
+ bytes[0] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 16) & 0xff));
+ bytes[1] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 8) & 0xff));
+ bytes[2] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 0) & 0xff));
+ } else {
+ bytes[0] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 0) & 0xff));
+ bytes[1] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 8) & 0xff));
+ bytes[2] = mpt::byte_cast<std::byte>(static_cast<uint8>((static_cast<Tunsigned>(other) >> 16) & 0xff));
+ }
+ }
+ operator int() const noexcept {
+ MPT_MAYBE_CONSTANT_IF (mpt::endian_is_big()) {
+ return (static_cast<int8>(mpt::byte_cast<uint8>(bytes[0])) * 65536) + (mpt::byte_cast<uint8>(bytes[1]) * 256) + mpt::byte_cast<uint8>(bytes[2]);
+ } else {
+ return (static_cast<int8>(mpt::byte_cast<uint8>(bytes[2])) * 65536) + (mpt::byte_cast<uint8>(bytes[1]) * 256) + mpt::byte_cast<uint8>(bytes[0]);
+ }
+ }
+};
+
+static_assert(sizeof(int24) == 3);
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#if !defined(CPPCHECK)
+// work-around crash in cppcheck 2.4.1
+namespace std {
+template <>
+class numeric_limits<mpt::uint24> : public std::numeric_limits<mpt::uint32> {
+public:
+ static constexpr mpt::uint32 min() noexcept {
+ return 0;
+ }
+ static constexpr mpt::uint32 lowest() noexcept {
+ return 0;
+ }
+ static constexpr mpt::uint32 max() noexcept {
+ return 0x00ffffff;
+ }
+};
+template <>
+class numeric_limits<mpt::int24> : public std::numeric_limits<mpt::int32> {
+public:
+ static constexpr mpt::int32 min() noexcept {
+ return 0 - 0x00800000;
+ }
+ static constexpr mpt::int32 lowest() noexcept {
+ return 0 - 0x00800000;
+ }
+ static constexpr mpt::int32 max() noexcept {
+ return 0 + 0x007fffff;
+ }
+};
+} // namespace std
+#endif // !CPPCHECK
+
+
+
+#endif // MPT_ENDIAN_INT24_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/endian/integer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/integer.hpp
new file mode 100644
index 00000000..abc54b73
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/integer.hpp
@@ -0,0 +1,493 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENDIAN_INTEGER_HPP
+#define MPT_ENDIAN_INTEGER_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/bit.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#include <limits>
+#include <type_traits>
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#if MPT_COMPILER_MSVC
+#include <intrin.h>
+#endif
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+struct BigEndian_tag {
+ static constexpr mpt::endian endian = mpt::endian::big;
+};
+
+struct LittleEndian_tag {
+ static constexpr mpt::endian endian = mpt::endian::little;
+};
+
+
+
+constexpr inline uint16 constexpr_bswap16(uint16 x) noexcept {
+ return uint16(0)
+ | ((x >> 8) & 0x00FFu)
+ | ((x << 8) & 0xFF00u);
+}
+
+constexpr inline uint32 constexpr_bswap32(uint32 x) noexcept {
+ return uint32(0)
+ | ((x & 0x000000FFu) << 24)
+ | ((x & 0x0000FF00u) << 8)
+ | ((x & 0x00FF0000u) >> 8)
+ | ((x & 0xFF000000u) >> 24);
+}
+
+constexpr inline uint64 constexpr_bswap64(uint64 x) noexcept {
+ return uint64(0)
+ | (((x >> 0) & 0xffull) << 56)
+ | (((x >> 8) & 0xffull) << 48)
+ | (((x >> 16) & 0xffull) << 40)
+ | (((x >> 24) & 0xffull) << 32)
+ | (((x >> 32) & 0xffull) << 24)
+ | (((x >> 40) & 0xffull) << 16)
+ | (((x >> 48) & 0xffull) << 8)
+ | (((x >> 56) & 0xffull) << 0);
+}
+
+#if MPT_COMPILER_GCC
+#define MPT_bswap16 __builtin_bswap16
+#define MPT_bswap32 __builtin_bswap32
+#define MPT_bswap64 __builtin_bswap64
+#elif MPT_COMPILER_MSVC
+#define MPT_bswap16 _byteswap_ushort
+#define MPT_bswap32 _byteswap_ulong
+#define MPT_bswap64 _byteswap_uint64
+#endif
+
+// No intrinsics available
+#ifndef MPT_bswap16
+#define MPT_bswap16(x) mpt::constexpr_bswap16(x)
+#endif
+#ifndef MPT_bswap32
+#define MPT_bswap32(x) mpt::constexpr_bswap32(x)
+#endif
+#ifndef MPT_bswap64
+#define MPT_bswap64(x) mpt::constexpr_bswap64(x)
+#endif
+
+
+
+template <typename T, typename Tendian, std::size_t size>
+MPT_CONSTEXPRINLINE std::array<std::byte, size> EndianEncode(T val) noexcept {
+ static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big);
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(!std::numeric_limits<T>::is_signed);
+ static_assert(sizeof(T) == size);
+ using base_type = T;
+ using unsigned_base_type = typename std::make_unsigned<base_type>::type;
+ using endian_type = Tendian;
+ unsigned_base_type uval = static_cast<unsigned_base_type>(val);
+ std::array<std::byte, size> data{};
+ if constexpr (endian_type::endian == mpt::endian::little) {
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ data[i] = static_cast<std::byte>(static_cast<uint8>((uval >> (i * 8)) & 0xffu));
+ }
+ } else {
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ data[(sizeof(base_type) - 1) - i] = static_cast<std::byte>(static_cast<uint8>((uval >> (i * 8)) & 0xffu));
+ }
+ }
+ return data;
+}
+
+template <typename T, typename Tendian, std::size_t size>
+MPT_CONSTEXPRINLINE T EndianDecode(std::array<std::byte, size> data) noexcept {
+ static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big);
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(!std::numeric_limits<T>::is_signed);
+ static_assert(sizeof(T) == size);
+ using base_type = T;
+ using unsigned_base_type = typename std::make_unsigned<base_type>::type;
+ using endian_type = Tendian;
+ base_type val = base_type();
+ unsigned_base_type uval = unsigned_base_type();
+ if constexpr (endian_type::endian == mpt::endian::little) {
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ uval |= static_cast<unsigned_base_type>(static_cast<uint8>(data[i])) << (i * 8);
+ }
+ } else {
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ uval |= static_cast<unsigned_base_type>(static_cast<uint8>(data[(sizeof(base_type) - 1) - i])) << (i * 8);
+ }
+ }
+ val = static_cast<base_type>(uval);
+ return val;
+}
+
+
+MPT_CONSTEXPR20_FUN uint64 SwapBytesImpl(uint64 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap64(value);
+ } else {
+ return MPT_bswap64(value);
+ }
+}
+
+MPT_CONSTEXPR20_FUN uint32 SwapBytesImpl(uint32 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap32(value);
+ } else {
+ return MPT_bswap32(value);
+ }
+}
+
+MPT_CONSTEXPR20_FUN uint16 SwapBytesImpl(uint16 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap16(value);
+ } else {
+ return MPT_bswap16(value);
+ }
+}
+
+MPT_CONSTEXPR20_FUN int64 SwapBytesImpl(int64 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap64(value);
+ } else {
+ return MPT_bswap64(value);
+ }
+}
+
+MPT_CONSTEXPR20_FUN int32 SwapBytesImpl(int32 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap32(value);
+ } else {
+ return MPT_bswap32(value);
+ }
+}
+
+MPT_CONSTEXPR20_FUN int16 SwapBytesImpl(int16 value) noexcept {
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ return mpt::constexpr_bswap16(value);
+ } else {
+ return MPT_bswap16(value);
+ }
+}
+
+// Do NOT remove these overloads, even if they seem useless.
+// We do not want risking to extend 8bit integers to int and then
+// endian-converting and casting back to int.
+// Thus these overloads.
+
+MPT_CONSTEXPR20_FUN uint8 SwapBytesImpl(uint8 value) noexcept {
+ return value;
+}
+
+MPT_CONSTEXPR20_FUN int8 SwapBytesImpl(int8 value) noexcept {
+ return value;
+}
+
+MPT_CONSTEXPR20_FUN char SwapBytesImpl(char value) noexcept {
+ return value;
+}
+
+#undef MPT_bswap16
+#undef MPT_bswap32
+#undef MPT_bswap64
+
+
+
+// On-disk integer types with defined endianness and no alignemnt requirements
+// Note: To easily debug module loaders (and anything else that uses this
+// wrapper struct), you can use the Debugger Visualizers available in
+// build/vs/debug/ to conveniently view the wrapped contents.
+
+template <typename T, typename Tendian>
+struct packed {
+public:
+ using base_type = T;
+ using endian_type = Tendian;
+
+public:
+ std::array<std::byte, sizeof(base_type)> data;
+
+public:
+ MPT_CONSTEXPR20_FUN void set(base_type val) noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ if constexpr (endian_type::endian == mpt::endian::big) {
+ typename std::make_unsigned<base_type>::type uval = val;
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ data[i] = static_cast<std::byte>((uval >> (8 * (sizeof(base_type) - 1 - i))) & 0xffu);
+ }
+ } else {
+ typename std::make_unsigned<base_type>::type uval = val;
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ data[i] = static_cast<std::byte>((uval >> (8 * i)) & 0xffu);
+ }
+ }
+ } else {
+ if constexpr (mpt::endian::native == mpt::endian::little || mpt::endian::native == mpt::endian::big) {
+ if constexpr (mpt::endian::native != endian_type::endian) {
+ val = mpt::SwapBytesImpl(val);
+ }
+ std::memcpy(data.data(), &val, sizeof(val));
+ } else {
+ using unsigned_base_type = typename std::make_unsigned<base_type>::type;
+ data = EndianEncode<unsigned_base_type, Tendian, sizeof(T)>(val);
+ }
+ }
+ }
+ MPT_CONSTEXPR20_FUN base_type get() const noexcept {
+ static_assert(std::numeric_limits<T>::is_integer);
+ MPT_MAYBE_CONSTANT_IF (MPT_IS_CONSTANT_EVALUATED20()) {
+ if constexpr (endian_type::endian == mpt::endian::big) {
+ typename std::make_unsigned<base_type>::type uval = 0;
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ uval |= static_cast<typename std::make_unsigned<base_type>::type>(data[i]) << (8 * (sizeof(base_type) - 1 - i));
+ }
+ return static_cast<base_type>(uval);
+ } else {
+ typename std::make_unsigned<base_type>::type uval = 0;
+ for (std::size_t i = 0; i < sizeof(base_type); ++i) {
+ uval |= static_cast<typename std::make_unsigned<base_type>::type>(data[i]) << (8 * i);
+ }
+ return static_cast<base_type>(uval);
+ }
+ } else {
+ if constexpr (mpt::endian::native == mpt::endian::little || mpt::endian::native == mpt::endian::big) {
+ base_type val = base_type();
+ std::memcpy(&val, data.data(), sizeof(val));
+ if constexpr (mpt::endian::native != endian_type::endian) {
+ val = mpt::SwapBytesImpl(val);
+ }
+ return val;
+ } else {
+ using unsigned_base_type = typename std::make_unsigned<base_type>::type;
+ return EndianDecode<unsigned_base_type, Tendian, sizeof(T)>(data);
+ }
+ }
+ }
+ MPT_CONSTEXPR20_FUN packed & operator=(const base_type & val) noexcept {
+ set(val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN operator base_type() const noexcept {
+ return get();
+ }
+
+public:
+ MPT_CONSTEXPR20_FUN packed & operator&=(base_type val) noexcept {
+ set(get() & val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator|=(base_type val) noexcept {
+ set(get() | val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator^=(base_type val) noexcept {
+ set(get() ^ val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator+=(base_type val) noexcept {
+ set(get() + val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator-=(base_type val) noexcept {
+ set(get() - val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator*=(base_type val) noexcept {
+ set(get() * val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator/=(base_type val) noexcept {
+ set(get() / val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator%=(base_type val) noexcept {
+ set(get() % val);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator++() noexcept { // prefix
+ set(get() + 1);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN packed & operator--() noexcept { // prefix
+ set(get() - 1);
+ return *this;
+ }
+ MPT_CONSTEXPR20_FUN base_type operator++(int) noexcept { // postfix
+ base_type old = get();
+ set(old + 1);
+ return old;
+ }
+ MPT_CONSTEXPR20_FUN base_type operator--(int) noexcept { // postfix
+ base_type old = get();
+ set(old - 1);
+ return old;
+ }
+};
+
+using int64le = packed<int64, LittleEndian_tag>;
+using int32le = packed<int32, LittleEndian_tag>;
+using int16le = packed<int16, LittleEndian_tag>;
+using int8le = packed<int8, LittleEndian_tag>;
+using uint64le = packed<uint64, LittleEndian_tag>;
+using uint32le = packed<uint32, LittleEndian_tag>;
+using uint16le = packed<uint16, LittleEndian_tag>;
+using uint8le = packed<uint8, LittleEndian_tag>;
+
+using int64be = packed<int64, BigEndian_tag>;
+using int32be = packed<int32, BigEndian_tag>;
+using int16be = packed<int16, BigEndian_tag>;
+using int8be = packed<int8, BigEndian_tag>;
+using uint64be = packed<uint64, BigEndian_tag>;
+using uint32be = packed<uint32, BigEndian_tag>;
+using uint16be = packed<uint16, BigEndian_tag>;
+using uint8be = packed<uint8, BigEndian_tag>;
+
+constexpr bool declare_binary_safe(const int64le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int32le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int16le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int8le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint64le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint32le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint16le &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint8le &) {
+ return true;
+}
+
+constexpr bool declare_binary_safe(const int64be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int32be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int16be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const int8be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint64be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint32be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint16be &) {
+ return true;
+}
+constexpr bool declare_binary_safe(const uint8be &) {
+ return true;
+}
+
+static_assert(mpt::check_binary_size<int64le>(8));
+static_assert(mpt::check_binary_size<int32le>(4));
+static_assert(mpt::check_binary_size<int16le>(2));
+static_assert(mpt::check_binary_size<int8le>(1));
+static_assert(mpt::check_binary_size<uint64le>(8));
+static_assert(mpt::check_binary_size<uint32le>(4));
+static_assert(mpt::check_binary_size<uint16le>(2));
+static_assert(mpt::check_binary_size<uint8le>(1));
+
+static_assert(mpt::check_binary_size<int64be>(8));
+static_assert(mpt::check_binary_size<int32be>(4));
+static_assert(mpt::check_binary_size<int16be>(2));
+static_assert(mpt::check_binary_size<int8be>(1));
+static_assert(mpt::check_binary_size<uint64be>(8));
+static_assert(mpt::check_binary_size<uint32be>(4));
+static_assert(mpt::check_binary_size<uint16be>(2));
+static_assert(mpt::check_binary_size<uint8be>(1));
+
+
+
+template <typename T>
+struct make_le {
+ using type = packed<typename std::remove_const<T>::type, LittleEndian_tag>;
+};
+
+template <typename T>
+struct make_be {
+ using type = packed<typename std::remove_const<T>::type, BigEndian_tag>;
+};
+
+template <mpt::endian endian, typename T>
+struct make_endian {
+};
+
+template <typename T>
+struct make_endian<mpt::endian::little, T> {
+ using type = packed<typename std::remove_const<T>::type, LittleEndian_tag>;
+};
+
+template <typename T>
+struct make_endian<mpt::endian::big, T> {
+ using type = packed<typename std::remove_const<T>::type, BigEndian_tag>;
+};
+
+template <typename T>
+MPT_CONSTEXPR20_FUN auto as_le(T v) noexcept -> typename mpt::make_le<typename std::remove_const<T>::type>::type {
+ typename mpt::make_le<typename std::remove_const<T>::type>::type res{};
+ res = v;
+ return res;
+}
+
+template <typename T>
+MPT_CONSTEXPR20_FUN auto as_be(T v) noexcept -> typename mpt::make_be<typename std::remove_const<T>::type>::type {
+ typename mpt::make_be<typename std::remove_const<T>::type>::type res{};
+ res = v;
+ return res;
+}
+
+template <typename Tpacked>
+MPT_CONSTEXPR20_FUN Tpacked as_endian(typename Tpacked::base_type v) noexcept {
+ Tpacked res{};
+ res = v;
+ return res;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+namespace std {
+template <typename T, typename Tendian>
+class numeric_limits<mpt::packed<T, Tendian>> : public std::numeric_limits<T> { };
+template <typename T, typename Tendian>
+class numeric_limits<const mpt::packed<T, Tendian>> : public std::numeric_limits<const T> { };
+} // namespace std
+
+
+
+#endif // MPT_ENDIAN_INTEGER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_floatingpoint.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_floatingpoint.hpp
new file mode 100644
index 00000000..b8a6094b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_floatingpoint.hpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENDIAN_TESTS_ENDIAN_FLOATINGPOINT_HPP
+#define MPT_ENDIAN_TESTS_ENDIAN_FLOATINGPOINT_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/endian/floatingpoint.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace endian {
+namespace floatingpoint {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/endian/floatingpoint")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::EncodeIEEE754binary32(1.0f), 0x3f800000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::EncodeIEEE754binary32(-1.0f), 0xbf800000u);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x00000000u), 0.0f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x41840000u), 16.5f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x3faa0000u), 1.328125f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0xbfaa0000u), -1.328125f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x3f800000u), 1.0f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x00000000u), 0.0f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0xbf800000u), -1.0f);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary32(0x3f800000u), 1.0f);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32LE(1.0f).GetInt32(), 0x3f800000u);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32BE(1.0f).GetInt32(), 0x3f800000u);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32LE(mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x80), mpt::as_byte(0x3f)), 1.0f);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32BE(mpt::as_byte(0x3f), mpt::as_byte(0x80), mpt::as_byte(0x00), mpt::as_byte(0x00)), 1.0f);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32LE(1.0f), IEEE754binary32LE(mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x80), mpt::as_byte(0x3f)));
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary32BE(1.0f), IEEE754binary32BE(mpt::as_byte(0x3f), mpt::as_byte(0x80), mpt::as_byte(0x00), mpt::as_byte(0x00)));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::EncodeIEEE754binary64(1.0), 0x3ff0000000000000ull);
+ MPT_TEST_EXPECT_EQUAL(mpt::EncodeIEEE754binary64(-1.0), 0xbff0000000000000ull);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x0000000000000000ull), 0.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x4030800000000000ull), 16.5);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x3FF5400000000000ull), 1.328125);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0xBFF5400000000000ull), -1.328125);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x3ff0000000000000ull), 1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x0000000000000000ull), 0.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0xbff0000000000000ull), -1.0);
+ MPT_TEST_EXPECT_EQUAL(mpt::DecodeIEEE754binary64(0x3ff0000000000000ull), 1.0);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64LE(1.0).GetInt64(), 0x3ff0000000000000ull);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64BE(1.0).GetInt64(), 0x3ff0000000000000ull);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64LE(mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0xf0), mpt::as_byte(0x3f)), 1.0);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64BE(mpt::as_byte(0x3f), mpt::as_byte(0xf0), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00)), 1.0);
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64LE(1.0), IEEE754binary64LE(mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0xf0), mpt::as_byte(0x3f)));
+ MPT_TEST_EXPECT_EQUAL(IEEE754binary64BE(1.0), IEEE754binary64BE(mpt::as_byte(0x3f), mpt::as_byte(0xf0), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00), mpt::as_byte(0x00)));
+}
+
+} // namespace floatingpoint
+} // namespace endian
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_ENDIAN_TESTS_ENDIAN_FLOATINGPOINT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_integer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_integer.hpp
new file mode 100644
index 00000000..0511642e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/endian/tests/tests_endian_integer.hpp
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENDIAN_TESTS_ENDIAN_INTEGER_HPP
+#define MPT_ENDIAN_TESTS_ENDIAN_INTEGER_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace endian {
+namespace integer {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/endian/integer")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ static_assert(std::numeric_limits<int8le>::min() == std::numeric_limits<int8>::min());
+ static_assert(std::numeric_limits<uint8le>::min() == std::numeric_limits<uint8>::min());
+
+ static_assert(std::numeric_limits<int16le>::min() == std::numeric_limits<int16>::min());
+ static_assert(std::numeric_limits<uint16le>::min() == std::numeric_limits<uint16>::min());
+
+ static_assert(std::numeric_limits<int32le>::min() == std::numeric_limits<int32>::min());
+ static_assert(std::numeric_limits<uint32le>::min() == std::numeric_limits<uint32>::min());
+
+ static_assert(std::numeric_limits<int64le>::min() == std::numeric_limits<int64>::min());
+ static_assert(std::numeric_limits<uint64le>::min() == std::numeric_limits<uint64>::min());
+
+ static_assert(std::numeric_limits<int8le>::max() == std::numeric_limits<int8>::max());
+ static_assert(std::numeric_limits<uint8le>::max() == std::numeric_limits<uint8>::max());
+
+ static_assert(std::numeric_limits<int16le>::max() == std::numeric_limits<int16>::max());
+ static_assert(std::numeric_limits<uint16le>::max() == std::numeric_limits<uint16>::max());
+
+ static_assert(std::numeric_limits<int32le>::max() == std::numeric_limits<int32>::max());
+ static_assert(std::numeric_limits<uint32le>::max() == std::numeric_limits<uint32>::max());
+
+ static_assert(std::numeric_limits<int64le>::max() == std::numeric_limits<int64>::max());
+ static_assert(std::numeric_limits<uint64le>::max() == std::numeric_limits<uint64>::max());
+
+ struct test_endian_constexpr {
+ static MPT_CONSTEXPR20_FUN int32le test(uint32 x) {
+ int32le foo{};
+ foo = x;
+ return foo;
+ }
+ };
+
+ MPT_CONSTEXPR20_VAR int32le foo = test_endian_constexpr::test(23);
+ static_cast<void>(foo);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(uint8(0x12)), 0x12);
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(uint16(0x1234)), 0x3412);
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(uint32(0x12345678u)), 0x78563412u);
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(uint64(0x123456789abcdef0ull)), 0xf0debc9a78563412ull);
+
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(int8(std::numeric_limits<int8>::min())), std::numeric_limits<int8>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(int16(std::numeric_limits<int16>::min())), int16(0x80));
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(int32(std::numeric_limits<int32>::min())), int32(0x80));
+ MPT_TEST_EXPECT_EQUAL(mpt::SwapBytesImpl(int64(std::numeric_limits<int64>::min())), int64(0x80));
+
+ // Packed integers with defined endianness
+ {
+ int8le le8;
+ le8.set(-128);
+ int8be be8;
+ be8.set(-128);
+ MPT_TEST_EXPECT_EQUAL(le8, -128);
+ MPT_TEST_EXPECT_EQUAL(be8, -128);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&le8, "\x80", 1), 0);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&be8, "\x80", 1), 0);
+ int16le le16;
+ le16.set(0x1234);
+ int16be be16;
+ be16.set(0x1234);
+ MPT_TEST_EXPECT_EQUAL(le16, 0x1234);
+ MPT_TEST_EXPECT_EQUAL(be16, 0x1234);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&le16, "\x34\x12", 2), 0);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&be16, "\x12\x34", 2), 0);
+ uint32le le32;
+ le32.set(0xFFEEDDCCu);
+ uint32be be32;
+ be32.set(0xFFEEDDCCu);
+ MPT_TEST_EXPECT_EQUAL(le32, 0xFFEEDDCCu);
+ MPT_TEST_EXPECT_EQUAL(be32, 0xFFEEDDCCu);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&le32, "\xCC\xDD\xEE\xFF", 4), 0);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&be32, "\xFF\xEE\xDD\xCC", 4), 0);
+ uint64le le64;
+ le64.set(0xDEADC0DE15C0FFEEull);
+ uint64be be64;
+ be64.set(0xDEADC0DE15C0FFEEull);
+ MPT_TEST_EXPECT_EQUAL(le64, 0xDEADC0DE15C0FFEEull);
+ MPT_TEST_EXPECT_EQUAL(be64, 0xDEADC0DE15C0FFEEull);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&le64, "\xEE\xFF\xC0\x15\xDE\xC0\xAD\xDE", 8), 0);
+ MPT_TEST_EXPECT_EQUAL(std::memcmp(&be64, "\xDE\xAD\xC0\xDE\x15\xC0\xFF\xEE", 8), 0);
+ }
+}
+
+} // namespace integer
+} // namespace endian
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_ENDIAN_TESTS_ENDIAN_INTEGER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/environment/environment.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/environment/environment.hpp
new file mode 100644
index 00000000..4fb021b1
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/environment/environment.hpp
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_ENVIRONMENT_ENVIRONMENT_HPP
+#define MPT_ENVIRONMENT_ENVIRONMENT_HPP
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/system_error/system_error.hpp"
+
+#include <optional>
+#if MPT_OS_WINDOWS
+#if defined(UNICODE) && !MPT_OS_WINDOWS_WINRT
+#include <vector>
+#endif // !MPT_OS_WINDOWS_WINRT
+#endif // MPT_OS_WINDOWS
+
+#include <cstdlib>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+inline std::optional<mpt::ustring> getenv(const mpt::ustring & env_var) {
+#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
+ MPT_UNUSED(env_var);
+ return std::nullopt;
+#elif MPT_OS_WINDOWS && defined(UNICODE)
+ std::vector<WCHAR> buf(32767);
+ DWORD size = GetEnvironmentVariable(mpt::transcode<std::wstring>(env_var).c_str(), buf.data(), 32767);
+ if (size == 0) {
+ mpt::windows::ExpectError(ERROR_ENVVAR_NOT_FOUND);
+ return std::nullopt;
+ }
+ return mpt::transcode<mpt::ustring>(buf.data());
+#else
+ const char * val = std::getenv(mpt::transcode<std::string>(mpt::environment_encoding, env_var).c_str());
+ if (!val) {
+ return std::nullopt;
+ }
+ return mpt::transcode<mpt::ustring>(mpt::environment_encoding, val);
+#endif
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_ENVIRONMENT_ENVIRONMENT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/exception_text/exception_text.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/exception_text/exception_text.hpp
new file mode 100644
index 00000000..26d18494
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/exception_text/exception_text.hpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_EXCEPTION_TEXT_EXCEPTION_TEXT_HPP
+#define MPT_EXCEPTION_TEXT_EXCEPTION_TEXT_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <exception>
+
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename Tstring>
+inline Tstring get_exception_text(const std::exception & e) {
+ if (e.what() && (std::strlen(e.what()) > 0)) {
+ return mpt::transcode<Tstring>(mpt::exception_string{e.what()});
+ } else if (typeid(e).name() && (std::strlen(typeid(e).name()) > 0)) {
+ return mpt::transcode<Tstring>(mpt::source_string{typeid(e).name()});
+ } else {
+ return mpt::transcode<Tstring>(mpt::source_string{"unknown exception name"});
+ }
+}
+
+template <>
+inline std::string get_exception_text<std::string>(const std::exception & e) {
+ if (e.what() && (std::strlen(e.what()) > 0)) {
+ return std::string{e.what()};
+ } else if (typeid(e).name() && (std::strlen(typeid(e).name()) > 0)) {
+ return std::string{typeid(e).name()};
+ } else {
+ return std::string{"unknown exception name"};
+ }
+}
+
+
+template <typename Tstring>
+inline Tstring get_current_exception_text() {
+ try {
+ throw;
+ } catch (const std::exception & e) {
+ return mpt::get_exception_text<Tstring>(e);
+ } catch (...) {
+ return mpt::transcode<Tstring>(mpt::source_string{"unknown exception"});
+ }
+}
+
+template <>
+inline std::string get_current_exception_text<std::string>() {
+ try {
+ throw;
+ } catch (const std::exception & e) {
+ return mpt::get_exception_text<std::string>(e);
+ } catch (...) {
+ return std::string{"unknown exception"};
+ }
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_EXCEPTION_TEXT_EXCEPTION_TEXT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_floatingpoint.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_floatingpoint.hpp
new file mode 100644
index 00000000..c308b623
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_floatingpoint.hpp
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_DETAULT_FLOATINGPOINT_HPP
+#define MPT_FORMAT_DETAULT_FLOATINGPOINT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+
+#if !defined(MPT_LIBCXX_QUIRK_NO_TO_CHARS_FLOAT)
+#define MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17 1
+#else
+#define MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17 0
+#endif
+
+#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+#include "mpt/base/algorithm.hpp"
+#endif
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/helpers.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+#include <charconv>
+#endif
+#if !MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+#include <iomanip>
+#include <ios>
+#include <limits>
+#include <locale>
+#include <sstream>
+#endif
+#include <string>
+#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+#include <system_error>
+#endif
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring to_chars_string(const T & x) {
+ std::string str(1, '\0');
+ bool done = false;
+ while (!done) {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), x);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ }
+ return mpt::convert_formatted_simple<Tstring>(str);
+}
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::transcode<Tstring>(mpt::to_chars_string<typename mpt::select_format_string_type<Tstring>::type>(x));
+}
+#endif
+
+
+#if !MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring to_stream_string(const T & x) {
+ using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type;
+ std::basic_ostringstream<stream_char_type> s;
+ s.imbue(std::locale::classic());
+ s << std::setprecision(std::numeric_limits<T>::max_digits10) << x;
+ return mpt::convert_formatted_simple<Tstring>(s.str());
+}
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::transcode<Tstring>(mpt::to_stream_string<typename mpt::select_format_string_type<Tstring>::type>(x));
+}
+#endif
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_DETAULT_FLOATINGPOINT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_formatter.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_formatter.hpp
new file mode 100644
index 00000000..27946a5a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_formatter.hpp
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_DEFAULT_FORMATTER_HPP
+#define MPT_FORMAT_DEFAULT_FORMATTER_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/default_floatingpoint.hpp"
+#include "mpt/format/default_integer.hpp"
+#include "mpt/format/default_string.hpp"
+
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+struct default_formatter {
+ template <typename Tstring, typename T>
+ static inline Tstring format(const T & value) {
+ using namespace mpt;
+ return format_value_default<Tstring>(value);
+ }
+};
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_DEFAULT_FORMATTER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_integer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_integer.hpp
new file mode 100644
index 00000000..fedd2dc0
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_integer.hpp
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_DEFAULT_INTEGER_HPP
+#define MPT_FORMAT_DEFAULT_INTEGER_HPP
+
+
+#include "mpt/base/detect.hpp"
+
+#if !defined(MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT)
+#define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 1
+#else // MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT
+#define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 0
+#endif // !MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT
+
+#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include "mpt/base/algorithm.hpp"
+#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/format/helpers.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include <charconv>
+#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#if !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include <locale>
+#include <sstream>
+#endif // !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include <string>
+#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include <system_error>
+#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring to_chars_string(const T & x) {
+ std::string str(1, '\0');
+ bool done = false;
+ while (!done) {
+ if constexpr (std::is_same<T, bool>::value) {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), static_cast<int>(x));
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ } else {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), x);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ }
+ }
+ return mpt::convert_formatted_simple<Tstring>(str);
+}
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::transcode<Tstring>(mpt::to_chars_string<typename mpt::select_format_string_type<Tstring>::type>(x));
+}
+
+#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+
+
+#if !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring to_stream_string(const T & x) {
+ using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type;
+ std::basic_ostringstream<stream_char_type> s;
+ s.imbue(std::locale::classic());
+ if constexpr (std::is_same<T, bool>::value) {
+ s << static_cast<int>(x);
+ } else if constexpr (mpt::is_character<T>::value) {
+ s << (x + 0); // force integral promotion
+ } else {
+ s << x;
+ }
+ return mpt::convert_formatted_simple<Tstring>(s.str());
+}
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::transcode<Tstring>(mpt::to_stream_string<typename mpt::select_format_string_type<Tstring>::type>(x));
+}
+
+#endif // !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
+
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_enum<T>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::format_value_default<Tstring>(mpt::to_underlying(x));
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_DEFAULT_INTEGER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_string.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_string.hpp
new file mode 100644
index 00000000..c8bf7734
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/default_string.hpp
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_DEFAULT_STRING_HPP
+#define MPT_FORMAT_DEFAULT_STRING_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename Tstring, typename T>
+inline auto format_value_default(const T & x) -> decltype(mpt::transcode<Tstring>(x)) {
+ return mpt::transcode<Tstring>(x);
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_DEFAULT_STRING_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/helpers.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/helpers.hpp
new file mode 100644
index 00000000..a8ff89fd
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/helpers.hpp
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_HELPERS_HPP
+#define MPT_FORMAT_HELPERS_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <string>
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename Tdststring, typename Tsrcstring>
+inline Tdststring convert_formatted_simple(const Tsrcstring & src) {
+ if constexpr (std::is_same<Tdststring, Tsrcstring>::value) {
+ return src;
+ } else {
+ Tdststring dst;
+ dst.reserve(src.length());
+ for (std::size_t i = 0; i < src.length(); ++i) {
+ dst.push_back(mpt::unsafe_char_convert<typename Tdststring::value_type>(src[i]));
+ }
+ return dst;
+ }
+}
+
+
+template <typename Tchar>
+struct select_format_char_type {
+ using type = char;
+};
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct select_format_char_type<wchar_t> {
+ using type = wchar_t;
+};
+#if MPT_USTRING_MODE_WIDE
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct select_format_char_type<char8_t> {
+ using type = wchar_t;
+};
+#endif // C++20
+template <>
+struct select_format_char_type<char16_t> {
+ using type = wchar_t;
+};
+template <>
+struct select_format_char_type<char32_t> {
+ using type = wchar_t;
+};
+#endif // MPT_USTRING_MODE_WIDE
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+
+template <typename Tstring>
+struct select_format_string_type {
+ using type = mpt::ustring;
+};
+
+template <>
+struct select_format_string_type<std::string> {
+ using type = std::string;
+};
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct select_format_string_type<std::wstring> {
+ using type = std::wstring;
+};
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct select_format_string_type<std::u8string> {
+ using type = std::u8string;
+};
+#endif // C++20
+
+template <>
+struct select_format_string_type<std::u16string> {
+ using type = std::u16string;
+};
+
+template <>
+struct select_format_string_type<std::u32string> {
+ using type = std::u32string;
+};
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_HELPERS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/join.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/join.hpp
new file mode 100644
index 00000000..76e5ef74
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/join.hpp
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_JOIN_HPP
+#define MPT_FORMAT_JOIN_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/utility.hpp"
+
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename Tstring, typename T>
+Tstring join_format(const std::vector<T> & vals, const Tstring & sep = Tstring(1, char_constants<typename Tstring::value_type>::comma)) {
+ Tstring str;
+ for (std::size_t i = 0; i < vals.size(); ++i) {
+ if (i > 0) {
+ str += sep;
+ }
+ str += mpt::format<Tstring>::val(vals[i]);
+ }
+ return str;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_JOIN_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/message.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/message.hpp
new file mode 100644
index 00000000..b4b0e914
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/message.hpp
@@ -0,0 +1,332 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_MESSAGE_HPP
+#define MPT_FORMAT_MESSAGE_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+
+#include <array>
+#include <stdexcept>
+#include <utility>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+class format_message_syntax_error
+ : public std::domain_error {
+public:
+ format_message_syntax_error()
+ : std::domain_error("format string syntax error") {
+ return;
+ }
+};
+
+template <typename Tformatter, typename Tformat>
+class message_formatter {
+
+public:
+ using Tstring = typename mpt::make_string_type<Tformat>::type;
+
+private:
+ Tstring format;
+
+private:
+ MPT_NOINLINE Tstring do_format(const mpt::span<const Tstring> vals) const {
+ using traits = typename mpt::string_traits<Tstring>;
+ using char_type = typename traits::char_type;
+ using size_type = typename traits::size_type;
+ Tstring result;
+ const size_type len = traits::length(format);
+ traits::reserve(result, len);
+ std::size_t max_arg = 0;
+ //std::size_t args = 0;
+ bool success = true;
+ enum class state : int {
+ error = -1,
+ text = 0,
+ open_seen = 1,
+ number_seen = 2,
+ close_seen = 3,
+ };
+ state state = state::text;
+ bool numbered_args = false;
+ bool unnumbered_args = false;
+ std::size_t last_arg = 0;
+ std::size_t this_arg = 0;
+ std::size_t current_arg = 0;
+ for (size_type pos = 0; pos != len; ++pos) {
+ char_type c = format[pos];
+ switch (state) {
+ case state::text:
+ if (c == char_type('{')) {
+ state = state::open_seen;
+ } else if (c == char_type('}')) {
+ state = state::close_seen;
+ } else {
+ state = state::text;
+ traits::append(result, 1, c); // output c here
+ }
+ break;
+ case state::open_seen:
+ if (c == char_type('{')) {
+ state = state::text;
+ traits::append(result, 1, char_type('{')); // output { here
+ } else if (c == char_type('}')) {
+ state = state::text;
+ unnumbered_args = true;
+ last_arg++;
+ this_arg = last_arg;
+ { // output this_arg here
+ const std::size_t n = this_arg - 1;
+ if (n < std::size(vals)) {
+ traits::append(result, vals[n]);
+ }
+ }
+ if (this_arg > max_arg) {
+ max_arg = this_arg;
+ }
+ //args += 1;
+ } else if (char_type('0') <= c && c <= char_type('9')) {
+ state = state::number_seen;
+ numbered_args = true;
+ current_arg = c - char_type('0');
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::number_seen:
+ if (c == char_type('{')) {
+ state = state::error;
+ } else if (c == char_type('}')) {
+ state = state::text;
+ this_arg = current_arg + 1;
+ { // output this_arg here
+ const std::size_t n = this_arg - 1;
+ if (n < std::size(vals)) {
+ traits::append(result, vals[n]);
+ }
+ }
+ if (this_arg > max_arg) {
+ max_arg = this_arg;
+ }
+ //args += 1;
+ } else if (char_type('0') <= c && c <= char_type('9')) {
+ state = state::number_seen;
+ numbered_args = true;
+ current_arg = (current_arg * 10) + (c - char_type('0'));
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::close_seen:
+ if (c == char_type('{')) {
+ state = state::error;
+ } else if (c == char_type('}')) {
+ state = state::text;
+ traits::append(result, 1, char_type('}')); // output } here
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::error:
+ state = state::error;
+ break;
+ }
+ }
+ if (state == state::error) {
+ success = false;
+ }
+ if (state != state::text) {
+ success = false;
+ }
+ if (numbered_args && unnumbered_args) {
+ success = false;
+ }
+ if (!success) {
+ throw format_message_syntax_error();
+ }
+ return result;
+ }
+
+public:
+ MPT_FORCEINLINE message_formatter(Tstring format_)
+ : format(std::move(format_)) {
+ }
+
+public:
+ template <typename... Ts>
+ MPT_NOINLINE Tstring operator()(Ts &&... xs) const {
+ const std::array<Tstring, sizeof...(xs)> vals{{Tformatter::template format<Tstring>(std::forward<Ts>(xs))...}};
+ return do_format(mpt::as_span(vals));
+ }
+
+}; // struct message_formatter<Tformat>
+
+
+template <typename Tformatter, std::ptrdiff_t N, typename Tchar, typename Tstring>
+class message_formatter_counted {
+
+private:
+ message_formatter<Tformatter, Tstring> formatter;
+
+public:
+ template <std::size_t literal_length>
+ inline message_formatter_counted(const Tchar (&format)[literal_length])
+ : formatter(Tstring(format)) {
+ return;
+ }
+
+public:
+ template <typename... Ts>
+ inline Tstring operator()(Ts &&... xs) const {
+ static_assert(static_cast<std::ptrdiff_t>(sizeof...(xs)) == N);
+ return formatter(std::forward<Ts>(xs)...);
+ }
+
+}; // struct message_formatter_counted<Tformat>
+
+
+template <typename Tchar>
+MPT_CONSTEXPRINLINE std::ptrdiff_t parse_format_string_argument_count_impl(const Tchar * const format, const std::size_t len) {
+ std::size_t max_arg = 0;
+ std::size_t args = 0;
+ bool success = true;
+ enum class state : int {
+ error = -1,
+ text = 0,
+ open_seen = 1,
+ number_seen = 2,
+ close_seen = 3,
+ };
+ state state = state::text;
+ bool numbered_args = false;
+ bool unnumbered_args = false;
+ std::size_t last_arg = 0;
+ std::size_t this_arg = 0;
+ std::size_t current_arg = 0;
+ for (std::size_t pos = 0; pos != len; ++pos) {
+ Tchar c = format[pos];
+ switch (state) {
+ case state::text:
+ if (c == Tchar('{')) {
+ state = state::open_seen;
+ } else if (c == Tchar('}')) {
+ state = state::close_seen;
+ } else {
+ state = state::text;
+ // output c here
+ }
+ break;
+ case state::open_seen:
+ if (c == Tchar('{')) {
+ state = state::text;
+ // output { here
+ } else if (c == Tchar('}')) {
+ state = state::text;
+ unnumbered_args = true;
+ last_arg++;
+ this_arg = last_arg;
+ // output this_arg here
+ if (this_arg > max_arg)
+ {
+ max_arg = this_arg;
+ }
+ args += 1;
+ } else if (Tchar('0') <= c && c <= Tchar('9')) {
+ state = state::number_seen;
+ numbered_args = true;
+ current_arg = c - Tchar('0');
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::number_seen:
+ if (c == Tchar('{')) {
+ state = state::error;
+ } else if (c == Tchar('}')) {
+ state = state::text;
+ this_arg = current_arg + 1;
+ // output this_arg here
+ if (this_arg > max_arg) {
+ max_arg = this_arg;
+ }
+ args += 1;
+ } else if (Tchar('0') <= c && c <= Tchar('9')) {
+ state = state::number_seen;
+ numbered_args = true;
+ current_arg = (current_arg * 10) + (c - Tchar('0'));
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::close_seen:
+ if (c == Tchar('{')) {
+ state = state::error;
+ } else if (c == Tchar('}')) {
+ state = state::text;
+ // output } here
+ } else {
+ state = state::error;
+ }
+ break;
+ case state::error:
+ state = state::error;
+ break;
+ }
+ }
+ if (state == state::error) {
+ success = false;
+ }
+ if (state != state::text) {
+ success = false;
+ }
+ if (numbered_args && unnumbered_args) {
+ success = false;
+ }
+ if (!success) {
+ throw format_message_syntax_error();
+ }
+ if (max_arg != args) {
+ throw format_message_syntax_error();
+ }
+ return max_arg;
+}
+
+
+template <typename Tchar, std::size_t literal_length>
+MPT_CONSTEXPRINLINE std::ptrdiff_t parse_format_string_argument_count(const Tchar (&format)[literal_length]) {
+ return parse_format_string_argument_count_impl(format, literal_length - 1);
+}
+
+
+template <typename Tformatter, std::size_t args, typename Tchar, std::size_t N>
+inline auto format_message(const Tchar (&format)[N]) {
+ using Tstring = typename mpt::make_string_type<const Tchar *>::type;
+ return message_formatter_counted<Tformatter, args, Tchar, Tstring>(format);
+}
+
+template <typename Tformatter, std::size_t args, typename Tstring, typename Tchar, std::size_t N>
+inline auto format_message_typed(const Tchar (&format)[N]) {
+ return message_formatter_counted<Tformatter, args, Tchar, Tstring>(format);
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_MESSAGE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/message_macros.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/message_macros.hpp
new file mode 100644
index 00000000..c4d9bdb5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/message_macros.hpp
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_MESSAGE_MACROS_HPP
+#define MPT_FORMAT_MESSAGE_MACROS_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/detect/mfc.hpp"
+#include "mpt/format/default_formatter.hpp"
+#include "mpt/format/message.hpp"
+
+
+
+#define MPT_AFORMAT_MESSAGE(f) mpt::format_message<mpt::default_formatter, mpt::parse_format_string_argument_count(f)>(f)
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+#define MPT_WFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(L##f), std::wstring>(L##f)
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#define MPT_UFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(MPT_ULITERAL(f)), mpt::ustring>(MPT_ULITERAL(f))
+
+#define MPT_LFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(f), mpt::lstring>(f)
+
+#if MPT_OS_WINDOWS
+#define MPT_TFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(TEXT(f)), mpt::tstring>(TEXT(f))
+#endif // MPT_OS_WINDOWS
+
+#if MPT_DETECTED_MFC
+#define MPT_CWFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(L##f), CStringW>(L##f)
+#define MPT_CAFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(f), CStringA>(f)
+#define MPT_CFORMAT_MESSAGE(f) mpt::format_message_typed<mpt::default_formatter, mpt::parse_format_string_argument_count(TEXT(f)), CString>(TEXT(f))
+#endif // MPT_DETECTED_MFC
+
+
+
+#endif // MPT_FORMAT_MESSAGE_MACROS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple.hpp
new file mode 100644
index 00000000..2ca40dce
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple.hpp
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_SIMPLE_HPP
+#define MPT_FORMAT_SIMPLE_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/pointer.hpp"
+#include "mpt/format/default_formatter.hpp"
+#include "mpt/format/simple_floatingpoint.hpp"
+#include "mpt/format/simple_integer.hpp"
+#include "mpt/format/simple_spec.hpp"
+#include "mpt/string/utility.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename Tstring>
+struct format : format_simple_base {
+
+ template <typename T>
+ static inline Tstring val(const T & x) {
+ return mpt::default_formatter::format<Tstring>(x);
+ }
+
+ template <typename T>
+ static inline Tstring fmt(const T & x, const format_simple_spec & f) {
+ return mpt::format_simple<Tstring>(x, f);
+ }
+
+ template <typename T>
+ static inline Tstring dec(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseDec().FillOff());
+ }
+ template <int width, typename T>
+ static inline Tstring dec0(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseDec().FillNul().Width(width));
+ }
+
+ template <typename T>
+ static inline Tstring dec(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseDec().FillOff().Group(g).GroupSep(s));
+ }
+ template <int width, typename T>
+ static inline Tstring dec0(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseDec().FillNul().Width(width).Group(g).GroupSep(s));
+ }
+
+ template <typename T>
+ static inline Tstring hex(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseLow().FillOff());
+ }
+ template <typename T>
+ static inline Tstring HEX(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseUpp().FillOff());
+ }
+ template <int width, typename T>
+ static inline Tstring hex0(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseLow().FillNul().Width(width));
+ }
+ template <int width, typename T>
+ static inline Tstring HEX0(const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseUpp().FillNul().Width(width));
+ }
+
+ template <typename T>
+ static inline Tstring hex(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseLow().FillOff().Group(g).GroupSep(s));
+ }
+ template <typename T>
+ static inline Tstring HEX(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseUpp().FillOff().Group(g).GroupSep(s));
+ }
+ template <int width, typename T>
+ static inline Tstring hex0(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseLow().FillNul().Width(width).Group(g).GroupSep(s));
+ }
+ template <int width, typename T>
+ static inline Tstring HEX0(unsigned int g, char s, const T & x) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().BaseHex().CaseUpp().FillNul().Width(width).Group(g).GroupSep(s));
+ }
+
+ template <typename T>
+ static inline Tstring flt(const T & x, int precision = -1) {
+ static_assert(std::is_floating_point<T>::value);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().NotaNrm().FillOff().Precision(precision));
+ }
+ template <typename T>
+ static inline Tstring fix(const T & x, int precision = -1) {
+ static_assert(std::is_floating_point<T>::value);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().NotaFix().FillOff().Precision(precision));
+ }
+ template <typename T>
+ static inline Tstring sci(const T & x, int precision = -1) {
+ static_assert(std::is_floating_point<T>::value);
+ return mpt::format_simple<Tstring>(x, format_simple_spec().NotaSci().FillOff().Precision(precision));
+ }
+
+ template <typename T>
+ static inline Tstring ptr(const T & x) {
+ static_assert(std::is_pointer<T>::value || std::is_same<T, std::uintptr_t>::value || std::is_same<T, std::intptr_t>::value, "");
+ return hex0<mpt::pointer_size * 2>(mpt::pointer_cast<const std::uintptr_t>(x));
+ }
+ template <typename T>
+ static inline Tstring PTR(const T & x) {
+ static_assert(std::is_pointer<T>::value || std::is_same<T, std::uintptr_t>::value || std::is_same<T, std::intptr_t>::value, "");
+ return HEX0<mpt::pointer_size * 2>(mpt::pointer_cast<const std::uintptr_t>(x));
+ }
+
+ static inline Tstring pad_left(std::size_t width_, const Tstring & str) {
+ typedef mpt::string_traits<Tstring> traits;
+ typename traits::size_type width = static_cast<typename traits::size_type>(width_);
+ return traits::pad(str, width, 0);
+ }
+ static inline Tstring pad_right(std::size_t width_, const Tstring & str) {
+ typedef mpt::string_traits<Tstring> traits;
+ typename traits::size_type width = static_cast<typename traits::size_type>(width_);
+ return traits::pad(str, 0, width);
+ }
+ static inline Tstring left(std::size_t width_, const Tstring & str) {
+ typedef mpt::string_traits<Tstring> traits;
+ typename traits::size_type width = static_cast<typename traits::size_type>(width_);
+ return (traits::length(str) < width) ? traits::pad(str, 0, width - traits::length(str)) : str;
+ }
+ static inline Tstring right(std::size_t width_, const Tstring & str) {
+ typedef mpt::string_traits<Tstring> traits;
+ typename traits::size_type width = static_cast<typename traits::size_type>(width_);
+ return (traits::length(str) < width) ? traits::pad(str, width - traits::length(str), 0) : str;
+ }
+ static inline Tstring center(std::size_t width_, const Tstring & str) {
+ typedef mpt::string_traits<Tstring> traits;
+ typename traits::size_type width = static_cast<typename traits::size_type>(width_);
+ return (traits::length(str) < width) ? traits::pad(str, (width - traits::length(str)) / 2, (width - traits::length(str) + 1) / 2) : str;
+ }
+
+}; // struct format
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_SIMPLE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_floatingpoint.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_floatingpoint.hpp
new file mode 100644
index 00000000..d1ccb32b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_floatingpoint.hpp
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_SIMPLE_FLOATINGPOINT_HPP
+#define MPT_FORMAT_SIMPLE_FLOATINGPOINT_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+
+#if !defined(MPT_LIBCXX_QUIRK_NO_TO_CHARS_FLOAT)
+#define MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17 1
+#else
+#define MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17 0
+#endif
+
+#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include "mpt/base/algorithm.hpp"
+#endif
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/default_floatingpoint.hpp"
+#include "mpt/format/helpers.hpp"
+#include "mpt/format/simple_spec.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include <charconv>
+#endif
+#if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include <iomanip>
+#include <ios>
+#endif
+#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include <iterator>
+#endif
+#if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include <limits>
+#include <locale>
+#include <sstream>
+#endif
+#include <string>
+#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+#include <system_error>
+#endif
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+
+
+template <typename Tstring, typename T>
+inline Tstring format_simple_floatingpoint_to_chars(const T & x, std::chars_format fmt) {
+ std::string str(1, '\0');
+ bool done = false;
+ while (!done) {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), x, fmt);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ }
+ return mpt::convert_formatted_simple<Tstring>(str);
+}
+
+
+template <typename Tstring, typename T>
+inline Tstring format_simple_floatingpoint_to_chars(const T & x, std::chars_format fmt, int precision) {
+ std::string str(1, '\0');
+ bool done = false;
+ while (!done) {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), x, fmt, precision);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ }
+ return mpt::convert_formatted_simple<Tstring>(str);
+}
+
+template <typename Tstring>
+inline Tstring format_simple_floatingpoint_postprocess_width(Tstring str, const format_simple_spec & format) {
+ format_simple_flags f = format.GetFlags();
+ std::size_t width = format.GetWidth();
+ if (f & format_simple_base::FillNul) {
+ auto pos = str.begin();
+ if (str.length() > 0) {
+ if (str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('+')) {
+ pos++;
+ width++;
+ } else if (str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('-')) {
+ pos++;
+ width++;
+ }
+ }
+ if (str.length() - std::distance(str.begin(), pos) < width) {
+ str.insert(pos, width - str.length() - std::distance(str.begin(), pos), '0');
+ }
+ } else {
+ if (str.length() < width) {
+ str.insert(0, width - str.length(), ' ');
+ }
+ }
+ return str;
+}
+
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring format_simple(const T & x, const format_simple_spec & f) {
+ using format_string_type = typename mpt::select_format_string_type<Tstring>::type;
+ if (f.GetPrecision() != -1) {
+ if (f.GetFlags() & format_simple_base::NotaSci) {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::scientific, f.GetPrecision()), f));
+ } else if (f.GetFlags() & format_simple_base::NotaFix) {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::fixed, f.GetPrecision()), f));
+ } else {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::general, f.GetPrecision()), f));
+ }
+ } else {
+ if (f.GetFlags() & format_simple_base::NotaSci) {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::scientific), f));
+ } else if (f.GetFlags() & format_simple_base::NotaFix) {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::fixed), f));
+ } else {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_postprocess_width(format_simple_floatingpoint_to_chars<format_string_type>(x, std::chars_format::general), f));
+ }
+ }
+}
+
+
+#else // !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+
+
+template <typename Tchar>
+struct NumPunct : std::numpunct<Tchar> {
+private:
+ unsigned int group;
+ char sep;
+
+public:
+ NumPunct(unsigned int g, char s)
+ : group(g)
+ , sep(s) { }
+ std::string do_grouping() const override {
+ return std::string(1, static_cast<char>(group));
+ }
+ Tchar do_thousands_sep() const override {
+ return static_cast<Tchar>(sep);
+ }
+};
+
+template <typename Tostream, typename T>
+inline void format_simple_floatingpoint_apply_stream_format(Tostream & o, const format_simple_spec & format, const T &) {
+ if (format.GetGroup() > 0)
+ {
+ o.imbue(std::locale(o.getloc(), new NumPunct<typename Tostream::char_type>(format.GetGroup(), format.GetGroupSep())));
+ }
+ format_simple_flags f = format.GetFlags();
+ std::size_t width = format.GetWidth();
+ int precision = format.GetPrecision();
+ if (precision != -1 && width != 0 && !(f & format_simple_base::NotaFix) && !(f & format_simple_base::NotaSci))
+ {
+ // fixup:
+ // precision behaves differently from .#
+ // avoid default format when precision and width are set
+ f &= ~format_simple_base::NotaNrm;
+ f |= format_simple_base::NotaFix;
+ }
+ if (f & format_simple_base::BaseDec) {
+ o << std::dec;
+ } else if (f & format_simple_base::BaseHex) {
+ o << std::hex;
+ }
+ if (f & format_simple_base::NotaNrm) { /*nothing*/
+ } else if (f & format_simple_base::NotaFix) {
+ o << std::setiosflags(std::ios::fixed);
+ } else if (f & format_simple_base::NotaSci) {
+ o << std::setiosflags(std::ios::scientific);
+ }
+ if (f & format_simple_base::CaseLow) {
+ o << std::nouppercase;
+ } else if (f & format_simple_base::CaseUpp) {
+ o << std::uppercase;
+ }
+ if (f & format_simple_base::FillOff) { /* nothing */
+ } else if (f & format_simple_base::FillNul) {
+ o << std::setw(width) << std::setfill(typename Tostream::char_type('0'));
+ }
+ if (precision != -1)
+ {
+ o << std::setprecision(precision);
+ } else
+ {
+ if constexpr (std::is_floating_point<T>::value)
+ {
+ if (f & format_simple_base::NotaNrm)
+ {
+ o << std::setprecision(std::numeric_limits<T>::max_digits10);
+ } else if (f & format_simple_base::NotaFix)
+ {
+ o << std::setprecision(std::numeric_limits<T>::digits10);
+ } else if (f & format_simple_base::NotaSci)
+ {
+ o << std::setprecision(std::numeric_limits<T>::max_digits10 - 1);
+ } else
+ {
+ o << std::setprecision(std::numeric_limits<T>::max_digits10);
+ }
+ }
+ }
+}
+
+
+template <typename Tstring, typename T>
+inline Tstring format_simple_floatingpoint_stream(const T & x, const format_simple_spec & f) {
+ using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type;
+ std::basic_ostringstream<stream_char_type> s;
+ s.imbue(std::locale::classic());
+ mpt::format_simple_floatingpoint_apply_stream_format(s, f, x);
+ s << x;
+ return mpt::convert_formatted_simple<Tstring>(s.str());
+}
+
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
+inline Tstring format_simple(const T & x, const format_simple_spec & format) {
+ return mpt::transcode<Tstring>(mpt::format_simple_floatingpoint_stream<typename mpt::select_format_string_type<Tstring>::type>(x, format));
+}
+
+
+
+#endif // MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_SIMPLE_FLOATINGPOINT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_integer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_integer.hpp
new file mode 100644
index 00000000..addecf8f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_integer.hpp
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_SIMPLE_INTEGER_HPP
+#define MPT_FORMAT_SIMPLE_INTEGER_HPP
+
+
+#include "mpt/base/detect.hpp"
+
+#if !defined(MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT)
+#define MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17 1
+#else // MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT
+#define MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17 0
+#endif // !MPT_LIBCXX_QUIRK_NO_TO_CHARS_INT
+
+#include "mpt/base/algorithm.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/helpers.hpp"
+#include "mpt/format/simple_spec.hpp"
+#include "mpt/string/types.hpp"
+
+#if !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <algorithm>
+#endif // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <charconv>
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#if !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <ios>
+#include <locale>
+#endif // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <string>
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#if !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <sstream>
+#endif // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <system_error>
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+#include <type_traits>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_simple_integer_to_chars(const T & x, int base) {
+ std::string str(1, '\0');
+ bool done = false;
+ while (!done) {
+ if constexpr (std::is_same<T, bool>::value) {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), static_cast<int>(x), base);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ } else {
+ std::to_chars_result result = std::to_chars(str.data(), str.data() + str.size(), x, base);
+ if (result.ec != std::errc{}) {
+ str.resize(mpt::exponential_grow(str.size()), '\0');
+ } else {
+ str.resize(result.ptr - str.data());
+ done = true;
+ }
+ }
+ }
+ return mpt::convert_formatted_simple<Tstring>(str);
+}
+
+#else // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_simple_integer_to_stream(const T & x, int base) {
+ using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type;
+ if ((base == 10) || ((base == 16) && std::is_unsigned<T>::value) || ((base == 8) && std::is_unsigned<T>::value)) {
+ // fast path
+ std::basic_ostringstream<stream_char_type> s;
+ s.imbue(std::locale::classic());
+ if (base == 16) {
+ s << std::hex;
+ } else if (base == 8) {
+ s << std::oct;
+ }
+ if constexpr (std::is_same<T, bool>::value) {
+ s << static_cast<int>(x);
+ } else if constexpr (mpt::is_character<T>::value) {
+ s << (x + 0); // force integral promotion
+ } else {
+ s << x;
+ }
+ return mpt::convert_formatted_simple<Tstring>(s.str());
+ } else {
+ if constexpr (std::is_same<T, bool>::value) {
+ return x ? Tstring(1, mpt::unsafe_char_convert<typename Tstring::value_type>('1')) : Tstring(1, mpt::unsafe_char_convert<typename Tstring::value_type>('0'));
+ } else if constexpr (std::is_unsigned<T>::value) {
+ Tstring result;
+ T val = x;
+ if (val == 0) {
+ result += Tstring(1, mpt::unsafe_char_convert<typename Tstring::value_type>('0'));
+ } else {
+ using Tunsigned = typename std::make_unsigned<T>::type;
+ while (val > 0) {
+ Tunsigned digit = static_cast<Tunsigned>(val % static_cast<unsigned int>(base));
+ val = static_cast<Tunsigned>(val / static_cast<unsigned int>(base));
+ if (digit >= 10) {
+ result += Tstring(1, static_cast<typename Tstring::value_type>(mpt::unsafe_char_convert<typename Tstring::value_type>('a') - 10 + digit));
+ } else {
+ result += Tstring(1, static_cast<typename Tstring::value_type>(mpt::unsafe_char_convert<typename Tstring::value_type>('0') + digit));
+ }
+ }
+ std::reverse(result.begin(), result.end());
+ }
+ return result;
+ } else {
+ Tstring result;
+ if (x == 0) {
+ result += Tstring(1, mpt::unsafe_char_convert<typename Tstring::value_type>('0'));
+ } else {
+ using Tunsigned = typename std::make_unsigned<T>::type;
+ Tunsigned val = (x != -x) ? ((x >= 0) ? x : -x) : (static_cast<Tunsigned>(-(x + 1)) + 1);
+ while (val > 0) {
+ Tunsigned digit = static_cast<Tunsigned>(val % static_cast<unsigned int>(base));
+ val = static_cast<Tunsigned>(val / static_cast<unsigned int>(base));
+ if (digit >= 10) {
+ result += Tstring(1, static_cast<typename Tstring::value_type>(mpt::unsafe_char_convert<typename Tstring::value_type>('a') - 10 + digit));
+ } else {
+ result += Tstring(1, static_cast<typename Tstring::value_type>(mpt::unsafe_char_convert<typename Tstring::value_type>('0') + digit));
+ }
+ }
+ if (x < 0) {
+ result += Tstring(1, mpt::unsafe_char_convert<typename Tstring::value_type>('-'));
+ }
+ std::reverse(result.begin(), result.end());
+ }
+ return result;
+ }
+ }
+}
+
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+
+template <typename Tstring>
+inline Tstring format_simple_integer_postprocess_case(Tstring str, const format_simple_spec & format) {
+ format_simple_flags f = format.GetFlags();
+ if (f & format_simple_base::CaseUpp) {
+ for (auto & c : str) {
+ if (mpt::unsafe_char_convert<typename Tstring::value_type>('a') <= c && c <= mpt::unsafe_char_convert<typename Tstring::value_type>('z')) {
+ c -= mpt::unsafe_char_convert<typename Tstring::value_type>('a') - mpt::unsafe_char_convert<typename Tstring::value_type>('A');
+ }
+ }
+ }
+ return str;
+}
+
+
+template <typename Tstring>
+inline Tstring format_simple_integer_postprocess_digits(Tstring str, const format_simple_spec & format) {
+ format_simple_flags f = format.GetFlags();
+ std::size_t width = format.GetWidth();
+ if (f & format_simple_base::FillNul) {
+ auto pos = str.begin();
+ if (str.length() > 0) {
+ if (str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('+')) {
+ pos++;
+ width++;
+ } else if (str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('-')) {
+ pos++;
+ width++;
+ }
+ }
+ if (str.length() < width) {
+ str.insert(pos, width - str.length(), mpt::unsafe_char_convert<typename Tstring::value_type>('0'));
+ }
+ }
+ return str;
+}
+
+
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4723) // potential divide by 0
+#endif // MPT_COMPILER_MSVC
+template <typename Tstring>
+inline Tstring format_simple_integer_postprocess_group(Tstring str, const format_simple_spec & format) {
+ if (format.GetGroup() > 0) {
+ const unsigned int groupSize = format.GetGroup();
+ const char groupSep = format.GetGroupSep();
+ std::size_t len = str.length();
+ for (std::size_t n = 0; n < len; ++n) {
+ if (n > 0 && (n % groupSize) == 0) {
+ if (!(n == (len - 1) && (str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('+') || str[0] == mpt::unsafe_char_convert<typename Tstring::value_type>('-')))) {
+ str.insert(str.begin() + (len - n), 1, mpt::unsafe_char_convert<typename Tstring::value_type>(groupSep));
+ }
+ }
+ }
+ }
+ return str;
+}
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif // MPT_COMPILER_MSVC
+
+
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_simple(const T & x, const format_simple_spec & format) {
+ int base = 10;
+ if (format.GetFlags() & format_simple_base::BaseDec) {
+ base = 10;
+ }
+ if (format.GetFlags() & format_simple_base::BaseHex) {
+ base = 16;
+ }
+ using format_string_type = typename mpt::select_format_string_type<Tstring>::type;
+ return mpt::transcode<Tstring>(mpt::format_simple_integer_postprocess_group(mpt::format_simple_integer_postprocess_digits(mpt::format_simple_integer_postprocess_case(mpt::format_simple_integer_to_chars<format_string_type>(x, base), format), format), format));
+}
+
+#else // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
+inline Tstring format_simple(const T & x, const format_simple_spec & format) {
+ int base = 10;
+ if (format.GetFlags() & format_simple_base::BaseDec) {
+ base = 10;
+ }
+ if (format.GetFlags() & format_simple_base::BaseHex) {
+ base = 16;
+ }
+ using format_string_type = typename mpt::select_format_string_type<Tstring>::type;
+ return mpt::transcode<Tstring>(mpt::format_simple_integer_postprocess_group(mpt::format_simple_integer_postprocess_digits(mpt::format_simple_integer_postprocess_case(mpt::format_simple_integer_to_stream<format_string_type>(x, base), format), format), format));
+}
+
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_SIMPLE_INTEGER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_spec.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_spec.hpp
new file mode 100644
index 00000000..797a6199
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/simple_spec.hpp
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_SIMPLE_SPEC_HPP
+#define MPT_FORMAT_SIMPLE_SPEC_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+struct format_simple_base {
+
+ enum FormatFlagsEnum {
+ BaseDec = 0x0001, // base 10 (integers only) // int+float
+ BaseHex = 0x0002, // base 16 (integers only) // int+float
+ CaseLow = 0x0010, // lower case hex digits // int+float
+ CaseUpp = 0x0020, // upper case hex digits // int+float
+ FillOff = 0x0100, // do not fill up width // int+float
+ FillNul = 0x0400, // fill up width with zeros // int+float
+ NotaNrm = 0x1000, // float: normal/default notation // float
+ NotaFix = 0x2000, // float: fixed point notation // float
+ NotaSci = 0x4000, // float: scientific notation // float
+ };
+
+}; // struct format_simple_base
+
+using format_simple_flags = unsigned int;
+
+static_assert(sizeof(format_simple_flags) >= sizeof(format_simple_base::FormatFlagsEnum));
+
+
+class format_simple_spec {
+private:
+ format_simple_flags flags;
+ std::size_t width; // int+float
+ int precision; // float
+ unsigned int group; // int
+ char group_sep; // int
+public:
+ MPT_CONSTEXPRINLINE format_simple_spec() noexcept
+ : flags(0)
+ , width(0)
+ , precision(-1)
+ , group(0)
+ , group_sep(',') { }
+ MPT_CONSTEXPRINLINE format_simple_flags GetFlags() const noexcept {
+ return flags;
+ }
+ MPT_CONSTEXPRINLINE std::size_t GetWidth() const noexcept {
+ return width;
+ }
+ MPT_CONSTEXPRINLINE int GetPrecision() const noexcept {
+ return precision;
+ }
+ MPT_CONSTEXPRINLINE unsigned int GetGroup() const noexcept {
+ return group;
+ }
+ MPT_CONSTEXPRINLINE char GetGroupSep() const noexcept {
+ return group_sep;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & SetFlags(format_simple_flags f) noexcept {
+ flags = f;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & SetWidth(std::size_t w) noexcept {
+ width = w;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & SetPrecision(int p) noexcept {
+ precision = p;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & SetGroup(unsigned int g) noexcept {
+ group = g;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & SetGroupSep(char s) noexcept {
+ group_sep = s;
+ return *this;
+ }
+
+public:
+ MPT_CONSTEXPRINLINE format_simple_spec & BaseDec() noexcept {
+ flags &= ~(format_simple_base::BaseDec | format_simple_base::BaseHex);
+ flags |= format_simple_base::BaseDec;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & BaseHex() noexcept {
+ flags &= ~(format_simple_base::BaseDec | format_simple_base::BaseHex);
+ flags |= format_simple_base::BaseHex;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & CaseLow() noexcept {
+ flags &= ~(format_simple_base::CaseLow | format_simple_base::CaseUpp);
+ flags |= format_simple_base::CaseLow;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & CaseUpp() noexcept {
+ flags &= ~(format_simple_base::CaseLow | format_simple_base::CaseUpp);
+ flags |= format_simple_base::CaseUpp;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FillOff() noexcept {
+ flags &= ~(format_simple_base::FillOff | format_simple_base::FillNul);
+ flags |= format_simple_base::FillOff;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FillNul() noexcept {
+ flags &= ~(format_simple_base::FillOff | format_simple_base::FillNul);
+ flags |= format_simple_base::FillNul;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & NotaNrm() noexcept {
+ flags &= ~(format_simple_base::NotaNrm | format_simple_base::NotaFix | format_simple_base::NotaSci);
+ flags |= format_simple_base::NotaNrm;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & NotaFix() noexcept {
+ flags &= ~(format_simple_base::NotaNrm | format_simple_base::NotaFix | format_simple_base::NotaSci);
+ flags |= format_simple_base::NotaFix;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & NotaSci() noexcept {
+ flags &= ~(format_simple_base::NotaNrm | format_simple_base::NotaFix | format_simple_base::NotaSci);
+ flags |= format_simple_base::NotaSci;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Width(std::size_t w) noexcept {
+ width = w;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Prec(int p) noexcept {
+ precision = p;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Group(unsigned int g) noexcept {
+ group = g;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & GroupSep(char s) noexcept {
+ group_sep = s;
+ return *this;
+ }
+
+public:
+ MPT_CONSTEXPRINLINE format_simple_spec & Dec() noexcept {
+ return BaseDec();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Hex() noexcept {
+ return BaseHex();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Low() noexcept {
+ return CaseLow();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Upp() noexcept {
+ return CaseUpp();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Off() noexcept {
+ return FillOff();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Nul() noexcept {
+ return FillNul();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Nrm() noexcept {
+ return NotaNrm();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Fix() noexcept {
+ return NotaFix();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Sci() noexcept {
+ return NotaSci();
+ }
+
+public:
+ MPT_CONSTEXPRINLINE format_simple_spec & Decimal() noexcept {
+ return BaseDec();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Hexadecimal() noexcept {
+ return BaseHex();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Lower() noexcept {
+ return CaseLow();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Upper() noexcept {
+ return CaseUpp();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FillNone() noexcept {
+ return FillOff();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FillZero() noexcept {
+ return FillNul();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FloatNormal() noexcept {
+ return NotaNrm();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FloatFixed() noexcept {
+ return NotaFix();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & FloatScientific() noexcept {
+ return NotaSci();
+ }
+ MPT_CONSTEXPRINLINE format_simple_spec & Precision(int p) noexcept {
+ return Prec(p);
+ }
+};
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_SIMPLE_SPEC_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_message.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_message.hpp
new file mode 100644
index 00000000..5de2dc13
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_message.hpp
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_TESTS_FORMAT_MESSAGE_HPP
+#define MPT_FORMAT_TESTS_FORMAT_MESSAGE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/message.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace format {
+namespace message {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/format/message")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ static_assert(mpt::parse_format_string_argument_count("") == 0);
+ static_assert(mpt::parse_format_string_argument_count("{{") == 0);
+ static_assert(mpt::parse_format_string_argument_count("}}") == 0);
+ static_assert(mpt::parse_format_string_argument_count("{}") == 1);
+ static_assert(mpt::parse_format_string_argument_count("{}{}") == 2);
+ static_assert(mpt::parse_format_string_argument_count("{0}{1}") == 2);
+
+ // basic
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{}{}{}")(1, 2, 3), "123");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{2}{1}{0}")(1, 2, 3), "321");
+
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{2}{1}{0}{4}{3}{6}{5}{7}{10}{9}{8}")(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a"), "21043657a98");
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE(L"{}{}{}")(1, 2, 3), L"123");
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+ // escaping behviour
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%")(), "%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%")(), "%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%%")(), "%%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{}")("a"), "a");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{}%")("a"), "a%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{}%")("a"), "a%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{}%%")("a"), "a%%");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%1")(), "%1");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%{}")("a"), "%a");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("%b")(), "%b");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{{}}")(), "{}");
+ MPT_TEST_EXPECT_EQUAL(MPT_AFORMAT_MESSAGE("{{{}}}")("a"), "{a}");
+}
+
+} // namespace message
+} // namespace format
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_TESTS_FORMAT_MESSAGE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_simple.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_simple.hpp
new file mode 100644
index 00000000..8956d0d6
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/format/tests/tests_format_simple.hpp
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_FORMAT_TESTS_FORMAT_SIMPLE_HPP
+#define MPT_FORMAT_TESTS_FORMAT_SIMPLE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/format/simple_integer.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace format {
+namespace simple {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/format/simple")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(1.5f), "1.5");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(true), "1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(false), "0");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(0), "0");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(-23), "-23");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(42), "42");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex0<3>((int32)-1), "-001");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex((int32)-1), "-1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex(-0xabcde), "-abcde");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex(std::numeric_limits<int32>::min()), "-80000000");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex(std::numeric_limits<int32>::min() + 1), "-7fffffff");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex(0x123e), "123e");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex0<6>(0x123e), "00123e");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex0<2>(0x123e), "123e");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<0>(1), "1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<1>(1), "1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<2>(1), "01");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<3>(1), "001");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<0>(11), "11");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<1>(11), "11");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<2>(11), "11");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<3>(11), "011");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<0>(-1), "-1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<1>(-1), "-1");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<2>(-1), "-01");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec0<3>(-1), "-001");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<7>(0xa2345678), MPT_USTRING("A2345678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<8>(0xa2345678), MPT_USTRING("A2345678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<9>(0xa2345678), MPT_USTRING("0A2345678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<10>(0xa2345678), MPT_USTRING("00A2345678"));
+
+#if MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_chars<std::string>(std::numeric_limits<int16>::min(), 10), "-32768");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_chars<std::string>(std::numeric_limits<int16>::max(), 10), "32767");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_chars<std::string>(std::numeric_limits<int16>::min(), 7), "-164351");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_chars<std::string>(std::numeric_limits<int16>::min() + 1, 7), "-164350");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_chars<std::string>(std::numeric_limits<int16>::max(), 7), "164350");
+
+#else // !MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_stream<std::string>(std::numeric_limits<int16>::min(), 10), "-32768");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_stream<std::string>(std::numeric_limits<int16>::max(), 10), "32767");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_stream<std::string>(std::numeric_limits<int16>::min(), 7), "-164351");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_stream<std::string>(std::numeric_limits<int16>::min() + 1, 7), "-164350");
+ MPT_TEST_EXPECT_EQUAL(mpt::format_simple_integer_to_stream<std::string>(std::numeric_limits<int16>::max(), 7), "164350");
+
+#endif // MPT_FORMAT_FORMAT_SIMPLE_INT_CXX17
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::hex(0x123e), L"123e");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::hex0<6>(0x123e), L"00123e");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::hex0<2>(0x123e), L"123e");
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::val(-87.0f), "-87");
+ if (mpt::format<std::string>::val(-0.5e-6) != "-5e-007"
+ && mpt::format<std::string>::val(-0.5e-6) != "-5e-07"
+ && mpt::format<std::string>::val(-0.5e-6) != "-5e-7"
+ && mpt::format<std::string>::val(-0.5e-6) != "-4.9999999999999998e-7"
+ && mpt::format<std::string>::val(-0.5e-6) != "-4.9999999999999998e-07"
+ && mpt::format<std::string>::val(-0.5e-6) != "-4.9999999999999998e-007")
+ {
+ MPT_TEST_EXPECT_EQUAL(true, false);
+ }
+ if (mpt::format<std::string>::val(-1.0 / 65536.0) != "-1.52587890625e-005"
+ && mpt::format<std::string>::val(-1.0 / 65536.0) != "-1.52587890625e-05"
+ && mpt::format<std::string>::val(-1.0 / 65536.0) != "-1.52587890625e-5")
+ {
+ MPT_TEST_EXPECT_EQUAL(true, false);
+ }
+ if (mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.52587891e-005"
+ && mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.52587891e-05"
+ && mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.52587891e-5"
+ && mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.5258789e-005"
+ && mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.5258789e-05"
+ && mpt::format<std::string>::val(-1.0f / 65536.0f) != "-1.5258789e-5")
+ {
+ MPT_TEST_EXPECT_EQUAL(true, false);
+ }
+ if (mpt::format<std::string>::val(58.65403492763) != "58.654034927630001"
+ && mpt::format<std::string>::val(58.65403492763) != "58.65403492763")
+ {
+ MPT_TEST_EXPECT_EQUAL(true, false);
+ }
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::flt(58.65403492763, 6), "58.654");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::fix(23.42, 1), "23.4");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::fix(234.2, 1), "234.2");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::fix(2342.0, 1), "2342.0");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec(2, ';', 2345678), std::string("2;34;56;78"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::dec(2, ';', 12345678), std::string("12;34;56;78"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::hex(3, ':', 0xa2345678), std::string("a2:345:678"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::dec(2, ';', 12345678), MPT_USTRING("12;34;56;78"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::hex(3, ':', 0xa2345678), MPT_USTRING("a2:345:678"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<7>(3, ':', 0xa2345678), MPT_USTRING("A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<8>(3, ':', 0xa2345678), MPT_USTRING("A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<9>(3, ':', 0xa2345678), MPT_USTRING("0A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<10>(3, ':', 0xa2345678), MPT_USTRING("0:0A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<11>(3, ':', 0xa2345678), MPT_USTRING("00:0A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<12>(3, ':', 0xa2345678), MPT_USTRING("000:0A2:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<7>(3, ':', -0x12345678), MPT_USTRING("-12:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<8>(3, ':', -0x12345678), MPT_USTRING("-12:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<9>(3, ':', -0x12345678), MPT_USTRING("-012:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<10>(3, ':', -0x12345678), MPT_USTRING("-0:012:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<11>(3, ':', -0x12345678), MPT_USTRING("-00:012:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<12>(3, ':', -0x12345678), MPT_USTRING("-000:012:345:678"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<5>(3, ':', 0x345678), MPT_USTRING("345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<6>(3, ':', 0x345678), MPT_USTRING("345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<7>(3, ':', 0x345678), MPT_USTRING("0:345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<5>(3, ':', -0x345678), MPT_USTRING("-345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<6>(3, ':', -0x345678), MPT_USTRING("-345:678"));
+ MPT_TEST_EXPECT_EQUAL(mpt::format<mpt::ustring>::HEX0<7>(3, ':', -0x345678), MPT_USTRING("-0:345:678"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::left(3, "a"), "a ");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::right(3, "a"), " a");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::center(3, "a"), " a ");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::center(4, "a"), " a ");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::flt(6.12345, 3), "6.12");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::fix(6.12345, 3), "6.123");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::flt(6.12345, 4), "6.123");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::string>::fix(6.12345, 4), "6.1235");
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::flt(6.12345, 3), L"6.12");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::fix(6.12345, 3), L"6.123");
+ MPT_TEST_EXPECT_EQUAL(mpt::format<std::wstring>::flt(6.12345, 4), L"6.123");
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+}
+
+} // namespace simple
+} // namespace format
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_FORMAT_TESTS_FORMAT_SIMPLE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/base.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/base.hpp
new file mode 100644
index 00000000..1ef286fd
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/base.hpp
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_BASE_HPP
+#define MPT_IO_BASE_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+using Offset = int64;
+
+inline constexpr std::size_t BUFFERSIZE_MINUSCULE = 1 * 256; // on stack usage, tuned for single word/line buffers
+inline constexpr std::size_t BUFFERSIZE_TINY = 1 * 1024; // on stack usage
+inline constexpr std::size_t BUFFERSIZE_SMALL = 4 * 1024; // on heap
+inline constexpr std::size_t BUFFERSIZE_NORMAL = 64 * 1024; // FILE I/O
+inline constexpr std::size_t BUFFERSIZE_LARGE = 1024 * 1024;
+
+
+
+template <typename Tfile, typename Enable = void>
+struct FileOperations {
+};
+
+template <typename Tfile>
+inline FileOperations<Tfile> FileOps(Tfile & f) {
+ ;
+ return FileOperations<Tfile>{f};
+}
+
+
+
+template <typename Tfile>
+inline bool IsValid(Tfile & f) {
+ return FileOps(f).IsValid();
+}
+
+template <typename Tfile>
+inline bool IsReadSeekable(Tfile & f) {
+ return FileOps(f).IsReadSeekable();
+}
+
+template <typename Tfile>
+inline bool IsWriteSeekable(Tfile & f) {
+ return FileOps(f).IsWriteSeekable();
+}
+
+template <typename Tfile>
+inline IO::Offset TellRead(Tfile & f) {
+ return FileOps(f).TellRead();
+}
+
+template <typename Tfile>
+inline IO::Offset TellWrite(Tfile & f) {
+ return FileOps(f).TellWrite();
+}
+
+template <typename Tfile>
+inline bool SeekBegin(Tfile & f) {
+ return FileOps(f).SeekBegin();
+}
+
+template <typename Tfile>
+inline bool SeekEnd(Tfile & f) {
+ return FileOps(f).SeekEnd();
+}
+
+template <typename Tfile>
+inline bool SeekAbsolute(Tfile & f, IO::Offset pos) {
+ return FileOps(f).SeekAbsolute(pos);
+}
+
+template <typename Tfile>
+inline bool SeekRelative(Tfile & f, IO::Offset off) {
+ return FileOps(f).SeekRelative(off);
+}
+
+template <typename Tfile>
+inline mpt::byte_span ReadRawImpl(Tfile & f, mpt::byte_span data) {
+ return FileOps(f).ReadRawImpl(data);
+}
+
+template <typename Tfile>
+inline bool WriteRawImpl(Tfile & f, mpt::const_byte_span data) {
+ return FileOps(f).WriteRawImpl(data);
+}
+
+template <typename Tfile>
+inline bool IsEof(Tfile & f) {
+ return FileOps(f).IsEof();
+}
+
+template <typename Tfile>
+inline bool Flush(Tfile & f) {
+ return FileOps(f).Flush();
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_BASE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/io.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io.hpp
new file mode 100644
index 00000000..1379f1e5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io.hpp
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_IO_HPP
+#define MPT_IO_IO_HPP
+
+
+
+#include "mpt/base/array.hpp"
+#include "mpt/base/bit.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/io/base.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename Tbyte, typename Tfile>
+inline mpt::byte_span ReadRaw(Tfile & f, Tbyte * data, std::size_t size) {
+ return mpt::IO::ReadRawImpl(f, mpt::as_span(mpt::byte_cast<std::byte *>(data), size));
+}
+
+template <typename Tbyte, typename Tfile>
+inline mpt::byte_span ReadRaw(Tfile & f, mpt::span<Tbyte> data) {
+ return mpt::IO::ReadRawImpl(f, mpt::byte_cast<mpt::byte_span>(data));
+}
+
+template <typename Tbyte, typename Tfile>
+inline bool WriteRaw(Tfile & f, const Tbyte * data, std::size_t size) {
+ return mpt::IO::WriteRawImpl(f, mpt::as_span(mpt::byte_cast<const std::byte *>(data), size));
+}
+
+template <typename Tbyte, typename Tfile>
+inline bool WriteRaw(Tfile & f, mpt::span<Tbyte> data) {
+ return mpt::IO::WriteRawImpl(f, mpt::byte_cast<mpt::const_byte_span>(data));
+}
+
+template <typename Tbinary, typename Tfile>
+inline bool Read(Tfile & f, Tbinary & v) {
+ return mpt::IO::ReadRaw(f, mpt::as_raw_memory(v)).size() == mpt::as_raw_memory(v).size();
+}
+
+template <typename Tbinary, typename Tfile>
+inline bool Write(Tfile & f, const Tbinary & v) {
+ return mpt::IO::WriteRaw(f, mpt::as_raw_memory(v));
+}
+
+template <typename Tbinary, typename Tfile>
+inline bool Write(Tfile & f, const std::vector<Tbinary> & v) {
+ static_assert(mpt::is_binary_safe<Tbinary>::value);
+ return mpt::IO::WriteRaw(f, mpt::as_raw_memory(v));
+}
+
+template <typename T, typename Tfile>
+inline bool WritePartial(Tfile & f, const T & v, std::size_t size = sizeof(T)) {
+ assert(size <= sizeof(T));
+ return mpt::IO::WriteRaw(f, mpt::as_span(mpt::as_raw_memory(v).data(), size));
+}
+
+template <typename Tfile>
+inline bool ReadByte(Tfile & f, std::byte & v) {
+ bool result = false;
+ std::byte byte = mpt::as_byte(0);
+ const std::size_t readResult = mpt::IO::ReadRaw(f, &byte, sizeof(std::byte)).size();
+ result = (readResult == sizeof(std::byte));
+ v = byte;
+ return result;
+}
+
+template <typename T, typename Tfile>
+inline bool ReadBinaryTruncatedLE(Tfile & f, T & v, std::size_t size) {
+ bool result = false;
+ static_assert(std::numeric_limits<T>::is_integer);
+ std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
+ const std::size_t readResult = mpt::IO::ReadRaw(f, bytes.data(), std::min(size, sizeof(T))).size();
+ result = (readResult == std::min(size, sizeof(T)));
+ v = mpt::bit_cast<typename mpt::make_le<T>::type>(bytes);
+ return result;
+}
+
+template <typename T, typename Tfile>
+inline bool ReadIntLE(Tfile & f, T & v) {
+ bool result = false;
+ static_assert(std::numeric_limits<T>::is_integer);
+ std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
+ const std::size_t readResult = mpt::IO::ReadRaw(f, mpt::as_span(bytes)).size();
+ result = (readResult == sizeof(T));
+ v = mpt::bit_cast<typename mpt::make_le<T>::type>(bytes);
+ return result;
+}
+
+template <typename T, typename Tfile>
+inline bool ReadIntBE(Tfile & f, T & v) {
+ bool result = false;
+ static_assert(std::numeric_limits<T>::is_integer);
+ std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
+ const std::size_t readResult = mpt::IO::ReadRaw(f, mpt::as_span(bytes)).size();
+ result = (readResult == sizeof(T));
+ v = mpt::bit_cast<typename mpt::make_be<T>::type>(bytes);
+ return result;
+}
+
+template <typename Tfile>
+inline bool ReadAdaptiveInt16LE(Tfile & f, uint16 & v) {
+ bool result = true;
+ uint8 byte = 0;
+ std::size_t additionalBytes = 0;
+ v = 0;
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ additionalBytes = (byte & 0x01);
+ v = byte >> 1;
+ for (std::size_t i = 0; i < additionalBytes; ++i) {
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ v |= (static_cast<uint16>(byte) << (((i + 1) * 8) - 1));
+ }
+ return result;
+}
+
+template <typename Tfile>
+inline bool ReadAdaptiveInt32LE(Tfile & f, uint32 & v) {
+ bool result = true;
+ uint8 byte = 0;
+ std::size_t additionalBytes = 0;
+ v = 0;
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ additionalBytes = (byte & 0x03);
+ v = byte >> 2;
+ for (std::size_t i = 0; i < additionalBytes; ++i) {
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ v |= (static_cast<uint32>(byte) << (((i + 1) * 8) - 2));
+ }
+ return result;
+}
+
+template <typename Tfile>
+inline bool ReadAdaptiveInt64LE(Tfile & f, uint64 & v) {
+ bool result = true;
+ uint8 byte = 0;
+ std::size_t additionalBytes = 0;
+ v = 0;
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ additionalBytes = (1 << (byte & 0x03)) - 1;
+ v = byte >> 2;
+ for (std::size_t i = 0; i < additionalBytes; ++i) {
+ byte = 0;
+ if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
+ result = false;
+ }
+ v |= (static_cast<uint64>(byte) << (((i + 1) * 8) - 2));
+ }
+ return result;
+}
+
+template <typename Tsize, typename Tfile>
+inline bool ReadSizedStringLE(Tfile & f, std::string & str, Tsize maxSize = std::numeric_limits<Tsize>::max()) {
+ static_assert(std::numeric_limits<Tsize>::is_integer);
+ str.clear();
+ Tsize size = 0;
+ if (!mpt::IO::ReadIntLE(f, size)) {
+ return false;
+ }
+ if (size > maxSize) {
+ return false;
+ }
+ for (Tsize i = 0; i != size; ++i) {
+ char c = '\0';
+ if (!mpt::IO::ReadIntLE(f, c)) {
+ return false;
+ }
+ str.push_back(c);
+ }
+ return true;
+}
+
+
+
+template <typename T, typename Tfile>
+inline bool WriteIntLE(Tfile & f, const T v) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::IO::Write(f, mpt::as_le(v));
+}
+
+template <typename T, typename Tfile>
+inline bool WriteIntBE(Tfile & f, const T v) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ return mpt::IO::Write(f, mpt::as_be(v));
+}
+
+template <typename Tfile>
+inline bool WriteAdaptiveInt16LE(Tfile & f, const uint16 v, std::size_t fixedSize = 0) {
+ std::size_t minSize = fixedSize;
+ std::size_t maxSize = fixedSize;
+ assert(minSize == 0 || minSize == 1 || minSize == 2);
+ assert(maxSize == 0 || maxSize == 1 || maxSize == 2);
+ assert(maxSize == 0 || maxSize >= minSize);
+ if (maxSize == 0) {
+ maxSize = 2;
+ }
+ if (v < 0x80 && minSize <= 1 && 1 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 1) | 0x00);
+ } else if (v < 0x8000 && minSize <= 2 && 2 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 1) | 0x01);
+ } else {
+ assert(false);
+ return false;
+ }
+}
+
+template <typename Tfile>
+inline bool WriteAdaptiveInt32LE(Tfile & f, const uint32 v, std::size_t fixedSize = 0) {
+ std::size_t minSize = fixedSize;
+ std::size_t maxSize = fixedSize;
+ assert(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 3 || minSize == 4);
+ assert(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 3 || maxSize == 4);
+ assert(maxSize == 0 || maxSize >= minSize);
+ if (maxSize == 0) {
+ maxSize = 4;
+ }
+ if (v < 0x40 && minSize <= 1 && 1 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
+ } else if (v < 0x4000 && minSize <= 2 && 2 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
+ } else if (v < 0x400000 && minSize <= 3 && 3 <= maxSize) {
+ uint32 value = static_cast<uint32>(v << 2) | 0x02;
+ std::byte bytes[3];
+ bytes[0] = static_cast<std::byte>(value >> 0);
+ bytes[1] = static_cast<std::byte>(value >> 8);
+ bytes[2] = static_cast<std::byte>(value >> 16);
+ return mpt::IO::WriteRaw(f, bytes, 3);
+ } else if (v < 0x40000000 && minSize <= 4 && 4 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x03);
+ } else {
+ assert(false);
+ return false;
+ }
+}
+
+template <typename Tfile>
+inline bool WriteAdaptiveInt64LE(Tfile & f, const uint64 v, std::size_t fixedSize = 0) {
+ std::size_t minSize = fixedSize;
+ std::size_t maxSize = fixedSize;
+ assert(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 4 || minSize == 8);
+ assert(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 4 || maxSize == 8);
+ assert(maxSize == 0 || maxSize >= minSize);
+ if (maxSize == 0) {
+ maxSize = 8;
+ }
+ if (v < 0x40 && minSize <= 1 && 1 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
+ } else if (v < 0x4000 && minSize <= 2 && 2 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
+ } else if (v < 0x40000000 && minSize <= 4 && 4 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x02);
+ } else if (v < 0x4000000000000000ull && minSize <= 8 && 8 <= maxSize) {
+ return mpt::IO::WriteIntLE<uint64>(f, static_cast<uint64>(v << 2) | 0x03);
+ } else {
+ assert(false);
+ return false;
+ }
+}
+
+// Write a variable-length integer, as found in MIDI files. The number of written bytes is placed in the bytesWritten parameter.
+template <typename Tfile, typename T>
+bool WriteVarInt(Tfile & f, const T v, std::size_t * bytesWritten = nullptr) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ static_assert(!std::numeric_limits<T>::is_signed);
+ std::byte out[(sizeof(T) * 8 + 6) / 7];
+ std::size_t numBytes = 0;
+ for (uint32 n = (sizeof(T) * 8) / 7; n > 0; n--) {
+ if (v >= (static_cast<T>(1) << (n * 7u))) {
+ out[numBytes++] = static_cast<std::byte>(((v >> (n * 7u)) & 0x7F) | 0x80);
+ }
+ }
+ out[numBytes++] = static_cast<std::byte>(v & 0x7F);
+ assert(numBytes <= std::size(out));
+ if (bytesWritten != nullptr) {
+ *bytesWritten = numBytes;
+ }
+ return mpt::IO::WriteRaw(f, out, numBytes);
+}
+
+template <typename Tsize, typename Tfile>
+inline bool WriteSizedStringLE(Tfile & f, const std::string & str) {
+ static_assert(std::numeric_limits<Tsize>::is_integer);
+ if (str.size() > std::numeric_limits<Tsize>::max()) {
+ return false;
+ }
+ Tsize size = static_cast<Tsize>(str.size());
+ if (!mpt::IO::WriteIntLE(f, size)) {
+ return false;
+ }
+ if (!mpt::IO::WriteRaw(f, str.data(), str.size())) {
+ return false;
+ }
+ return true;
+}
+
+template <typename Tfile>
+inline bool WriteText(Tfile & f, const std::string & s) {
+ return mpt::IO::WriteRaw(f, s.data(), s.size());
+}
+
+template <typename Tfile>
+inline bool WriteTextCRLF(Tfile & f) {
+ return mpt::IO::WriteText(f, "\r\n");
+}
+
+template <typename Tfile>
+inline bool WriteTextLF(Tfile & f) {
+ return mpt::IO::WriteText(f, "\n");
+}
+
+template <typename Tfile>
+inline bool WriteTextCRLF(Tfile & f, const std::string & s) {
+ return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextCRLF(f);
+}
+
+template <typename Tfile>
+inline bool WriteTextLF(Tfile & f, const std::string & s) {
+ return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextLF(f);
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_IO_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_span.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_span.hpp
new file mode 100644
index 00000000..94cdaa99
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_span.hpp
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_IO_SPAN_HPP
+#define MPT_IO_IO_SPAN_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io/base.hpp"
+
+#include <algorithm>
+#include <utility>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename Tbyte>
+struct FileOperations<std::pair<mpt::span<Tbyte>, IO::Offset>> {
+
+private:
+ std::pair<mpt::span<Tbyte>, IO::Offset> & f;
+
+public:
+ FileOperations(std::pair<mpt::span<Tbyte>, IO::Offset> & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return (f.second >= 0);
+ }
+
+ inline bool IsReadSeekable() {
+ MPT_UNUSED(f);
+ return true;
+ }
+
+ inline bool IsWriteSeekable() {
+ MPT_UNUSED(f);
+ return true;
+ }
+
+ inline IO::Offset TellRead() {
+ return f.second;
+ }
+
+ inline IO::Offset TellWrite() {
+ return f.second;
+ }
+
+ inline bool SeekBegin() {
+ f.second = 0;
+ return true;
+ }
+
+ inline bool SeekEnd() {
+ f.second = f.first.size();
+ return true;
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ f.second = pos;
+ return true;
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ if (f.second < 0)
+ {
+ return false;
+ }
+ f.second += off;
+ return true;
+ }
+
+ inline mpt::byte_span ReadRawImpl(mpt::byte_span data) {
+ if (f.second < 0)
+ {
+ return data.first(0);
+ }
+ if (f.second >= static_cast<IO::Offset>(f.first.size()))
+ {
+ return data.first(0);
+ }
+ std::size_t num = mpt::saturate_cast<std::size_t>(std::min(static_cast<IO::Offset>(f.first.size()) - f.second, static_cast<IO::Offset>(data.size())));
+ std::copy(mpt::byte_cast<const std::byte *>(f.first.data() + f.second), mpt::byte_cast<const std::byte *>(f.first.data() + f.second + num), data.data());
+ f.second += num;
+ return data.first(num);
+ }
+
+ inline bool WriteRawImpl(mpt::const_byte_span data) {
+ if (f.second < 0)
+ {
+ return false;
+ }
+ if (f.second > static_cast<IO::Offset>(f.first.size()))
+ {
+ return false;
+ }
+ std::size_t num = mpt::saturate_cast<std::size_t>(std::min(static_cast<IO::Offset>(f.first.size()) - f.second, static_cast<IO::Offset>(data.size())));
+ if (num != data.size())
+ {
+ return false;
+ }
+ std::copy(data.data(), data.data() + num, mpt::byte_cast<std::byte *>(f.first.data() + f.second));
+ f.second += num;
+ return true;
+ }
+
+ inline bool IsEof() {
+ return (f.second >= static_cast<IO::Offset>(f.first.size()));
+ }
+
+ inline bool Flush() {
+ MPT_UNUSED(f);
+ return true;
+ }
+};
+
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_IO_SPAN_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_stdstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_stdstream.hpp
new file mode 100644
index 00000000..23f6602f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_stdstream.hpp
@@ -0,0 +1,332 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_IO_STDSTREAM_HPP
+#define MPT_IO_IO_STDSTREAM_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/io/base.hpp"
+
+#include <ios>
+#include <istream>
+#include <ostream>
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+//static_assert(sizeof(std::streamoff) == 8); // Assert 64bit file support.
+
+struct FileOperationsStdIos {
+
+private:
+ std::ios & f;
+
+public:
+ FileOperationsStdIos(std::ios & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return !f.fail();
+ }
+};
+
+struct FileOperationsStdIstream
+ : public FileOperationsStdIos {
+
+private:
+ std::istream & f;
+
+public:
+ FileOperationsStdIstream(std::istream & f_)
+ : FileOperationsStdIos(f_)
+ , f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsReadSeekable() {
+ f.clear();
+ std::streampos oldpos = f.tellg();
+ if (f.fail() || oldpos == std::streampos(-1))
+ {
+ f.clear();
+ return false;
+ }
+ f.seekg(0, std::ios::beg);
+ if (f.fail())
+ {
+ f.clear();
+ f.seekg(oldpos);
+ f.clear();
+ return false;
+ }
+ f.seekg(0, std::ios::end);
+ if (f.fail())
+ {
+ f.clear();
+ f.seekg(oldpos);
+ f.clear();
+ return false;
+ }
+ std::streampos length = f.tellg();
+ if (f.fail() || length == std::streampos(-1))
+ {
+ f.clear();
+ f.seekg(oldpos);
+ f.clear();
+ return false;
+ }
+ f.seekg(oldpos);
+ f.clear();
+ return true;
+ }
+
+ inline IO::Offset TellRead() {
+ return f.tellg();
+ }
+
+ inline bool SeekBegin() {
+ f.seekg(0);
+ return !f.fail();
+ }
+
+ inline bool SeekEnd() {
+ f.seekg(0, std::ios::end);
+ return !f.fail();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ if (!mpt::in_range<std::streamoff>(pos))
+ {
+ return false;
+ }
+ f.seekg(static_cast<std::streamoff>(pos), std::ios::beg);
+ return !f.fail();
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ if (!mpt::in_range<std::streamoff>(off))
+ {
+ return false;
+ }
+ f.seekg(static_cast<std::streamoff>(off), std::ios::cur);
+ return !f.fail();
+ }
+
+ inline mpt::byte_span ReadRawImpl(mpt::byte_span data) {
+ f.read(mpt::byte_cast<char *>(data.data()), data.size());
+ return data.first(mpt::saturate_cast<std::size_t>(f.gcount()));
+ }
+
+ inline bool IsEof() {
+ return f.eof();
+ }
+};
+
+struct FileOperationsStdOstream
+ : public FileOperationsStdIos {
+
+private:
+ std::ostream & f;
+
+public:
+ FileOperationsStdOstream(std::ostream & f_)
+ : FileOperationsStdIos(f_)
+ , f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsWriteSeekable() {
+ f.clear();
+ std::streampos oldpos = f.tellp();
+ if (f.fail() || oldpos == std::streampos(-1))
+ {
+ f.clear();
+ return false;
+ }
+ f.seekp(0, std::ios::beg);
+ if (f.fail())
+ {
+ f.clear();
+ f.seekp(oldpos);
+ f.clear();
+ return false;
+ }
+ f.seekp(0, std::ios::end);
+ if (f.fail())
+ {
+ f.clear();
+ f.seekp(oldpos);
+ f.clear();
+ return false;
+ }
+ std::streampos length = f.tellp();
+ if (f.fail() || length == std::streampos(-1))
+ {
+ f.clear();
+ f.seekp(oldpos);
+ f.clear();
+ return false;
+ }
+ f.seekp(oldpos);
+ f.clear();
+ return true;
+ }
+
+ inline IO::Offset TellWrite() {
+ return f.tellp();
+ }
+
+ inline bool SeekBegin() {
+ f.seekp(0);
+ return !f.fail();
+ }
+
+ inline bool SeekEnd() {
+ f.seekp(0, std::ios::end);
+ return !f.fail();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ if (!mpt::in_range<std::streamoff>(pos))
+ {
+ return false;
+ }
+ f.seekp(static_cast<std::streamoff>(pos), std::ios::beg);
+ return !f.fail();
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ if (!mpt::in_range<std::streamoff>(off))
+ {
+ return false;
+ }
+ f.seekp(static_cast<std::streamoff>(off), std::ios::cur);
+ return !f.fail();
+ }
+
+ inline bool WriteRawImpl(mpt::const_byte_span data) {
+ f.write(mpt::byte_cast<const char *>(data.data()), data.size());
+ return !f.fail();
+ }
+
+ inline bool Flush() {
+ f.flush();
+ return !f.fail();
+ }
+};
+
+struct FileOperationsStdIOstream
+ : public FileOperationsStdIstream
+ , public FileOperationsStdOstream {
+
+private:
+ std::iostream & f;
+
+public:
+ FileOperationsStdIOstream(std::iostream & f_)
+ : FileOperationsStdIstream(f_)
+ , FileOperationsStdOstream(f_)
+ , f(f_) {
+ return;
+ }
+
+public:
+ inline bool SeekBegin() {
+ FileOperationsStdIstream::SeekBegin();
+ FileOperationsStdOstream::SeekBegin();
+ return !f.fail();
+ }
+
+ inline bool SeekEnd() {
+ FileOperationsStdIstream::SeekEnd();
+ FileOperationsStdOstream::SeekEnd();
+ return !f.fail();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ if (!mpt::in_range<std::streamoff>(pos))
+ {
+ return false;
+ }
+ FileOperationsStdIstream::SeekAbsolute(pos);
+ FileOperationsStdOstream::SeekAbsolute(pos);
+ return !f.fail();
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ if (!mpt::in_range<std::streamoff>(off))
+ {
+ return false;
+ }
+ FileOperationsStdIstream::SeekRelative(off);
+ FileOperationsStdOstream::SeekRelative(off);
+ return !f.fail();
+ }
+};
+
+
+
+template <typename Tstream>
+struct FileOperations<Tstream, typename std::enable_if_t<std::is_base_of<std::iostream, Tstream>::value>>
+ : public FileOperationsStdIOstream {
+public:
+ FileOperations(Tstream & f)
+ : FileOperationsStdIOstream(f) {
+ return;
+ }
+};
+
+
+
+template <typename Tstream>
+struct FileOperations<Tstream, typename std::enable_if_t<std::is_base_of<std::istream, Tstream>::value && !std::is_base_of<std::iostream, Tstream>::value>>
+ : public FileOperationsStdIstream {
+public:
+ FileOperations(Tstream & f)
+ : FileOperationsStdIstream(f) {
+ return;
+ }
+};
+
+
+
+template <typename Tstream>
+struct FileOperations<Tstream, typename std::enable_if_t<std::is_base_of<std::ostream, Tstream>::value && !std::is_base_of<std::iostream, Tstream>::value>>
+ : public FileOperationsStdOstream {
+public:
+ FileOperations(Tstream & f)
+ : FileOperationsStdOstream(f) {
+ return;
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_IO_STDSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_virtual_wrapper.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_virtual_wrapper.hpp
new file mode 100644
index 00000000..ad65c548
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/io_virtual_wrapper.hpp
@@ -0,0 +1,405 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_IO_VIRTUAL_WRAPPER_HPP
+#define MPT_IO_IO_VIRTUAL_WRAPPER_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io/base.hpp"
+
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class IFileBase {
+protected:
+ IFileBase() = default;
+ virtual ~IFileBase() = default;
+
+public:
+ virtual bool IsValid() = 0;
+ virtual bool IsReadSeekable() = 0;
+ virtual IO::Offset TellRead() = 0;
+ virtual bool SeekBegin() = 0;
+ virtual bool SeekEnd() = 0;
+ virtual bool SeekAbsolute(IO::Offset pos) = 0;
+ virtual bool SeekRelative(IO::Offset off) = 0;
+ virtual mpt::byte_span ReadRawImpl(mpt::byte_span data) = 0;
+ virtual bool IsEof() = 0;
+};
+
+template <typename Tfile>
+class IFile
+ : public IFileBase {
+private:
+ Tfile & f;
+
+public:
+ IFile(Tfile & f_)
+ : f(f_) {
+ }
+ ~IFile() override = default;
+
+public:
+ bool IsValid() override {
+ return mpt::IO::IsValid(f);
+ }
+ bool IsReadSeekable() override {
+ return mpt::IO::IsReadSeekable(f);
+ }
+ IO::Offset TellRead() override {
+ return mpt::IO::TellRead(f);
+ }
+ bool SeekBegin() override {
+ return mpt::IO::SeekBegin(f);
+ }
+ bool SeekEnd() override {
+ return mpt::IO::SeekEnd(f);
+ }
+ bool SeekAbsolute(IO::Offset pos) override {
+ return mpt::IO::SeekAbsolute(f, pos);
+ }
+ bool SeekRelative(IO::Offset off) override {
+ return mpt::IO::SeekRelative(f, off);
+ }
+ mpt::byte_span ReadRawImpl(mpt::byte_span data) override {
+ return mpt::IO::ReadRawImpl(f, data);
+ }
+ bool IsEof() override {
+ return mpt::IO::IsEof(f);
+ }
+};
+
+
+
+class OFileBase {
+protected:
+ OFileBase() = default;
+ virtual ~OFileBase() = default;
+
+public:
+ virtual bool IsValid() = 0;
+ virtual bool IsWriteSeekable() = 0;
+ virtual IO::Offset TellWrite() = 0;
+ virtual bool SeekBegin() = 0;
+ virtual bool SeekEnd() = 0;
+ virtual bool SeekAbsolute(IO::Offset pos) = 0;
+ virtual bool SeekRelative(IO::Offset off) = 0;
+ virtual bool WriteRawImpl(mpt::const_byte_span data) = 0;
+ virtual bool Flush() = 0;
+};
+
+template <typename Tfile>
+class OFile
+ : public OFileBase {
+private:
+ Tfile & f;
+
+public:
+ OFile(Tfile & f_)
+ : f(f_) {
+ }
+ ~OFile() override = default;
+
+public:
+ bool IsValid() override {
+ return mpt::IO::IsValid(f);
+ }
+ bool IsWriteSeekable() override {
+ return mpt::IO::IsWriteSeekable(f);
+ }
+ IO::Offset TellWrite() override {
+ return mpt::IO::TellWrite(f);
+ }
+ bool SeekBegin() override {
+ return mpt::IO::SeekBegin(f);
+ }
+ bool SeekEnd() override {
+ return mpt::IO::SeekEnd(f);
+ }
+ bool SeekAbsolute(IO::Offset pos) override {
+ return mpt::IO::SeekAbsolute(f, pos);
+ }
+ bool SeekRelative(IO::Offset off) override {
+ return mpt::IO::SeekRelative(f, off);
+ }
+ bool WriteRawImpl(mpt::const_byte_span data) override {
+ return mpt::IO::WriteRawImpl(f, data);
+ }
+ bool Flush() override {
+ return mpt::IO::Flush(f);
+ }
+};
+
+
+
+class IOFileBase {
+protected:
+ IOFileBase() = default;
+ virtual ~IOFileBase() = default;
+
+public:
+ virtual bool IsValid() = 0;
+ virtual bool IsReadSeekable() = 0;
+ virtual bool IsWriteSeekable() = 0;
+ virtual IO::Offset TellRead() = 0;
+ virtual IO::Offset TellWrite() = 0;
+ virtual bool SeekBegin() = 0;
+ virtual bool SeekEnd() = 0;
+ virtual bool SeekAbsolute(IO::Offset pos) = 0;
+ virtual bool SeekRelative(IO::Offset off) = 0;
+ virtual mpt::byte_span ReadRawImpl(mpt::byte_span data) = 0;
+ virtual bool WriteRawImpl(mpt::const_byte_span data) = 0;
+ virtual bool IsEof() = 0;
+ virtual bool Flush() = 0;
+};
+
+template <typename Tfile>
+class IOFile
+ : public IOFileBase {
+private:
+ Tfile & f;
+
+public:
+ IOFile(Tfile & f_)
+ : f(f_) {
+ }
+ ~IOFile() override = default;
+
+public:
+ bool IsValid() override {
+ return mpt::IO::IsValid(f);
+ }
+ bool IsReadSeekable() override {
+ return mpt::IO::IsReadSeekable(f);
+ }
+ bool IsWriteSeekable() override {
+ return mpt::IO::IsWriteSeekable(f);
+ }
+ IO::Offset TellRead() override {
+ return mpt::IO::TellRead(f);
+ }
+ IO::Offset TellWrite() override {
+ return mpt::IO::TellWrite(f);
+ }
+ bool SeekBegin() override {
+ return mpt::IO::SeekBegin(f);
+ }
+ bool SeekEnd() override {
+ return mpt::IO::SeekEnd(f);
+ }
+ bool SeekAbsolute(IO::Offset pos) override {
+ return mpt::IO::SeekAbsolute(f, pos);
+ }
+ bool SeekRelative(IO::Offset off) override {
+ return mpt::IO::SeekRelative(f, off);
+ }
+ mpt::byte_span ReadRawImpl(mpt::byte_span data) override {
+ return mpt::IO::ReadRawImpl(f, data);
+ }
+ bool WriteRawImpl(mpt::const_byte_span data) override {
+ return mpt::IO::WriteRawImpl(f, data);
+ }
+ bool IsEof() override {
+ return mpt::IO::IsEof(f);
+ }
+ bool Flush() override {
+ return mpt::IO::Flush(f);
+ }
+};
+
+
+
+template <typename Tfile>
+struct FileOperations<Tfile, typename std::enable_if_t<std::is_base_of<IFileBase, Tfile>::value>> {
+
+private:
+ IFileBase & f;
+
+public:
+ FileOperations(IFileBase & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return f.IsValid();
+ }
+
+ inline bool IsReadSeekable() {
+ return f.IsReadSeekable();
+ }
+
+ inline IO::Offset TellRead() {
+ return f.TellRead();
+ }
+
+ inline bool SeekBegin() {
+ return f.SeekBegin();
+ }
+
+ inline bool SeekEnd() {
+ return f.SeekEnd();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ return f.SeekAbsolute(pos);
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ return f.SeekRelative(off);
+ }
+
+ inline mpt::byte_span ReadRawImpl(mpt::byte_span data) {
+ return f.ReadRawImpl(data);
+ }
+
+ inline bool IsEof() {
+ return f.IsEof();
+ }
+};
+
+
+
+template <typename Tfile>
+struct FileOperations<Tfile, typename std::enable_if_t<std::is_base_of<OFileBase, Tfile>::value>> {
+
+private:
+ OFileBase & f;
+
+public:
+ FileOperations(OFileBase & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return f.IsValid();
+ }
+
+ inline bool IsWriteSeekable() {
+ return f.IsWriteSeekable();
+ }
+
+ inline IO::Offset TellWrite() {
+ return f.TellWrite();
+ }
+
+ inline bool SeekBegin() {
+ return f.SeekBegin();
+ }
+
+ inline bool SeekEnd() {
+ return f.SeekEnd();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ return f.SeekAbsolute(pos);
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ return f.SeekRelative(off);
+ }
+
+ inline bool WriteRawImpl(mpt::const_byte_span data) {
+ return f.WriteRawImpl(data);
+ }
+
+ inline bool Flush() {
+ return f.Flush();
+ }
+};
+
+
+
+template <typename Tfile>
+struct FileOperations<Tfile, typename std::enable_if_t<std::is_base_of<IOFileBase, Tfile>::value>> {
+
+private:
+ IOFileBase & f;
+
+public:
+ FileOperations(IOFileBase & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return f.IsValid();
+ }
+
+ inline bool IsReadSeekable() {
+ return f.IsReadSeekable();
+ }
+
+ inline bool IsWriteSeekable() {
+ return f.IsWriteSeekable();
+ }
+
+ inline IO::Offset TellRead() {
+ return f.TellRead();
+ }
+
+ inline IO::Offset TellWrite() {
+ return f.TellWrite();
+ }
+
+ inline bool SeekBegin() {
+ return f.SeekBegin();
+ }
+
+ inline bool SeekEnd() {
+ return f.SeekEnd();
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ return f.SeekAbsolute(pos);
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ return f.SeekRelative(off);
+ }
+
+ inline mpt::byte_span ReadRawImpl(mpt::byte_span data) {
+ return f.ReadRawImpl(data);
+ }
+
+ inline bool WriteRawImpl(mpt::const_byte_span data) {
+ return f.WriteRawImpl(data);
+ }
+
+ inline bool IsEof() {
+ return f.IsEof();
+ }
+
+ inline bool Flush() {
+ return f.Flush();
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_IO_VIRTUAL_WRAPPER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io/tests/tests_io.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io/tests/tests_io.hpp
new file mode 100644
index 00000000..38e2a695
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io/tests/tests_io.hpp
@@ -0,0 +1,573 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_TESTS_IO_HPP
+#define MPT_IO_TESTS_IO_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <ios>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace io {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/io")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+
+ // check that empty stringstream behaves correctly with our MSVC workarounds when using iostream interface directly
+
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(ss.tellp(), std::streampos(0));
+ }
+ {
+ std::ostringstream ss;
+ ss.seekp(0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::ostringstream ss;
+ ss.seekp(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!ss.fail(), true);
+ }
+ {
+ std::ostringstream ss;
+ ss.seekp(0, std::ios_base::cur);
+ MPT_TEST_EXPECT_EQUAL(!ss.fail(), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(ss.tellg(), std::streampos(0));
+ }
+ {
+ std::istringstream ss;
+ ss.seekg(0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::istringstream ss;
+ ss.seekg(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!ss.fail(), true);
+ }
+ {
+ std::istringstream ss;
+ ss.seekg(0, std::ios_base::cur);
+ MPT_TEST_EXPECT_EQUAL(!ss.fail(), true);
+ }
+
+ {
+ std::ostringstream s;
+ char b = 23;
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellp(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekp(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellp(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.write(&b, 1);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellp(), std::streampos(1));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekp(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellp(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekp(0, std::ios_base::end);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellp(), std::streampos(1));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.str(), std::string(1, b));
+ }
+
+ {
+ std::istringstream s;
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekg(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekg(0, std::ios_base::end);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ }
+
+ {
+ std::istringstream s("a");
+ char a = 0;
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekg(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.read(&a, 1);
+ MPT_TEST_EXPECT_EQUAL(a, 'a');
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(1));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekg(0, std::ios_base::beg);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(0));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ s.seekg(0, std::ios_base::end);
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(s.tellg(), std::streampos(1));
+ MPT_TEST_EXPECT_EQUAL(!s.fail(), true);
+ MPT_TEST_EXPECT_EQUAL(std::string(1, a), std::string(1, 'a'));
+ }
+
+ // check that empty native and fixed stringstream both behaves correctly with out IO functions
+
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(ss), 0);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(ss), true);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekRelative(ss, 0), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(ss), 0);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(ss), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekRelative(ss, 0), true);
+ }
+
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(ss), 0);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(ss), true);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::ostringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekRelative(ss, 0), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(ss), 0);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(ss), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekAbsolute(ss, 0), true);
+ }
+ {
+ std::istringstream ss;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekRelative(ss, 0), true);
+ }
+
+ {
+ std::ostringstream s;
+ char b = 23;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::WriteRaw(s, &b, 1), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(s.str(), std::string(1, b));
+ }
+
+ {
+ std::istringstream s;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ }
+
+ {
+ std::istringstream s("a");
+ char a = 0;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::ReadRaw(s, &a, 1).size(), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(std::string(1, a), std::string(1, 'a'));
+ }
+
+ {
+ std::ostringstream s;
+ char b = 23;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::WriteRaw(s, &b, 1), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(s.str(), std::string(1, b));
+ }
+
+ {
+ std::istringstream s;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ }
+
+ {
+ std::istringstream s("a");
+ char a = 0;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::ReadRaw(s, &a, 1).size(), 1u);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekBegin(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 0);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::SeekEnd(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellRead(s), 1);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::IsValid(s), true);
+ MPT_TEST_EXPECT_EQUAL(std::string(1, a), std::string(1, 'a'));
+ }
+
+ // General file I/O tests
+ {
+ // Verify that writing arrays does not confuse the compiler.
+ // This is both, compile-time and run-time cheking.
+ // Run-time in case some weird compiler gets confused by our templates
+ // and only writes the first array element.
+ std::ostringstream f;
+ uint16be data[2];
+ mpt::reset(data);
+ data[0] = 0x1234;
+ data[1] = 0x5678;
+ mpt::IO::Write(f, data);
+ MPT_TEST_EXPECT_EQUAL(f.str(), std::string("\x12\x34\x56\x78"));
+ }
+ {
+ std::ostringstream f;
+ std::vector<int16be> data;
+ data.resize(3);
+ data[0] = 0x1234;
+ data[1] = 0x5678;
+ data[2] = 0x1234;
+ mpt::IO::Write(f, data);
+ MPT_TEST_EXPECT_EQUAL(f.str(), std::string("\x12\x34\x56\x78\x12\x34"));
+ }
+ {
+ std::ostringstream f;
+ int16be data[3];
+ mpt::reset(data);
+ data[0] = 0x1234;
+ data[1] = 0x5678;
+ data[2] = 0x1234;
+ mpt::IO::Write(f, data);
+ MPT_TEST_EXPECT_EQUAL(f.str(), std::string("\x12\x34\x56\x78\x12\x34"));
+ }
+
+ {
+ auto TestAdaptive16 = [&](uint16 value, mpt::IO::Offset expected_size, std::size_t fixedSize, const char * bytes) {
+ std::stringstream f;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::WriteAdaptiveInt16LE(f, value, fixedSize), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(f), expected_size);
+ if (bytes) {
+ mpt::IO::SeekBegin(f);
+ for (mpt::IO::Offset i = 0; i < expected_size; ++i) {
+ uint8 val = 0;
+ mpt::IO::ReadIntLE<uint8>(f, val);
+ MPT_TEST_EXPECT_EQUAL(val, static_cast<uint8>(bytes[i]));
+ }
+ }
+ mpt::IO::SeekBegin(f);
+ uint16 result = 0;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::ReadAdaptiveInt16LE(f, result), true);
+ MPT_TEST_EXPECT_EQUAL(result, value);
+ };
+ auto TestAdaptive32 = [&](uint32 value, mpt::IO::Offset expected_size, std::size_t fixedSize, const char * bytes) {
+ std::stringstream f;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::WriteAdaptiveInt32LE(f, value, fixedSize), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(f), expected_size);
+ if (bytes) {
+ mpt::IO::SeekBegin(f);
+ for (mpt::IO::Offset i = 0; i < expected_size; ++i) {
+ uint8 val = 0;
+ mpt::IO::ReadIntLE<uint8>(f, val);
+ MPT_TEST_EXPECT_EQUAL(val, static_cast<uint8>(bytes[i]));
+ }
+ }
+ mpt::IO::SeekBegin(f);
+ uint32 result = 0;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::ReadAdaptiveInt32LE(f, result), true);
+ MPT_TEST_EXPECT_EQUAL(result, value);
+ };
+ auto TestAdaptive64 = [&](uint64 value, mpt::IO::Offset expected_size, std::size_t fixedSize, const char * bytes) {
+ std::stringstream f;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::WriteAdaptiveInt64LE(f, value, fixedSize), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::TellWrite(f), expected_size);
+ if (bytes) {
+ mpt::IO::SeekBegin(f);
+ for (mpt::IO::Offset i = 0; i < expected_size; ++i) {
+ uint8 val = 0;
+ mpt::IO::ReadIntLE<uint8>(f, val);
+ MPT_TEST_EXPECT_EQUAL(val, static_cast<uint8>(bytes[i]));
+ }
+ }
+ mpt::IO::SeekBegin(f);
+ uint64 result = 0;
+ MPT_TEST_EXPECT_EQUAL(mpt::IO::ReadAdaptiveInt64LE(f, result), true);
+ MPT_TEST_EXPECT_EQUAL(result, value);
+ };
+ TestAdaptive16(0, 1, 0, "\x00");
+ TestAdaptive16(1, 1, 0, "\x02");
+ TestAdaptive16(2, 1, 0, nullptr);
+ TestAdaptive16(0x7f, 1, 0, nullptr);
+ TestAdaptive16(0x80, 2, 0, "\x01\x01");
+ TestAdaptive16(0x81, 2, 0, "\x03\x01");
+ TestAdaptive16(0x7fff, 2, 0, "\xff\xff");
+ TestAdaptive16(0, 1, 1, nullptr);
+ TestAdaptive16(1, 1, 1, nullptr);
+ TestAdaptive16(2, 1, 1, nullptr);
+ TestAdaptive16(0x7f, 1, 1, nullptr);
+ TestAdaptive16(0x80, 2, 0, nullptr);
+ TestAdaptive16(0x81, 2, 0, nullptr);
+ TestAdaptive16(0x7fff, 2, 0, nullptr);
+ TestAdaptive16(0, 2, 2, "\x01\x00");
+ TestAdaptive16(1, 2, 2, "\x03\x00");
+ TestAdaptive16(2, 2, 2, nullptr);
+ TestAdaptive16(0x7f, 2, 2, nullptr);
+ TestAdaptive16(0x80, 2, 2, nullptr);
+ TestAdaptive16(0x81, 2, 2, nullptr);
+ TestAdaptive16(0x7fff, 2, 2, nullptr);
+
+ TestAdaptive32(0, 1, 0, "\x00");
+ TestAdaptive32(1, 1, 0, nullptr);
+ TestAdaptive32(2, 1, 0, nullptr);
+ TestAdaptive32(0x3f, 1, 0, nullptr);
+ TestAdaptive32(0x40, 2, 0, "\x01\x01");
+ TestAdaptive32(0x41, 2, 0, "\x05\x01");
+ TestAdaptive32(0x7f, 2, 0, nullptr);
+ TestAdaptive32(0x80, 2, 0, nullptr);
+ TestAdaptive32(0x3fff, 2, 0, nullptr);
+ TestAdaptive32(0x4000, 3, 0, "\x02\x00\x01");
+ TestAdaptive32(0x4001, 3, 0, nullptr);
+ TestAdaptive32(0x3fffff, 3, 0, nullptr);
+ TestAdaptive32(0x400000, 4, 0, "\x03\x00\x00\x01");
+ TestAdaptive32(0x400001, 4, 0, nullptr);
+ TestAdaptive32(0x3fffffff, 4, 0, "\xff\xff\xff\xff");
+ TestAdaptive32(0, 2, 2, nullptr);
+ TestAdaptive32(1, 2, 2, nullptr);
+ TestAdaptive32(2, 2, 2, nullptr);
+ TestAdaptive32(0x3f, 2, 2, nullptr);
+ TestAdaptive32(0x40, 2, 2, nullptr);
+ TestAdaptive32(0x41, 2, 2, nullptr);
+ TestAdaptive32(0x7f, 2, 2, nullptr);
+ TestAdaptive32(0x80, 2, 2, nullptr);
+ TestAdaptive32(0x3fff, 2, 2, nullptr);
+ TestAdaptive32(0, 3, 3, nullptr);
+ TestAdaptive32(1, 3, 3, nullptr);
+ TestAdaptive32(2, 3, 3, nullptr);
+ TestAdaptive32(0x3f, 3, 3, nullptr);
+ TestAdaptive32(0x40, 3, 3, nullptr);
+ TestAdaptive32(0x41, 3, 3, nullptr);
+ TestAdaptive32(0x7f, 3, 3, nullptr);
+ TestAdaptive32(0x80, 3, 3, nullptr);
+ TestAdaptive32(0x3fff, 3, 3, nullptr);
+ TestAdaptive32(0x4000, 3, 3, nullptr);
+ TestAdaptive32(0x4001, 3, 3, nullptr);
+ TestAdaptive32(0x3fffff, 3, 3, nullptr);
+ TestAdaptive32(0, 4, 4, nullptr);
+ TestAdaptive32(1, 4, 4, nullptr);
+ TestAdaptive32(2, 4, 4, nullptr);
+ TestAdaptive32(0x3f, 4, 4, nullptr);
+ TestAdaptive32(0x40, 4, 4, nullptr);
+ TestAdaptive32(0x41, 4, 4, nullptr);
+ TestAdaptive32(0x7f, 4, 4, nullptr);
+ TestAdaptive32(0x80, 4, 4, nullptr);
+ TestAdaptive32(0x3fff, 4, 4, nullptr);
+ TestAdaptive32(0x4000, 4, 4, nullptr);
+ TestAdaptive32(0x4001, 4, 4, nullptr);
+ TestAdaptive32(0x3fffff, 4, 4, nullptr);
+ TestAdaptive32(0x400000, 4, 4, nullptr);
+ TestAdaptive32(0x400001, 4, 4, nullptr);
+ TestAdaptive32(0x3fffffff, 4, 4, nullptr);
+
+ TestAdaptive64(0, 1, 0, nullptr);
+ TestAdaptive64(1, 1, 0, nullptr);
+ TestAdaptive64(2, 1, 0, nullptr);
+ TestAdaptive64(0x3f, 1, 0, nullptr);
+ TestAdaptive64(0x40, 2, 0, nullptr);
+ TestAdaptive64(0x41, 2, 0, nullptr);
+ TestAdaptive64(0x7f, 2, 0, nullptr);
+ TestAdaptive64(0x80, 2, 0, nullptr);
+ TestAdaptive64(0x3fff, 2, 0, nullptr);
+ TestAdaptive64(0x4000, 4, 0, nullptr);
+ TestAdaptive64(0x4001, 4, 0, nullptr);
+ TestAdaptive64(0x3fffff, 4, 0, nullptr);
+ TestAdaptive64(0x400000, 4, 0, nullptr);
+ TestAdaptive64(0x400001, 4, 0, nullptr);
+ TestAdaptive64(0x3fffffff, 4, 0, nullptr);
+ TestAdaptive64(0x40000000, 8, 0, nullptr);
+ TestAdaptive64(0x40000001, 8, 0, nullptr);
+ TestAdaptive64(0x3fffffffffffffffull, 8, 0, nullptr);
+ TestAdaptive64(0, 2, 2, nullptr);
+ TestAdaptive64(1, 2, 2, nullptr);
+ TestAdaptive64(2, 2, 2, nullptr);
+ TestAdaptive64(0x3f, 2, 2, nullptr);
+ TestAdaptive64(0, 4, 4, nullptr);
+ TestAdaptive64(1, 4, 4, nullptr);
+ TestAdaptive64(2, 4, 4, nullptr);
+ TestAdaptive64(0x3f, 4, 4, nullptr);
+ TestAdaptive64(0x40, 4, 4, nullptr);
+ TestAdaptive64(0x41, 4, 4, nullptr);
+ TestAdaptive64(0x7f, 4, 4, nullptr);
+ TestAdaptive64(0x80, 4, 4, nullptr);
+ TestAdaptive64(0x3fff, 4, 4, nullptr);
+ TestAdaptive64(0, 8, 8, nullptr);
+ TestAdaptive64(1, 8, 8, nullptr);
+ TestAdaptive64(2, 8, 8, nullptr);
+ TestAdaptive64(0x3f, 8, 8, nullptr);
+ TestAdaptive64(0x40, 8, 8, nullptr);
+ TestAdaptive64(0x41, 8, 8, nullptr);
+ TestAdaptive64(0x7f, 8, 8, nullptr);
+ TestAdaptive64(0x80, 8, 8, nullptr);
+ TestAdaptive64(0x3fff, 8, 8, nullptr);
+ TestAdaptive64(0x4000, 8, 8, nullptr);
+ TestAdaptive64(0x4001, 8, 8, nullptr);
+ TestAdaptive64(0x3fffff, 8, 8, nullptr);
+ TestAdaptive64(0x400000, 8, 8, nullptr);
+ TestAdaptive64(0x400001, 8, 8, nullptr);
+ TestAdaptive64(0x3fffffff, 8, 8, nullptr);
+ TestAdaptive64(0x40000000, 8, 8, nullptr);
+ TestAdaptive64(0x40000001, 8, 8, nullptr);
+ TestAdaptive64(0x3fffffffffffffffull, 8, 8, nullptr);
+ }
+}
+
+} // namespace io
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_TESTS_IO_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/callbackstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/callbackstream.hpp
new file mode 100644
index 00000000..330c2dab
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/callbackstream.hpp
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_CALLBACKSTREAM_HPP
+#define MPT_IO_READ_CALLBACKSTREAM_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename Tstream>
+struct CallbackStreamTemplate {
+ enum : int {
+ SeekSet = 0,
+ SeekCur = 1,
+ SeekEnd = 2
+ };
+ Tstream stream;
+ std::size_t (*read)(Tstream stream, void * dst, std::size_t bytes);
+ int (*seek)(Tstream stream, int64 offset, int whence);
+ int64 (*tell)(Tstream stream);
+};
+
+using CallbackStream = CallbackStreamTemplate<void *>;
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_CALLBACKSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor.hpp
new file mode 100644
index 00000000..d2d6c62b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor.hpp
@@ -0,0 +1,419 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_HPP
+#define MPT_IO_READ_FILECURSOR_HPP
+
+
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+
+#include <algorithm>
+#include <optional>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// change to show warnings for functions which trigger pre-caching the whole file for unseekable streams
+//#define MPT_FILECURSOR_DEPRECATED [[deprecated]]
+#define MPT_FILECURSOR_DEPRECATED
+
+
+
+template <typename Ttraits, typename Tfilenametraits>
+class FileCursor {
+
+private:
+ using traits_type = Ttraits;
+ using filename_traits_type = Tfilenametraits;
+
+public:
+ using pos_type = typename traits_type::pos_type;
+
+ using data_type = typename traits_type::data_type;
+ using ref_data_type = typename traits_type::ref_data_type;
+ using shared_data_type = typename traits_type::shared_data_type;
+ using value_data_type = typename traits_type::value_data_type;
+
+ using filename_type = typename filename_traits_type::filename_type;
+ using shared_filename_type = typename filename_traits_type::shared_filename_type;
+
+protected:
+ shared_data_type SharedDataContainer() const {
+ return traits_type::get_shared(m_data);
+ }
+ ref_data_type DataContainer() const {
+ return traits_type::get_ref(m_data);
+ }
+
+ static value_data_type DataInitializer() {
+ return traits_type::make_data();
+ }
+ static value_data_type DataInitializer(mpt::const_byte_span data) {
+ return traits_type::make_data(data);
+ }
+
+ static value_data_type CreateChunkImpl(shared_data_type data, pos_type position, pos_type size) {
+ return traits_type::make_chunk(data, position, size);
+ }
+
+private:
+ data_type m_data;
+
+ pos_type streamPos; // Cursor location in the file
+
+ shared_filename_type m_fileName; // Filename that corresponds to this FileCursor. It is only set if this FileCursor represents the whole contents of fileName. May be nullopt.
+
+public:
+ // Initialize invalid file reader object.
+ FileCursor()
+ : m_data(DataInitializer())
+ , streamPos(0)
+ , m_fileName(nullptr) {
+ return;
+ }
+
+ // Initialize file reader object with pointer to data and data length.
+ template <typename Tbyte>
+ explicit FileCursor(mpt::span<Tbyte> bytedata, shared_filename_type filename = shared_filename_type{})
+ : m_data(DataInitializer(mpt::byte_cast<mpt::const_byte_span>(bytedata)))
+ , streamPos(0)
+ , m_fileName(std::move(filename)) {
+ return;
+ }
+
+ // Initialize file reader object based on an existing file reader object window.
+ explicit FileCursor(value_data_type other, shared_filename_type filename = shared_filename_type{})
+ : m_data(std::move(other))
+ , streamPos(0)
+ , m_fileName(std::move(filename)) {
+ return;
+ }
+
+public:
+ std::optional<filename_type> GetOptionalFileName() const {
+ return filename_traits_type::get_optional_filename(m_fileName);
+ }
+
+ // Returns true if the object points to a valid (non-empty) stream.
+ operator bool() const {
+ return IsValid();
+ }
+
+ // Returns true if the object points to a valid (non-empty) stream.
+ bool IsValid() const {
+ return DataContainer().IsValid();
+ }
+
+ // Reset cursor to first byte in file.
+ void Rewind() {
+ streamPos = 0;
+ }
+
+ // Seek to a position in the mapped file.
+ // Returns false if position is invalid.
+ bool Seek(pos_type position) {
+ if (position <= streamPos) {
+ streamPos = position;
+ return true;
+ }
+ if (position <= DataContainer().GetLength()) {
+ streamPos = position;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Increases position by skipBytes.
+ // Returns true if skipBytes could be skipped or false if the file end was reached earlier.
+ bool Skip(pos_type skipBytes) {
+ if (CanRead(skipBytes)) {
+ streamPos += skipBytes;
+ return true;
+ } else {
+ streamPos = DataContainer().GetLength();
+ return false;
+ }
+ }
+
+ // Decreases position by skipBytes.
+ // Returns true if skipBytes could be skipped or false if the file start was reached earlier.
+ bool SkipBack(pos_type skipBytes) {
+ if (streamPos >= skipBytes) {
+ streamPos -= skipBytes;
+ return true;
+ } else {
+ streamPos = 0;
+ return false;
+ }
+ }
+
+ // Returns cursor position in the mapped file.
+ pos_type GetPosition() const {
+ return streamPos;
+ }
+
+ // Return true IFF seeking and GetLength() is fast.
+ // In particular, it returns false for unseekable stream where GetLength()
+ // requires pre-caching.
+ bool HasFastGetLength() const {
+ return DataContainer().HasFastGetLength();
+ }
+
+ // Returns size of the mapped file in bytes.
+ MPT_FILECURSOR_DEPRECATED pos_type GetLength() const {
+ // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
+ return DataContainer().GetLength();
+ }
+
+ // Return byte count between cursor position and end of file, i.e. how many bytes can still be read.
+ MPT_FILECURSOR_DEPRECATED pos_type BytesLeft() const {
+ // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
+ return DataContainer().GetLength() - streamPos;
+ }
+
+ bool EndOfFile() const {
+ return !CanRead(1);
+ }
+
+ bool NoBytesLeft() const {
+ return !CanRead(1);
+ }
+
+ // Check if "amount" bytes can be read from the current position in the stream.
+ bool CanRead(pos_type amount) const {
+ return DataContainer().CanRead(streamPos, amount);
+ }
+
+ // Check if file size is at least size, without potentially caching the whole file to query the exact file length.
+ bool LengthIsAtLeast(pos_type size) const {
+ return DataContainer().CanRead(0, size);
+ }
+
+ // Check if file size is exactly size, without potentially caching the whole file if it is larger.
+ bool LengthIs(pos_type size) const {
+ return DataContainer().CanRead(0, size) && !DataContainer().CanRead(size, 1);
+ }
+
+protected:
+ FileCursor CreateChunk(pos_type position, pos_type length) const {
+ pos_type readableLength = DataContainer().GetReadableLength(position, length);
+ if (readableLength == 0)
+ {
+ return FileCursor();
+ }
+ return FileCursor(CreateChunkImpl(SharedDataContainer(), position, std::min(length, DataContainer().GetLength() - position)));
+ }
+
+public:
+ // Create a new FileCursor object for parsing a sub chunk at a given position with a given length.
+ // The file cursor is not modified.
+ FileCursor GetChunkAt(pos_type position, pos_type length) const {
+ return CreateChunk(position, length);
+ }
+
+ // Create a new FileCursor object for parsing a sub chunk at the current position with a given length.
+ // The file cursor is not advanced.
+ FileCursor GetChunk(pos_type length) {
+ return CreateChunk(streamPos, length);
+ }
+ // Create a new FileCursor object for parsing a sub chunk at the current position with a given length.
+ // The file cursor is advanced by "length" bytes.
+ FileCursor ReadChunk(pos_type length) {
+ pos_type position = streamPos;
+ Skip(length);
+ return CreateChunk(position, length);
+ }
+
+ class PinnedView {
+ private:
+ std::size_t size_;
+ const std::byte * pinnedData;
+ std::vector<std::byte> cache;
+
+ private:
+ void Init(const FileCursor & file, std::size_t size) {
+ size_ = 0;
+ pinnedData = nullptr;
+ if (!file.CanRead(size)) {
+ size = file.BytesLeft();
+ }
+ size_ = size;
+ if (file.DataContainer().HasPinnedView()) {
+ pinnedData = file.DataContainer().GetRawData() + file.GetPosition();
+ } else {
+ cache.resize(size_);
+ if (!cache.empty()) {
+ file.GetRaw(mpt::as_span(cache));
+ }
+ }
+ }
+
+ public:
+ PinnedView()
+ : size_(0)
+ , pinnedData(nullptr) {
+ }
+ PinnedView(const FileCursor & file) {
+ Init(file, file.BytesLeft());
+ }
+ PinnedView(const FileCursor & file, std::size_t size) {
+ Init(file, size);
+ }
+ PinnedView(FileCursor & file, bool advance) {
+ Init(file, file.BytesLeft());
+ if (advance)
+ {
+ file.Skip(size_);
+ }
+ }
+ PinnedView(FileCursor & file, std::size_t size, bool advance) {
+ Init(file, size);
+ if (advance) {
+ file.Skip(size_);
+ }
+ }
+
+ public:
+ mpt::const_byte_span GetSpan() const {
+ if (pinnedData) {
+ return mpt::as_span(pinnedData, size_);
+ } else if (!cache.empty()) {
+ return mpt::as_span(cache);
+ } else {
+ return mpt::const_byte_span();
+ }
+ }
+ mpt::const_byte_span span() const {
+ return GetSpan();
+ }
+ void invalidate() {
+ size_ = 0;
+ pinnedData = nullptr;
+ cache = std::vector<std::byte>();
+ }
+ const std::byte * data() const {
+ return span().data();
+ }
+ std::size_t size() const {
+ return size_;
+ }
+ mpt::const_byte_span::pointer begin() const {
+ return span().data();
+ }
+ mpt::const_byte_span::pointer end() const {
+ return span().data() + span().size();
+ }
+ mpt::const_byte_span::const_pointer cbegin() const {
+ return span().data();
+ }
+ mpt::const_byte_span::const_pointer cend() const {
+ return span().data() + span().size();
+ }
+ };
+
+ // Returns a pinned view into the remaining raw data from cursor position.
+ PinnedView GetPinnedView() const {
+ return PinnedView(*this);
+ }
+ // Returns a pinned view into the remeining raw data from cursor position, clamped at size.
+ PinnedView GetPinnedView(std::size_t size) const {
+ return PinnedView(*this, size);
+ }
+
+ // Returns a pinned view into the remeining raw data from cursor position.
+ // File cursor is advaned by the size of the returned pinned view.
+ PinnedView ReadPinnedView() {
+ return PinnedView(*this, true);
+ }
+ // Returns a pinned view into the remeining raw data from cursor position, clamped at size.
+ // File cursor is advaned by the size of the returned pinned view.
+ PinnedView ReadPinnedView(std::size_t size) {
+ return PinnedView(*this, size, true);
+ }
+
+ // Returns raw stream data at cursor position.
+ // Should only be used if absolutely necessary, for example for sample reading, or when used with a small chunk of the file retrieved by ReadChunk().
+ // Use GetPinnedView(size) whenever possible.
+ MPT_FILECURSOR_DEPRECATED mpt::const_byte_span GetRawData() const {
+ // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
+ return mpt::span(DataContainer().GetRawData() + streamPos, DataContainer().GetLength() - streamPos);
+ }
+ template <typename T>
+ MPT_FILECURSOR_DEPRECATED mpt::span<const T> GetRawData() const {
+ // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
+ return mpt::span(mpt::byte_cast<const T *>(DataContainer().GetRawData() + streamPos), DataContainer().GetLength() - streamPos);
+ }
+
+ mpt::byte_span GetRawWithOffset(std::size_t offset, mpt::byte_span dst) const {
+ return DataContainer().Read(streamPos + offset, dst);
+ }
+ template <typename Tspan>
+ Tspan GetRawWithOffset(std::size_t offset, Tspan dst) const {
+ return mpt::byte_cast<Tspan>(DataContainer().Read(streamPos + offset, mpt::byte_cast<mpt::byte_span>(dst)));
+ }
+
+ mpt::byte_span GetRaw(mpt::byte_span dst) const {
+ return DataContainer().Read(streamPos, dst);
+ }
+ template <typename Tspan>
+ Tspan GetRaw(Tspan dst) const {
+ return mpt::byte_cast<Tspan>(DataContainer().Read(streamPos, mpt::byte_cast<mpt::byte_span>(dst)));
+ }
+
+ mpt::byte_span ReadRaw(mpt::byte_span dst) {
+ mpt::byte_span result = DataContainer().Read(streamPos, dst);
+ streamPos += result.size();
+ return result;
+ }
+ template <typename Tspan>
+ Tspan ReadRaw(Tspan dst) {
+ Tspan result = mpt::byte_cast<Tspan>(DataContainer().Read(streamPos, mpt::byte_cast<mpt::byte_span>(dst)));
+ streamPos += result.size();
+ return result;
+ }
+
+ std::vector<std::byte> GetRawDataAsByteVector() const {
+ PinnedView view = GetPinnedView();
+ return mpt::make_vector(view.span());
+ }
+ std::vector<std::byte> ReadRawDataAsByteVector() {
+ PinnedView view = ReadPinnedView();
+ return mpt::make_vector(view.span());
+ }
+ std::vector<std::byte> GetRawDataAsByteVector(std::size_t size) const {
+ PinnedView view = GetPinnedView(size);
+ return mpt::make_vector(view.span());
+ }
+ std::vector<std::byte> ReadRawDataAsByteVector(std::size_t size) {
+ PinnedView view = ReadPinnedView(size);
+ return mpt::make_vector(view.span());
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_callbackstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_callbackstream.hpp
new file mode 100644
index 00000000..14fccef7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_callbackstream.hpp
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_CALLBACKSTREAM_HPP
+#define MPT_IO_READ_FILECURSOR_CALLBACKSTREAM_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/callbackstream.hpp"
+#include "mpt/io_read/filecursor.hpp"
+#include "mpt/io_read/filecursor_traits_filedata.hpp"
+#include "mpt/io_read/filecursor_filename_traits.hpp"
+#include "mpt/io_read/filedata_callbackstream.hpp"
+
+#include <memory>
+#include <utility>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// Initialize file reader object with a CallbackStream.
+template <typename Tpath, typename Tstream>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(CallbackStreamTemplate<Tstream> s, std::shared_ptr<Tpath> filename = nullptr) {
+ if (FileDataCallbackStreamTemplate<Tstream>::IsSeekable(s)) {
+ return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>(std::static_pointer_cast<IFileData>(std::make_shared<FileDataCallbackStreamSeekableTemplate<Tstream>>(s)), std::move(filename));
+ } else {
+ return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>(std::static_pointer_cast<IFileData>(std::make_shared<FileDataCallbackStreamUnseekableTemplate<Tstream>>(s)), std::move(filename));
+ }
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_CALLBACKSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_filename_traits.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_filename_traits.hpp
new file mode 100644
index 00000000..b1621266
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_filename_traits.hpp
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_FILENAME_TRAITS_HPP
+#define MPT_IO_READ_FILECURSOR_FILENAME_TRAITS_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include <memory>
+#include <optional>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileCursorFilenameTraitsNone {
+
+public:
+ struct empty_type { };
+
+ using filename_type = empty_type;
+ using shared_filename_type = empty_type;
+
+ static std::optional<filename_type> get_optional_filename(shared_filename_type /* filename */) {
+ return std::nullopt;
+ }
+};
+
+template <typename Tpath>
+class FileCursorFilenameTraits {
+
+public:
+ using filename_type = Tpath;
+ using shared_filename_type = std::shared_ptr<Tpath>;
+
+ static std::optional<filename_type> get_optional_filename(shared_filename_type filename) {
+ if (!filename) {
+ return std::nullopt;
+ }
+ return *filename;
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_FILENAME_TRAITS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_memory.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_memory.hpp
new file mode 100644
index 00000000..78d954d1
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_memory.hpp
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_MEMORY_HPP
+#define MPT_IO_READ_FILECURSOR_MEMORY_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io_read/filecursor.hpp"
+#include "mpt/io_read/filecursor_traits_filedata.hpp"
+#include "mpt/io_read/filecursor_filename_traits.hpp"
+
+#include <memory>
+#include <utility>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// Initialize file reader object with pointer to data and data length.
+template <typename Tpath, typename Tbyte>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(mpt::span<Tbyte> bytedata, std::shared_ptr<Tpath> filename = nullptr) {
+ return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>(mpt::byte_cast<mpt::const_byte_span>(bytedata), std::move(filename));
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_STDSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_stdstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_stdstream.hpp
new file mode 100644
index 00000000..b68d9e9a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_stdstream.hpp
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_STDSTREAM_HPP
+#define MPT_IO_READ_FILECURSOR_STDSTREAM_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/filecursor.hpp"
+#include "mpt/io_read/filecursor_traits_filedata.hpp"
+#include "mpt/io_read/filecursor_filename_traits.hpp"
+#include "mpt/io_read/filedata_stdstream.hpp"
+
+#include <istream>
+#include <memory>
+#include <utility>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// Initialize file reader object with a std::istream.
+template <typename Tpath>
+inline FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>> make_FileCursor(std::istream & s, std::shared_ptr<Tpath> filename = nullptr) {
+ if (FileDataStdStream::IsSeekable(s)) {
+ return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>(std::static_pointer_cast<IFileData>(std::make_shared<FileDataStdStreamSeekable>(s)), std::move(filename));
+ } else {
+ return FileCursor<FileCursorTraitsFileData, FileCursorFilenameTraits<Tpath>>(std::static_pointer_cast<IFileData>(std::make_shared<FileDataStdStreamUnseekable>(s)), std::move(filename));
+ }
+}
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_STDSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_filedata.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_filedata.hpp
new file mode 100644
index 00000000..2d3ef1f9
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_filedata.hpp
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_TRAITS_FILEDATA_HPP
+#define MPT_IO_READ_FILECURSOR_TRAITS_FILEDATA_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/filedata.hpp"
+#include "mpt/io_read/filedata_base.hpp"
+#include "mpt/io_read/filedata_memory.hpp"
+
+#include <memory>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileCursorTraitsFileData {
+
+public:
+ using pos_type = IFileData::pos_type;
+
+ using data_type = std::shared_ptr<const IFileData>;
+ using ref_data_type = const IFileData &;
+ using shared_data_type = std::shared_ptr<const IFileData>;
+ using value_data_type = std::shared_ptr<const IFileData>;
+
+ static shared_data_type get_shared(const data_type & data) {
+ return data;
+ }
+ static ref_data_type get_ref(const data_type & data) {
+ return *data;
+ }
+
+ static value_data_type make_data() {
+ return std::make_shared<FileDataDummy>();
+ }
+ static value_data_type make_data(mpt::const_byte_span data) {
+ return std::make_shared<FileDataMemory>(data);
+ }
+
+ static value_data_type make_chunk(shared_data_type data, pos_type position, pos_type size) {
+ return std::static_pointer_cast<IFileData>(std::make_shared<FileDataWindow>(data, position, size));
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_TRAITS_FILEDATA_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_memory.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_memory.hpp
new file mode 100644
index 00000000..254fc640
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filecursor_traits_memory.hpp
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILECURSOR_TRAITS_MEMORY_HPP
+#define MPT_IO_READ_FILECURSOR_TRAITS_MEMORY_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/io_read/filedata.hpp"
+#include "mpt/io_read/filedata_memory.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileCursorTraitsMemory {
+
+public:
+ using pos_type = FileDataMemory::pos_type;
+
+ using data_type = FileDataMemory;
+ using ref_data_type = const FileDataMemory &;
+ using shared_data_type = const FileDataMemory &;
+ using value_data_type = FileDataMemory;
+
+ static shared_data_type get_shared(const data_type & data) {
+ return data;
+ }
+ static ref_data_type get_ref(const data_type & data) {
+ return data;
+ }
+
+ static value_data_type make_data() {
+ return mpt::const_byte_span();
+ }
+ static value_data_type make_data(mpt::const_byte_span data) {
+ return data;
+ }
+
+ static value_data_type make_chunk(shared_data_type data, pos_type position, pos_type size) {
+ return mpt::as_span(data.GetRawData() + position, size);
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILECURSOR_TRAITS_MEMORY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata.hpp
new file mode 100644
index 00000000..afaf8db9
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata.hpp
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_HPP
+#define MPT_IO_READ_FILEDATA_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+
+#include <algorithm>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class IFileData {
+public:
+ typedef std::size_t pos_type;
+
+protected:
+ IFileData() = default;
+
+public:
+ IFileData(const IFileData &) = default;
+ IFileData & operator=(const IFileData &) = default;
+ virtual ~IFileData() = default;
+
+public:
+ virtual bool IsValid() const = 0;
+ virtual bool HasFastGetLength() const = 0;
+ virtual bool HasPinnedView() const = 0;
+ virtual const std::byte * GetRawData() const = 0;
+ virtual pos_type GetLength() const = 0;
+ virtual mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const = 0;
+
+ virtual bool CanRead(pos_type pos, std::size_t length) const {
+ pos_type dataLength = GetLength();
+ if ((pos == dataLength) && (length == 0)) {
+ return true;
+ }
+ if (pos >= dataLength) {
+ return false;
+ }
+ return length <= dataLength - pos;
+ }
+
+ virtual std::size_t GetReadableLength(pos_type pos, std::size_t length) const {
+ pos_type dataLength = GetLength();
+ if (pos >= dataLength) {
+ return 0;
+ }
+ return std::min(length, dataLength - pos);
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base.hpp
new file mode 100644
index 00000000..fba96a8f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base.hpp
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_BASE_HPP
+#define MPT_IO_READ_FILEDATA_BASE_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/filedata.hpp"
+
+#include <algorithm>
+#include <memory>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataDummy : public IFileData {
+public:
+ FileDataDummy() { }
+
+public:
+ bool IsValid() const override {
+ return false;
+ }
+
+ bool HasFastGetLength() const override {
+ return true;
+ }
+
+ bool HasPinnedView() const override {
+ return true;
+ }
+
+ const std::byte * GetRawData() const override {
+ return nullptr;
+ }
+
+ pos_type GetLength() const override {
+ return 0;
+ }
+ mpt::byte_span Read(pos_type /* pos */, mpt::byte_span dst) const override {
+ return dst.first(0);
+ }
+};
+
+
+class FileDataWindow : public IFileData {
+private:
+ std::shared_ptr<const IFileData> data;
+ const pos_type dataOffset;
+ const pos_type dataLength;
+
+public:
+ FileDataWindow(std::shared_ptr<const IFileData> src, pos_type off, pos_type len)
+ : data(src)
+ , dataOffset(off)
+ , dataLength(len) { }
+
+ bool IsValid() const override {
+ return data->IsValid();
+ }
+ bool HasFastGetLength() const override {
+ return data->HasFastGetLength();
+ }
+ bool HasPinnedView() const override {
+ return data->HasPinnedView();
+ }
+ const std::byte * GetRawData() const override {
+ return data->GetRawData() + dataOffset;
+ }
+ pos_type GetLength() const override {
+ return dataLength;
+ }
+ mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override {
+ if (pos >= dataLength) {
+ return dst.first(0);
+ }
+ return data->Read(dataOffset + pos, dst.first(std::min(dst.size(), dataLength - pos)));
+ }
+ bool CanRead(pos_type pos, std::size_t length) const override {
+ if ((pos == dataLength) && (length == 0)) {
+ return true;
+ }
+ if (pos >= dataLength) {
+ return false;
+ }
+ return (length <= dataLength - pos);
+ }
+ pos_type GetReadableLength(pos_type pos, std::size_t length) const override {
+ if (pos >= dataLength) {
+ return 0;
+ }
+ return std::min(length, dataLength - pos);
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_BASE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_buffered.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_buffered.hpp
new file mode 100644
index 00000000..637be25c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_buffered.hpp
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_BASE_BUFFERED_HPP
+#define MPT_IO_READ_FILEDATA_BASE_BUFFERED_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io_read/filedata.hpp"
+#include "mpt/io_read/filedata_base_seekable.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataSeekableBuffered : public FileDataSeekable {
+
+private:
+ enum : std::size_t {
+ CHUNK_SIZE = mpt::IO::BUFFERSIZE_SMALL,
+ BUFFER_SIZE = mpt::IO::BUFFERSIZE_NORMAL
+ };
+ enum : std::size_t {
+ NUM_CHUNKS = BUFFER_SIZE / CHUNK_SIZE
+ };
+ struct chunk_info {
+ pos_type ChunkOffset = 0;
+ pos_type ChunkLength = 0;
+ bool ChunkValid = false;
+ };
+ mutable std::vector<std::byte> m_Buffer = std::vector<std::byte>(BUFFER_SIZE);
+
+ mpt::byte_span chunk_data(std::size_t chunkIndex) const {
+ return mpt::byte_span(m_Buffer.data() + (chunkIndex * CHUNK_SIZE), CHUNK_SIZE);
+ }
+
+ mutable std::array<chunk_info, NUM_CHUNKS> m_ChunkInfo = {};
+ mutable std::array<std::size_t, NUM_CHUNKS> m_ChunkIndexLRU = {};
+
+ std::size_t InternalFillPageAndReturnIndex(pos_type pos) const {
+ pos = mpt::align_down(pos, static_cast<pos_type>(CHUNK_SIZE));
+ for (std::size_t chunkLRUIndex = 0; chunkLRUIndex < NUM_CHUNKS; ++chunkLRUIndex) {
+ std::size_t chunkIndex = m_ChunkIndexLRU[chunkLRUIndex];
+ if (m_ChunkInfo[chunkIndex].ChunkValid && (m_ChunkInfo[chunkIndex].ChunkOffset == pos)) {
+ std::size_t chunk = std::move(m_ChunkIndexLRU[chunkLRUIndex]);
+ std::move_backward(m_ChunkIndexLRU.begin(), m_ChunkIndexLRU.begin() + chunkLRUIndex, m_ChunkIndexLRU.begin() + (chunkLRUIndex + 1));
+ m_ChunkIndexLRU[0] = std::move(chunk);
+ return chunkIndex;
+ }
+ }
+ {
+ std::size_t chunk = std::move(m_ChunkIndexLRU[NUM_CHUNKS - 1]);
+ std::move_backward(m_ChunkIndexLRU.begin(), m_ChunkIndexLRU.begin() + (NUM_CHUNKS - 1), m_ChunkIndexLRU.begin() + NUM_CHUNKS);
+ m_ChunkIndexLRU[0] = std::move(chunk);
+ }
+ std::size_t chunkIndex = m_ChunkIndexLRU[0];
+ chunk_info & chunk = m_ChunkInfo[chunkIndex];
+ chunk.ChunkOffset = pos;
+ chunk.ChunkLength = InternalReadBuffered(pos, chunk_data(chunkIndex)).size();
+ chunk.ChunkValid = true;
+ return chunkIndex;
+ }
+
+protected:
+ FileDataSeekableBuffered(pos_type streamLength_)
+ : FileDataSeekable(streamLength_) {
+ return;
+ }
+
+private:
+ mpt::byte_span InternalReadSeekable(pos_type pos, mpt::byte_span dst) const override {
+ pos_type totalRead = 0;
+ std::byte * pdst = dst.data();
+ std::size_t count = dst.size();
+ while (count > 0) {
+ std::size_t chunkIndex = InternalFillPageAndReturnIndex(pos);
+ pos_type pageSkip = pos - m_ChunkInfo[chunkIndex].ChunkOffset;
+ pos_type chunkWanted = std::min(static_cast<pos_type>(CHUNK_SIZE) - pageSkip, count);
+ pos_type chunkGot = (m_ChunkInfo[chunkIndex].ChunkLength > pageSkip) ? (m_ChunkInfo[chunkIndex].ChunkLength - pageSkip) : 0;
+ pos_type chunk = std::min(chunkWanted, chunkGot);
+ std::copy(chunk_data(chunkIndex).data() + pageSkip, chunk_data(chunkIndex).data() + pageSkip + chunk, pdst);
+ pos += chunk;
+ pdst += chunk;
+ totalRead += chunk;
+ count -= chunk;
+ if (chunkWanted > chunk) {
+ return dst.first(totalRead);
+ }
+ }
+ return dst.first(totalRead);
+ }
+
+ virtual mpt::byte_span InternalReadBuffered(pos_type pos, mpt::byte_span dst) const = 0;
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_BASE_BUFFERED_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_seekable.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_seekable.hpp
new file mode 100644
index 00000000..b2324c7d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_seekable.hpp
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_BASE_SEEKABLE_HPP
+#define MPT_IO_READ_FILEDATA_BASE_SEEKABLE_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/filedata.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataSeekable : public IFileData {
+
+private:
+ pos_type streamLength;
+
+ mutable bool cached;
+ mutable std::vector<std::byte> cache;
+
+protected:
+ FileDataSeekable(pos_type streamLength_)
+ : streamLength(streamLength_)
+ , cached(false) {
+ return;
+ }
+
+
+private:
+ void CacheStream() const {
+ if (cached) {
+ return;
+ }
+ cache.resize(streamLength);
+ InternalReadSeekable(0, mpt::as_span(cache));
+ cached = true;
+ }
+
+public:
+ bool IsValid() const override {
+ return true;
+ }
+
+ bool HasFastGetLength() const override {
+ return true;
+ }
+
+ bool HasPinnedView() const override {
+ return cached;
+ }
+
+ const std::byte * GetRawData() const override {
+ CacheStream();
+ return cache.data();
+ }
+
+ pos_type GetLength() const override {
+ return streamLength;
+ }
+
+ mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override {
+ if (cached) {
+ IFileData::pos_type cache_avail = std::min(IFileData::pos_type(cache.size()) - pos, dst.size());
+ std::copy(cache.begin() + pos, cache.begin() + pos + cache_avail, dst.data());
+ return dst.first(cache_avail);
+ } else {
+ return InternalReadSeekable(pos, dst);
+ }
+ }
+
+private:
+ virtual mpt::byte_span InternalReadSeekable(pos_type pos, mpt::byte_span dst) const = 0;
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_BASE_SEEKABLE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_unseekable.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_unseekable.hpp
new file mode 100644
index 00000000..78d9ca45
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_base_unseekable.hpp
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_HPP
+#define MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_HPP
+
+
+
+#include "mpt/base/algorithm.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io_read/filedata.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataUnseekable : public IFileData {
+
+private:
+ mutable std::vector<std::byte> cache;
+ mutable std::size_t cachesize;
+ mutable bool streamFullyCached;
+
+protected:
+ FileDataUnseekable()
+ : cachesize(0)
+ , streamFullyCached(false) {
+ return;
+ }
+
+private:
+ enum : std::size_t {
+ QUANTUM_SIZE = mpt::IO::BUFFERSIZE_SMALL,
+ BUFFER_SIZE = mpt::IO::BUFFERSIZE_NORMAL
+ };
+
+ void EnsureCacheBuffer(std::size_t requiredbuffersize) const {
+ if (cache.size() >= cachesize + requiredbuffersize) {
+ return;
+ }
+ if (cache.size() == 0) {
+ cache.resize(mpt::align_up<std::size_t>(cachesize + requiredbuffersize, BUFFER_SIZE));
+ } else if (mpt::exponential_grow(cache.size()) < cachesize + requiredbuffersize) {
+ cache.resize(mpt::align_up<std::size_t>(cachesize + requiredbuffersize, BUFFER_SIZE));
+ } else {
+ cache.resize(mpt::exponential_grow(cache.size()));
+ }
+ }
+
+ void CacheStream() const {
+ if (streamFullyCached) {
+ return;
+ }
+ while (!InternalEof()) {
+ EnsureCacheBuffer(BUFFER_SIZE);
+ std::size_t readcount = InternalReadUnseekable(mpt::span(&cache[cachesize], BUFFER_SIZE)).size();
+ cachesize += readcount;
+ }
+ streamFullyCached = true;
+ }
+
+
+ void CacheStreamUpTo(pos_type pos, pos_type length) const {
+ if (streamFullyCached) {
+ return;
+ }
+ if (length > std::numeric_limits<pos_type>::max() - pos) {
+ length = std::numeric_limits<pos_type>::max() - pos;
+ }
+ std::size_t target = mpt::saturate_cast<std::size_t>(pos + length);
+ if (target <= cachesize) {
+ return;
+ }
+ std::size_t alignedpos = mpt::align_up<std::size_t>(target, QUANTUM_SIZE);
+ std::size_t needcount = alignedpos - cachesize;
+ EnsureCacheBuffer(needcount);
+ std::size_t readcount = InternalReadUnseekable(mpt::span(&cache[cachesize], alignedpos - cachesize)).size();
+ cachesize += readcount;
+ if (!InternalEof()) {
+ // can read further
+ return;
+ }
+ streamFullyCached = true;
+ }
+
+private:
+ void ReadCached(pos_type pos, mpt::byte_span dst) const {
+ std::copy(cache.begin() + pos, cache.begin() + pos + dst.size(), dst.data());
+ }
+
+public:
+ bool IsValid() const override {
+ return true;
+ }
+
+ bool HasFastGetLength() const override {
+ return false;
+ }
+
+ bool HasPinnedView() const override {
+ return true; // we have the cache which is required for seeking anyway
+ }
+
+ const std::byte * GetRawData() const override {
+ CacheStream();
+ return cache.data();
+ }
+
+ pos_type GetLength() const override {
+ CacheStream();
+ return cachesize;
+ }
+
+ mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override {
+ CacheStreamUpTo(pos, dst.size());
+ if (pos >= IFileData::pos_type(cachesize)) {
+ return dst.first(0);
+ }
+ IFileData::pos_type cache_avail = std::min(IFileData::pos_type(cachesize) - pos, dst.size());
+ ReadCached(pos, dst.subspan(0, cache_avail));
+ return dst.subspan(0, cache_avail);
+ }
+
+ bool CanRead(pos_type pos, std::size_t length) const override {
+ CacheStreamUpTo(pos, length);
+ if ((pos == IFileData::pos_type(cachesize)) && (length == 0)) {
+ return true;
+ }
+ if (pos >= IFileData::pos_type(cachesize)) {
+ return false;
+ }
+ return length <= IFileData::pos_type(cachesize) - pos;
+ }
+
+ std::size_t GetReadableLength(pos_type pos, std::size_t length) const override {
+ CacheStreamUpTo(pos, length);
+ if (pos >= cachesize) {
+ return 0;
+ }
+ return std::min(static_cast<IFileData::pos_type>(cachesize) - pos, length);
+ }
+
+private:
+ virtual bool InternalEof() const = 0;
+ virtual mpt::byte_span InternalReadUnseekable(mpt::byte_span dst) const = 0;
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_callbackstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_callbackstream.hpp
new file mode 100644
index 00000000..ae72ab59
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_callbackstream.hpp
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_CALLBACKSTREAM_HPP
+#define MPT_IO_READ_FILEDATA_CALLBACKSTREAM_HPP
+
+
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/callbackstream.hpp"
+#include "mpt/io_read/filedata.hpp"
+#include "mpt/io_read/filedata_base_seekable.hpp"
+#include "mpt/io_read/filedata_base_unseekable.hpp"
+
+#include <algorithm>
+#include <memory>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+template <typename Tstream>
+class FileDataCallbackStreamTemplate {
+public:
+ static bool IsSeekable(CallbackStreamTemplate<Tstream> stream) {
+ if (!stream.stream) {
+ return false;
+ }
+ if (!stream.seek) {
+ return false;
+ }
+ if (!stream.tell) {
+ return false;
+ }
+ int64 oldpos = stream.tell(stream.stream);
+ if (oldpos < 0) {
+ return false;
+ }
+ if (stream.seek(stream.stream, 0, CallbackStream::SeekSet) < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return false;
+ }
+ if (stream.seek(stream.stream, 0, CallbackStream::SeekEnd) < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return false;
+ }
+ int64 length = stream.tell(stream.stream);
+ if (length < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return false;
+ }
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return true;
+ }
+
+ static IFileData::pos_type GetLength(CallbackStreamTemplate<Tstream> stream) {
+ if (!stream.stream) {
+ return 0;
+ }
+ if (!stream.seek) {
+ return false;
+ }
+ if (!stream.tell) {
+ return false;
+ }
+ int64 oldpos = stream.tell(stream.stream);
+ if (oldpos < 0) {
+ return 0;
+ }
+ if (stream.seek(stream.stream, 0, CallbackStream::SeekSet) < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return 0;
+ }
+ if (stream.seek(stream.stream, 0, CallbackStream::SeekEnd) < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return 0;
+ }
+ int64 length = stream.tell(stream.stream);
+ if (length < 0) {
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return 0;
+ }
+ stream.seek(stream.stream, oldpos, CallbackStream::SeekSet);
+ return mpt::saturate_cast<IFileData::pos_type>(length);
+ }
+};
+
+using FileDataCallbackStream = FileDataCallbackStreamTemplate<void *>;
+
+
+
+template <typename Tstream>
+class FileDataCallbackStreamSeekableTemplate : public FileDataSeekable {
+private:
+ CallbackStreamTemplate<Tstream> stream;
+
+public:
+ FileDataCallbackStreamSeekableTemplate(CallbackStreamTemplate<Tstream> s)
+ : FileDataSeekable(FileDataCallbackStream::GetLength(s))
+ , stream(s) {
+ return;
+ }
+
+private:
+ mpt::byte_span InternalReadSeekable(pos_type pos, mpt::byte_span dst) const override {
+ if (!stream.read) {
+ return dst.first(0);
+ }
+ if (stream.seek(stream.stream, pos, CallbackStream::SeekSet) < 0) {
+ return dst.first(0);
+ }
+ int64 totalread = 0;
+ std::byte * pdst = dst.data();
+ std::size_t count = dst.size();
+ while (count > 0) {
+ int64 readcount = stream.read(stream.stream, pdst, count);
+ if (readcount <= 0) {
+ break;
+ }
+ pdst += static_cast<std::size_t>(readcount);
+ count -= static_cast<std::size_t>(readcount);
+ totalread += readcount;
+ }
+ return dst.first(static_cast<std::size_t>(totalread));
+ }
+};
+
+using FileDataCallbackStreamSeekable = FileDataCallbackStreamSeekableTemplate<void *>;
+
+
+
+template <typename Tstream>
+class FileDataCallbackStreamUnseekableTemplate : public FileDataUnseekable {
+private:
+ CallbackStreamTemplate<Tstream> stream;
+ mutable bool eof_reached;
+
+public:
+ FileDataCallbackStreamUnseekableTemplate(CallbackStreamTemplate<Tstream> s)
+ : FileDataUnseekable()
+ , stream(s)
+ , eof_reached(false) {
+ return;
+ }
+
+private:
+ bool InternalEof() const override {
+ return eof_reached;
+ }
+
+ mpt::byte_span InternalReadUnseekable(mpt::byte_span dst) const override {
+ if (eof_reached) {
+ return dst.first(0);
+ }
+ if (!stream.read) {
+ eof_reached = true;
+ return dst.first(0);
+ }
+ int64 totalread = 0;
+ std::byte * pdst = dst.data();
+ std::size_t count = dst.size();
+ while (count > 0) {
+ int64 readcount = stream.read(stream.stream, pdst, count);
+ if (readcount <= 0) {
+ eof_reached = true;
+ break;
+ }
+ pdst += static_cast<std::size_t>(readcount);
+ count -= static_cast<std::size_t>(readcount);
+ totalread += readcount;
+ }
+ return dst.first(static_cast<std::size_t>(totalread));
+ }
+};
+
+using FileDataCallbackStreamUnseekable = FileDataCallbackStreamUnseekableTemplate<void *>;
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_CALLBACKSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_memory.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_memory.hpp
new file mode 100644
index 00000000..004104c3
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_memory.hpp
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_MEMORY_HPP
+#define MPT_IO_READ_FILEDATA_MEMORY_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/io_read/filedata.hpp"
+
+#include <algorithm>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataMemory
+ : public IFileData {
+
+private:
+ const std::byte * streamData; // Pointer to memory-mapped file
+ pos_type streamLength; // Size of memory-mapped file in bytes
+
+public:
+ FileDataMemory()
+ : streamData(nullptr)
+ , streamLength(0) { }
+ FileDataMemory(mpt::const_byte_span data)
+ : streamData(data.data())
+ , streamLength(data.size()) { }
+
+public:
+ bool IsValid() const override {
+ return streamData != nullptr;
+ }
+
+ bool HasFastGetLength() const override {
+ return true;
+ }
+
+ bool HasPinnedView() const override {
+ return true;
+ }
+
+ const std::byte * GetRawData() const override {
+ return streamData;
+ }
+
+ pos_type GetLength() const override {
+ return streamLength;
+ }
+
+ mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override {
+ if (pos >= streamLength) {
+ return dst.first(0);
+ }
+ pos_type avail = std::min(streamLength - pos, dst.size());
+ std::copy(streamData + pos, streamData + pos + avail, dst.data());
+ return dst.first(avail);
+ }
+
+ bool CanRead(pos_type pos, std::size_t length) const override {
+ if ((pos == streamLength) && (length == 0)) {
+ return true;
+ }
+ if (pos >= streamLength) {
+ return false;
+ }
+ return (length <= streamLength - pos);
+ }
+
+ std::size_t GetReadableLength(pos_type pos, std::size_t length) const override {
+ if (pos >= streamLength) {
+ return 0;
+ }
+ return std::min(length, streamLength - pos);
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_MEMORY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_stdstream.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_stdstream.hpp
new file mode 100644
index 00000000..e7f72b12
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filedata_stdstream.hpp
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEDATA_STDSTREAM_HPP
+#define MPT_IO_READ_FILEDATA_STDSTREAM_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/io/io_stdstream.hpp"
+#include "mpt/io_read/filedata.hpp"
+#include "mpt/io_read/filedata_base_buffered.hpp"
+#include "mpt/io_read/filedata_base_unseekable.hpp"
+
+#include <ios>
+#include <istream>
+#include <ostream>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+class FileDataStdStream {
+
+public:
+ static bool IsSeekable(std::istream & stream) {
+ return mpt::IO::IsReadSeekable(stream);
+ }
+
+ static IFileData::pos_type GetLength(std::istream & stream) {
+ stream.clear();
+ std::streampos oldpos = stream.tellg();
+ stream.seekg(0, std::ios::end);
+ std::streampos length = stream.tellg();
+ stream.seekg(oldpos);
+ return mpt::saturate_cast<IFileData::pos_type>(static_cast<int64>(length));
+ }
+};
+
+
+
+class FileDataStdStreamSeekable : public FileDataSeekableBuffered {
+
+private:
+ std::istream & stream;
+
+public:
+ FileDataStdStreamSeekable(std::istream & s)
+ : FileDataSeekableBuffered(FileDataStdStream::GetLength(s))
+ , stream(s) {
+ return;
+ }
+
+private:
+ mpt::byte_span InternalReadBuffered(pos_type pos, mpt::byte_span dst) const override {
+ stream.clear(); // tellg needs eof and fail bits unset
+ std::streampos currentpos = stream.tellg();
+ if (currentpos == std::streampos(-1) || static_cast<int64>(pos) != currentpos) {
+ // inefficient istream implementations might invalidate their buffer when seeking, even when seeking to the current position
+ stream.seekg(pos);
+ }
+ stream.read(mpt::byte_cast<char *>(dst.data()), dst.size());
+ return dst.first(static_cast<std::size_t>(stream.gcount()));
+ }
+};
+
+
+
+class FileDataStdStreamUnseekable : public FileDataUnseekable {
+
+private:
+ std::istream & stream;
+
+public:
+ FileDataStdStreamUnseekable(std::istream & s)
+ : stream(s) {
+ return;
+ }
+
+private:
+ bool InternalEof() const override {
+ if (stream) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ mpt::byte_span InternalReadUnseekable(mpt::byte_span dst) const override {
+ stream.read(mpt::byte_cast<char *>(dst.data()), dst.size());
+ return dst.first(static_cast<std::size_t>(stream.gcount()));
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEDATA_STDSTREAM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filereader.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filereader.hpp
new file mode 100644
index 00000000..84a39d6a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_read/filereader.hpp
@@ -0,0 +1,594 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_READ_FILEREADER_HPP
+#define MPT_IO_READ_FILEREADER_HPP
+
+
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/bit.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/span.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/io/base.hpp"
+#include "mpt/endian/floatingpoint.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/string/utility.hpp"
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+namespace FileReader {
+
+// Read a "T" object from the stream.
+// If not enough bytes can be read, false is returned.
+// If successful, the file cursor is advanced by the size of "T".
+template <typename T, typename TFileCursor>
+bool Read(TFileCursor & f, T & target) {
+ // cppcheck false-positive
+ // cppcheck-suppress uninitvar
+ mpt::byte_span dst = mpt::as_raw_memory(target);
+ if (dst.size() != f.GetRaw(dst).size()) {
+ return false;
+ }
+ f.Skip(dst.size());
+ return true;
+}
+
+// Read an array of binary-safe T values.
+// If successful, the file cursor is advanced by the size of the array.
+// Otherwise, the target is zeroed.
+template <typename T, std::size_t destSize, typename TFileCursor>
+bool ReadArray(TFileCursor & f, T (&destArray)[destSize]) {
+ static_assert(mpt::is_binary_safe<T>::value);
+ if (!f.CanRead(sizeof(destArray))) {
+ mpt::reset(destArray);
+ return false;
+ }
+ f.ReadRaw(mpt::as_raw_memory(destArray));
+ return true;
+}
+
+// Read an array of binary-safe T values.
+// If successful, the file cursor is advanced by the size of the array.
+// Otherwise, the target is zeroed.
+template <typename T, std::size_t destSize, typename TFileCursor>
+bool ReadArray(TFileCursor & f, std::array<T, destSize> & destArray) {
+ static_assert(mpt::is_binary_safe<T>::value);
+ if (!f.CanRead(sizeof(destArray))) {
+ destArray.fill(T{});
+ return false;
+ }
+ f.ReadRaw(mpt::as_raw_memory(destArray));
+ return true;
+}
+
+// Read destSize elements of binary-safe type T into a vector.
+// If successful, the file cursor is advanced by the size of the vector.
+// Otherwise, the vector is resized to destSize, but possibly existing contents are not cleared.
+template <typename T, typename TFileCursor>
+bool ReadVector(TFileCursor & f, std::vector<T> & destVector, size_t destSize) {
+ static_assert(mpt::is_binary_safe<T>::value);
+ destVector.resize(destSize);
+ if (!f.CanRead(sizeof(T) * destSize)) {
+ return false;
+ }
+ f.ReadRaw(mpt::as_raw_memory(destVector));
+ return true;
+}
+
+template <typename T, std::size_t destSize, typename TFileCursor>
+std::array<T, destSize> ReadArray(TFileCursor & f) {
+ std::array<T, destSize> destArray;
+ ReadArray(f, destArray);
+ return destArray;
+}
+
+// Read some kind of integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename T, typename TFileCursor>
+T ReadIntLE(TFileCursor & f) {
+ static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
+ typename mpt::make_le<T>::type target;
+ if (!FileReader::Read(f, target)) {
+ return 0;
+ }
+ return target;
+}
+
+// Read some kind of integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename T, typename TFileCursor>
+T ReadIntBE(TFileCursor & f) {
+ static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
+ typename mpt::make_be<T>::type target;
+ if (!FileReader::Read(f, target)) {
+ return 0;
+ }
+ return target;
+}
+
+// Read a integer in little-endian format which has some of its higher bytes not stored in file.
+// If successful, the file cursor is advanced by the given size.
+template <typename T, typename TFileCursor>
+T ReadTruncatedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) {
+ static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
+ assert(sizeof(T) >= size);
+ if (size == 0) {
+ return 0;
+ }
+ if (!f.CanRead(size)) {
+ return 0;
+ }
+ uint8 buf[sizeof(T)];
+ bool negative = false;
+ for (std::size_t i = 0; i < sizeof(T); ++i) {
+ uint8 byte = 0;
+ if (i < size) {
+ FileReader::Read(f, byte);
+ negative = std::numeric_limits<T>::is_signed && ((byte & 0x80) != 0x00);
+ } else {
+ // sign or zero extend
+ byte = negative ? 0xff : 0x00;
+ }
+ buf[i] = byte;
+ }
+ return mpt::bit_cast<typename mpt::make_le<T>::type>(buf);
+}
+
+// Read a supplied-size little endian integer to a fixed size variable.
+// The data is properly sign-extended when fewer bytes are stored.
+// If more bytes are stored, higher order bytes are silently ignored.
+// If successful, the file cursor is advanced by the given size.
+template <typename T, typename TFileCursor>
+T ReadSizedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) {
+ static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
+ if (size == 0) {
+ return 0;
+ }
+ if (!f.CanRead(size)) {
+ return 0;
+ }
+ if (size < sizeof(T)) {
+ return FileReader::ReadTruncatedIntLE<T>(f, size);
+ }
+ T retval = FileReader::ReadIntLE<T>(f);
+ f.Skip(size - sizeof(T));
+ return retval;
+}
+
+// Read unsigned 32-Bit integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint32 ReadUint32LE(TFileCursor & f) {
+ return FileReader::ReadIntLE<uint32>(f);
+}
+
+// Read unsigned 32-Bit integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint32 ReadUint32BE(TFileCursor & f) {
+ return FileReader::ReadIntBE<uint32>(f);
+}
+
+// Read signed 32-Bit integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+int32 ReadInt32LE(TFileCursor & f) {
+ return FileReader::ReadIntLE<int32>(f);
+}
+
+// Read signed 32-Bit integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+int32 ReadInt32BE(TFileCursor & f) {
+ return FileReader::ReadIntBE<int32>(f);
+}
+
+// Read unsigned 24-Bit integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint32 ReadUint24LE(TFileCursor & f) {
+ const auto arr = FileReader::ReadArray<uint8, 3>(f);
+ return arr[0] | (arr[1] << 8) | (arr[2] << 16);
+}
+
+// Read unsigned 24-Bit integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint32 ReadUint24BE(TFileCursor & f) {
+ const auto arr = FileReader::ReadArray<uint8, 3>(f);
+ return (arr[0] << 16) | (arr[1] << 8) | arr[2];
+}
+
+// Read unsigned 16-Bit integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint16 ReadUint16LE(TFileCursor & f) {
+ return FileReader::ReadIntLE<uint16>(f);
+}
+
+// Read unsigned 16-Bit integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint16 ReadUint16BE(TFileCursor & f) {
+ return FileReader::ReadIntBE<uint16>(f);
+}
+
+// Read signed 16-Bit integer in little-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+int16 ReadInt16LE(TFileCursor & f) {
+ return FileReader::ReadIntLE<int16>(f);
+}
+
+// Read signed 16-Bit integer in big-endian format.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+int16 ReadInt16BE(TFileCursor & f) {
+ return FileReader::ReadIntBE<int16>(f);
+}
+
+// Read a single 8bit character.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+char ReadChar(TFileCursor & f) {
+ char target;
+ if (!FileReader::Read(f, target)) {
+ return 0;
+ }
+ return target;
+}
+
+// Read unsigned 8-Bit integer.
+// If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+uint8 ReadUint8(TFileCursor & f) {
+ uint8 target;
+ if (!FileReader::Read(f, target)) {
+ return 0;
+ }
+ return target;
+}
+
+// Read signed 8-Bit integer. If successful, the file cursor is advanced by the size of the integer.
+template <typename TFileCursor>
+int8 ReadInt8(TFileCursor & f) {
+ int8 target;
+ if (!FileReader::Read(f, target)) {
+ return 0;
+ }
+ return target;
+}
+
+// Read 32-Bit float in little-endian format.
+// If successful, the file cursor is advanced by the size of the float.
+template <typename TFileCursor>
+float ReadFloatLE(TFileCursor & f) {
+ IEEE754binary32LE target;
+ if (!FileReader::Read(f, target)) {
+ return 0.0f;
+ }
+ return target;
+}
+
+// Read 32-Bit float in big-endian format.
+// If successful, the file cursor is advanced by the size of the float.
+template <typename TFileCursor>
+float ReadFloatBE(TFileCursor & f) {
+ IEEE754binary32BE target;
+ if (!FileReader::Read(f, target)) {
+ return 0.0f;
+ }
+ return target;
+}
+
+// Read 64-Bit float in little-endian format.
+// If successful, the file cursor is advanced by the size of the float.
+template <typename TFileCursor>
+double ReadDoubleLE(TFileCursor & f) {
+ IEEE754binary64LE target;
+ if (!FileReader::Read(f, target)) {
+ return 0.0;
+ }
+ return target;
+}
+
+// Read 64-Bit float in big-endian format.
+// If successful, the file cursor is advanced by the size of the float.
+template <typename TFileCursor>
+double ReadDoubleBE(TFileCursor & f) {
+ IEEE754binary64BE target;
+ if (!FileReader::Read(f, target)) {
+ return 0.0;
+ }
+ return target;
+}
+
+// Read a struct.
+// If successful, the file cursor is advanced by the size of the struct. Otherwise, the target is zeroed.
+template <typename T, typename TFileCursor>
+bool ReadStruct(TFileCursor & f, T & target) {
+ static_assert(mpt::is_binary_safe<T>::value);
+ if (!FileReader::Read(f, target)) {
+ mpt::reset(target);
+ return false;
+ }
+ return true;
+}
+
+// Allow to read a struct partially (if there's less memory available than the struct's size, fill it up with zeros).
+// The file cursor is advanced by "partialSize" bytes.
+template <typename T, typename TFileCursor>
+typename TFileCursor::pos_type ReadStructPartial(TFileCursor & f, T & target, typename TFileCursor::pos_type partialSize = sizeof(T)) {
+ static_assert(mpt::is_binary_safe<T>::value);
+ typename TFileCursor::pos_type copyBytes = std::min(partialSize, sizeof(T));
+ if (!f.CanRead(copyBytes)) {
+ copyBytes = f.BytesLeft();
+ }
+ f.GetRaw(mpt::span(mpt::as_raw_memory(target).data(), copyBytes));
+ std::memset(mpt::as_raw_memory(target).data() + copyBytes, 0, sizeof(target) - copyBytes);
+ f.Skip(partialSize);
+ return copyBytes;
+}
+
+// Read a null-terminated string into a std::string
+template <typename TFileCursor>
+bool ReadNullString(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits<typename TFileCursor::pos_type>::max()) {
+ dest.clear();
+ if (!f.CanRead(1)) {
+ return false;
+ }
+ char buffer[mpt::IO::BUFFERSIZE_MINUSCULE];
+ typename TFileCursor::pos_type avail = 0;
+ while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) {
+ auto end = std::find(buffer, buffer + avail, '\0');
+ dest.insert(dest.end(), buffer, end);
+ f.Skip(end - buffer);
+ if (end < buffer + avail) {
+ // Found null char
+ f.Skip(1);
+ break;
+ }
+ }
+ return dest.length() != 0;
+}
+
+// Read a string up to the next line terminator into a std::string
+template <typename TFileCursor>
+bool ReadLine(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits<typename TFileCursor::pos_type>::max()) {
+ dest.clear();
+ if (!f.CanRead(1)) {
+ return false;
+ }
+ char buffer[mpt::IO::BUFFERSIZE_MINUSCULE];
+ char c = '\0';
+ typename TFileCursor::pos_type avail = 0;
+ while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) {
+ auto end = std::find_if(buffer, buffer + avail, mpt::is_any_line_ending<char>);
+ dest.insert(dest.end(), buffer, end);
+ f.Skip(end - buffer);
+ if (end < buffer + avail) {
+ // Found line ending
+ f.Skip(1);
+ // Handle CRLF line ending
+ if (*end == '\r') {
+ if (FileReader::Read(f, c) && c != '\n') {
+ f.SkipBack(1);
+ }
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+// Compare a magic string with the current stream position.
+// Returns true if they are identical and advances the file cursor by the the length of the "magic" string.
+// Returns false if the string could not be found. The file cursor is not advanced in this case.
+template <size_t N, typename TFileCursor>
+bool ReadMagic(TFileCursor & f, const char (&magic)[N]) {
+ assert(magic[N - 1] == '\0');
+ for (std::size_t i = 0; i < N - 1; ++i) {
+ assert(magic[i] != '\0');
+ }
+ constexpr typename TFileCursor::pos_type magicLength = N - 1;
+ std::byte buffer[magicLength] = {};
+ if (f.GetRaw(mpt::span(buffer, magicLength)).size() != magicLength) {
+ return false;
+ }
+ if (std::memcmp(buffer, magic, magicLength)) {
+ return false;
+ }
+ f.Skip(magicLength);
+ return true;
+}
+
+// Read variable-length unsigned integer (as found in MIDI files).
+// If successful, the file cursor is advanced by the size of the integer and true is returned.
+// False is returned if not enough bytes were left to finish reading of the integer or if an overflow happened (source doesn't fit into target integer).
+// In case of an overflow, the target is also set to the maximum value supported by its data type.
+template <typename T, typename TFileCursor>
+bool ReadVarInt(TFileCursor & f, T & target) {
+ static_assert(std::numeric_limits<T>::is_integer == true && std::numeric_limits<T>::is_signed == false, "Target type is not an unsigned integer");
+
+ if (f.NoBytesLeft()) {
+ target = 0;
+ return false;
+ }
+
+ std::byte bytes[16]; // More than enough for any valid VarInt
+ typename TFileCursor::pos_type avail = f.GetRaw(mpt::as_span(bytes)).size();
+ typename TFileCursor::pos_type readPos = 1;
+
+ uint8 b = mpt::byte_cast<uint8>(bytes[0]);
+ target = (b & 0x7F);
+ std::size_t writtenBits = static_cast<std::size_t>(mpt::bit_width(target)); // Bits used in the most significant byte
+
+ while (readPos < avail && (b & 0x80) != 0) {
+ b = mpt::byte_cast<uint8>(bytes[readPos++]);
+ target <<= 7;
+ target |= (b & 0x7F);
+ writtenBits += 7;
+ if (readPos == avail) {
+ f.Skip(readPos);
+ avail = f.GetRaw(mpt::as_span(bytes)).size();
+ readPos = 0;
+ }
+ }
+ f.Skip(readPos);
+
+ if (writtenBits > sizeof(target) * 8u) {
+ // Overflow
+ target = std::numeric_limits<T>::max();
+ return false;
+ } else if ((b & 0x80) != 0) {
+ // Reached EOF
+ return false;
+ }
+ return true;
+}
+
+template <typename Tid, typename Tsize>
+struct ChunkHeader {
+ using id_type = Tid;
+ using size_type = Tsize;
+ friend constexpr bool declare_binary_safe(const ChunkHeader &) noexcept {
+ return true;
+ }
+ id_type id{};
+ size_type size{};
+ id_type GetID() const {
+ return id;
+ }
+ size_type GetLength() const {
+ return size;
+ }
+};
+
+template <typename TChunkHeader, typename TFileCursor>
+struct Chunk {
+ TChunkHeader header;
+ TFileCursor data;
+ TChunkHeader GetHeader() const {
+ return header;
+ }
+ TFileCursor GetData() const {
+ return data;
+ }
+};
+
+template <typename TChunkHeader, typename TFileCursor>
+struct ChunkList {
+
+ using id_type = decltype(TChunkHeader().GetID());
+ using size_type = decltype(TChunkHeader().GetLength());
+
+ std::vector<Chunk<TChunkHeader, TFileCursor>> chunks;
+
+ // Check if the list contains a given chunk.
+ bool ChunkExists(id_type id) const {
+ return std::find_if(chunks.begin(), chunks.end(), [id](const Chunk<TChunkHeader, TFileCursor> & chunk) { return chunk.GetHeader().GetID() == id; }) != chunks.end();
+ }
+
+ // Retrieve the first chunk with a given ID.
+ TFileCursor GetChunk(id_type id) const {
+ auto chunk = std::find_if(chunks.begin(), chunks.end(), [id](const Chunk<TChunkHeader, TFileCursor> & chunk) { return chunk.GetHeader().GetID() == id; });
+ if (chunk == chunks.end()) {
+ return TFileCursor();
+ }
+ return chunk->GetData();
+ }
+
+ // Retrieve all chunks with a given ID.
+ std::vector<TFileCursor> GetAllChunks(id_type id) const {
+ std::vector<TFileCursor> result;
+ for (const auto & chunk : chunks) {
+ if (chunk.GetHeader().GetID() == id) {
+ result.push_back(chunk.GetData());
+ }
+ }
+ return result;
+ }
+};
+
+// Read a single "TChunkHeader" chunk.
+// T is required to have the methods GetID() and GetLength().
+// GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
+template <typename TChunkHeader, typename TFileCursor>
+Chunk<TChunkHeader, TFileCursor> ReadNextChunk(TFileCursor & f, typename TFileCursor::pos_type alignment) {
+ Chunk<TChunkHeader, TFileCursor> result;
+ if (!FileReader::Read(f, result.header)) {
+ return Chunk<TChunkHeader, TFileCursor>();
+ }
+ typename TFileCursor::pos_type dataSize = result.header.GetLength();
+ result.data = f.ReadChunk(dataSize);
+ if (alignment > 1) {
+ if ((dataSize % alignment) != 0) {
+ f.Skip(alignment - (dataSize % alignment));
+ }
+ }
+ return result;
+}
+
+// Read a series of "TChunkHeader" chunks until the end of file is reached.
+// T is required to have the methods GetID() and GetLength().
+// GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
+template <typename TChunkHeader, typename TFileCursor>
+ChunkList<TChunkHeader, TFileCursor> ReadChunks(TFileCursor & f, typename TFileCursor::pos_type alignment) {
+ ChunkList<TChunkHeader, TFileCursor> result;
+ while (f.CanRead(sizeof(TChunkHeader))) {
+ result.chunks.push_back(FileReader::ReadNextChunk<TChunkHeader, TFileCursor>(f, alignment));
+ }
+ return result;
+}
+
+// Read a series of "TChunkHeader" chunks until a given chunk ID is found.
+// T is required to have the methods GetID() and GetLength().
+// GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
+template <typename TChunkHeader, typename TFileCursor>
+ChunkList<TChunkHeader, TFileCursor> ReadChunksUntil(TFileCursor & f, typename TFileCursor::pos_type alignment, decltype(TChunkHeader().GetID()) lastID) {
+ ChunkList<TChunkHeader, TFileCursor> result;
+ while (f.CanRead(sizeof(TChunkHeader))) {
+ result.chunks.push_back(FileReader::ReadNextChunk<TChunkHeader, TFileCursor>(f, alignment));
+ if (result.chunks.back().GetHeader().GetID() == lastID) {
+ break;
+ }
+ }
+ return result;
+}
+
+} // namespace FileReader
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_READ_FILEREADER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/io_write/buffer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/io_write/buffer.hpp
new file mode 100644
index 00000000..646ce0fc
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/io_write/buffer.hpp
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_IO_WRITE_BUFFER_HPP
+#define MPT_IO_WRITE_BUFFER_HPP
+
+
+
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/io/base.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace IO {
+
+
+
+// WriteBuffer class that avoids calling to the underlying file writing
+// functions for every operation, which would involve rather slow un-inlinable
+// virtual calls in the iostream and FILE* cases. It is the users responabiliy
+// to call HasWriteError() to check for writeback errors at this buffering
+// level.
+
+template <typename Tfile>
+class WriteBuffer {
+private:
+ mpt::byte_span buffer;
+ std::size_t size = 0;
+ Tfile & f;
+ bool writeError = false;
+
+public:
+ WriteBuffer(const WriteBuffer &) = delete;
+ WriteBuffer & operator=(const WriteBuffer &) = delete;
+
+public:
+ inline WriteBuffer(Tfile & f_, mpt::byte_span buffer_)
+ : buffer(buffer_)
+ , f(f_) {
+ }
+ inline ~WriteBuffer() noexcept(false) {
+ if (!writeError)
+ {
+ FlushLocal();
+ }
+ }
+
+public:
+ inline Tfile & file() const {
+ if (IsDirty())
+ {
+ FlushLocal();
+ }
+ return f;
+ }
+
+public:
+ inline bool HasWriteError() const {
+ return writeError;
+ }
+ inline void ClearError() {
+ writeError = false;
+ }
+ inline bool IsDirty() const {
+ return size > 0;
+ }
+ inline bool IsClean() const {
+ return size == 0;
+ }
+ inline bool IsFull() const {
+ return size == buffer.size();
+ }
+ inline std::size_t GetCurrentSize() const {
+ return size;
+ }
+ inline bool Write(mpt::const_byte_span data) {
+ bool result = true;
+ for (std::size_t i = 0; i < data.size(); ++i)
+ {
+ buffer[size] = data[i];
+ size++;
+ if (IsFull())
+ {
+ FlushLocal();
+ }
+ }
+ return result;
+ }
+ inline void FlushLocal() {
+ if (IsClean())
+ {
+ return;
+ }
+ try
+ {
+ if (!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size)))
+ {
+ writeError = true;
+ }
+ } catch (const std::exception &)
+ {
+ writeError = true;
+ throw;
+ }
+ size = 0;
+ }
+};
+
+template <typename Tfile>
+struct FileOperations<WriteBuffer<Tfile>> {
+
+private:
+ WriteBuffer<Tfile> & f;
+
+public:
+ FileOperations(WriteBuffer<Tfile> & f_)
+ : f(f_) {
+ return;
+ }
+
+public:
+ inline bool IsValid() {
+ return IsValid(f.file());
+ }
+
+ inline bool IsReadSeekable() {
+ return IsReadSeekable(f.file());
+ }
+
+ inline bool IsWriteSeekable() {
+ return IsWriteSeekable(f.file());
+ }
+
+ inline IO::Offset TellRead() {
+ f.FlushLocal();
+ return TellRead(f.file());
+ }
+
+ inline IO::Offset TellWrite() {
+ return TellWrite(f.file()) + f.GetCurrentSize();
+ }
+
+ inline bool SeekBegin() {
+ f.FlushLocal();
+ return SeekBegin(f.file());
+ }
+
+ inline bool SeekEnd() {
+ f.FlushLocal();
+ return SeekEnd(f.file());
+ }
+
+ inline bool SeekAbsolute(IO::Offset pos) {
+ f.FlushLocal();
+ return SeekAbsolute(f.file(), pos);
+ }
+
+ inline bool SeekRelative(IO::Offset off) {
+ f.FlushLocal();
+ return SeekRelative(f.file(), off);
+ }
+
+ inline mpt::byte_span ReadRawImpl(mpt::byte_span data) {
+ f.FlushLocal();
+ return ReadRawImpl(f.file(), data);
+ }
+
+ inline bool WriteRawImpl(mpt::const_byte_span data) {
+ return f.Write(data);
+ }
+
+ inline bool IsEof() {
+ f.FlushLocal();
+ return IsEof(f.file());
+ }
+
+ inline bool Flush() {
+ f.FlushLocal();
+ return Flush(f.file());
+ }
+};
+
+
+
+} // namespace IO
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_IO_WRITE_BUFFER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/json/json.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/json/json.hpp
new file mode 100644
index 00000000..f1fc42d8
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/json/json.hpp
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_JSON_JSON_HPP
+#define MPT_JSON_JSON_HPP
+
+#include "mpt/base/detect.hpp"
+#include "mpt/detect/nlohmann_json.hpp"
+
+#if MPT_DETECTED_NLOHMANN_JSON
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#endif // MPT_DETECTED_NLOHMANN_JSON
+
+#if MPT_DETECTED_NLOHMANN_JSON
+#include <optional>
+#endif // MPT_DETECTED_NLOHMANN_JSON
+
+#if MPT_DETECTED_NLOHMANN_JSON
+#include <nlohmann/json.hpp>
+#endif // MPT_DETECTED_NLOHMANN_JSON
+
+
+
+namespace nlohmann {
+template <>
+struct adl_serializer<mpt::ustring> {
+ static void to_json(json & j, const mpt::ustring & val) {
+ j = mpt::transcode<std::string>(mpt::common_encoding::utf8, val);
+ }
+ static void from_json(const json & j, mpt::ustring & val) {
+ val = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, j.get<std::string>());
+ }
+};
+template <typename Tvalue>
+struct adl_serializer<std::map<mpt::ustring, Tvalue>> {
+ static void to_json(json & j, const std::map<mpt::ustring, Tvalue> & val) {
+ std::map<std::string, Tvalue> utf8map;
+ for (const auto & value : val)
+ {
+ utf8map[mpt::transcode<std::string>(mpt::common_encoding::utf8, value.first)] = value.second;
+ }
+ j = std::move(utf8map);
+ }
+ static void from_json(const json & j, std::map<mpt::ustring, Tvalue> & val) {
+ std::map<std::string, Tvalue> utf8map = j.get<std::map<std::string, Tvalue>>();
+ std::map<mpt::ustring, Tvalue> result;
+ for (const auto & value : utf8map)
+ {
+ result[mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, value.first)] = value.second;
+ }
+ val = std::move(result);
+ }
+};
+template <typename Tvalue>
+struct adl_serializer<std::optional<Tvalue>> {
+ static void to_json(json & j, const std::optional<Tvalue> & val) {
+ j = (val ? json{*val} : json{nullptr});
+ }
+ static void from_json(const json & j, std::optional<Tvalue> & val) {
+ if (!j.is_null())
+ {
+ val = j.get<Tvalue>();
+ } else
+ {
+ val = std::nullopt;
+ }
+ }
+};
+} // namespace nlohmann
+
+
+
+#endif // MPT_JSON_JSON_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/library/library.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/library/library.hpp
new file mode 100644
index 00000000..1b20277a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/library/library.hpp
@@ -0,0 +1,465 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_LIBRARY_LIBRARY_HPP
+#define MPT_LIBRARY_LIBRARY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/detect/dl.hpp"
+#include "mpt/detect/ltdl.hpp"
+#include "mpt/path/path.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <filesystem>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#elif MPT_OS_ANDROID
+#elif defined(MPT_WITH_DL)
+#include <dlfcn.h>
+#elif defined(MPT_WITH_LTDL)
+#include <ltdl.h>
+#endif
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+class library {
+
+public:
+ typedef void * (*func_ptr)(); // pointer to function returning void *
+ static_assert(sizeof(func_ptr) == sizeof(void *));
+
+ enum class path_search {
+ invalid,
+ unsafe,
+ default_,
+ system,
+ application,
+ none,
+ };
+
+ enum class path_prefix {
+ none,
+ default_,
+ };
+
+ enum class path_suffix {
+ none,
+ default_,
+ };
+
+ struct path {
+
+ public:
+ path_search search = path_search::invalid;
+ path_prefix prefix = path_prefix::default_;
+ mpt::path filename{};
+ path_suffix suffix = path_suffix::default_;
+
+ static mpt::path default_prefix() {
+#if MPT_OS_WINDOWS
+ return MPT_PATH("");
+#else
+ return MPT_PATH("lib");
+#endif
+ }
+
+ static mpt::path default_suffix() {
+#if MPT_OS_WINDOWS
+ return MPT_PATH(".dll");
+#elif MPT_OS_ANDROID || defined(MPT_WITH_DL)
+ return MPT_PATH(".so");
+#else
+ return MPT_PATH("");
+#endif
+ }
+
+ std::optional<mpt::path> get_effective_filename() const {
+ switch (search) {
+ case library::path_search::unsafe:
+ break;
+ case library::path_search::default_:
+ break;
+ case library::path_search::system:
+ if (filename.is_absolute()) {
+ return std::nullopt;
+ }
+ break;
+ case library::path_search::application:
+ if (filename.is_absolute()) {
+ return std::nullopt;
+ }
+ break;
+ case library::path_search::none:
+ if (filename.is_relative()) {
+ return std::nullopt;
+ }
+ break;
+ case library::path_search::invalid:
+ return std::nullopt;
+ break;
+ }
+ mpt::path result{};
+ result += filename.parent_path();
+ result += ((prefix == path_prefix::default_) ? default_prefix() : mpt::path{});
+ result += filename.filename();
+ result += ((suffix == path_suffix::default_) ? default_suffix() : mpt::path{});
+ return result;
+ }
+ };
+
+#if MPT_OS_WINDOWS
+
+private:
+ HMODULE m_hModule = NULL;
+
+ library(HMODULE hModule)
+ : m_hModule(hModule) {
+ return;
+ }
+
+public:
+ library(const library &) = delete;
+ library & operator=(const library &) = delete;
+
+ library(library && other) noexcept
+ : m_hModule(other.m_hModule) {
+ other.m_hModule = NULL;
+ }
+
+ library & operator=(library && other) noexcept {
+ FreeLibrary(m_hModule);
+ m_hModule = other.m_hModule;
+ other.m_hModule = NULL;
+ return *this;
+ }
+
+private:
+#if !MPT_OS_WINDOWS_WINRT
+#if (_WIN32_WINNT < 0x0602)
+
+ static mpt::path get_application_path() {
+ std::vector<TCHAR> path(MAX_PATH);
+ while (GetModuleFileName(0, path.data(), mpt::saturate_cast<DWORD>(path.size())) >= path.size()) {
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return mpt::path{};
+ }
+ path.resize(mpt::exponential_grow(path.size()));
+ }
+ return mpt::path::from_stdpath(std::filesystem::absolute(mpt::transcode<mpt::path>(mpt::winstring(path.data())).stdpath()));
+ }
+
+ static mpt::path get_system_path() {
+ DWORD size = GetSystemDirectory(nullptr, 0);
+ std::vector<TCHAR> path(size + 1);
+ if (!GetSystemDirectory(path.data(), size + 1)) {
+ return mpt::path{};
+ }
+ return mpt::transcode<mpt::path>(mpt::winstring(path.data()));
+ }
+
+#endif
+#endif // !MPT_OS_WINDOWS_WINRT
+
+public:
+ static std::optional<library> load(mpt::library::path path) {
+
+ HMODULE hModule = NULL;
+
+ std::optional<mpt::path> optionalfilename = path.get_effective_filename();
+ if (!optionalfilename) {
+ return std::nullopt;
+ }
+ mpt::path & filename = optionalfilename.value();
+ if (filename.empty()) {
+ return std::nullopt;
+ }
+
+#if MPT_OS_WINDOWS_WINRT
+
+#if (_WIN32_WINNT < 0x0602)
+ MPT_UNUSED(path);
+ return std::nullopt;
+#else // Windows 8
+ switch (path.search) {
+ case library::path_search::unsafe:
+ hModule = LoadPackagedLibrary(filename.ospath().c_str(), 0);
+ break;
+ case library::path_search::default_:
+ hModule = LoadPackagedLibrary(filename.ospath().c_str(), 0);
+ break;
+ case library::path_search::system:
+ hModule = NULL; // Only application packaged libraries can be loaded dynamically in WinRT
+ break;
+ case library::path_search::application:
+ hModule = LoadPackagedLibrary(filename.ospath().c_str(), 0);
+ break;
+ case library::path_search::none:
+ hModule = NULL; // Absolute path is not supported in WinRT
+ break;
+ case library::path_search::invalid:
+ hModule = NULL;
+ break;
+ }
+#endif // Windows Version
+
+#else // !MPT_OS_WINDOWS_WINRT
+
+#if (_WIN32_WINNT >= 0x0602) // Windows 8
+
+ switch (path.search) {
+ case library::path_search::unsafe:
+ hModule = LoadLibrary(filename.ospath().c_str());
+ break;
+ case library::path_search::default_:
+ hModule = LoadLibraryEx(filename.ospath().c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
+ break;
+ case library::path_search::system:
+ hModule = LoadLibraryEx(filename.ospath().c_str(), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ break;
+ case library::path_search::application:
+ hModule = LoadLibraryEx(filename.ospath().c_str(), NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
+ break;
+ case library::path_search::none:
+ hModule = LoadLibraryEx(filename.ospath().c_str(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
+ break;
+ case library::path_search::invalid:
+ hModule = NULL;
+ break;
+ }
+
+#else // < Windows 8
+
+ switch (path.search) {
+ case library::path_search::unsafe:
+ hModule = LoadLibrary(filename.ospath().c_str());
+ break;
+ case library::path_search::default_:
+ hModule = LoadLibrary(filename.ospath().c_str());
+ break;
+ case library::path_search::system:
+ {
+ mpt::path system_path = get_system_path();
+ if (system_path.empty()) {
+ hModule = NULL;
+ } else {
+ hModule = LoadLibrary((system_path / filename).ospath().c_str());
+ }
+ }
+ break;
+ case library::path_search::application:
+ {
+ mpt::path application_path = get_application_path();
+ if (application_path.empty()) {
+ hModule = NULL;
+ } else {
+ hModule = LoadLibrary((application_path / filename).ospath().c_str());
+ }
+ }
+ break;
+ case library::path_search::none:
+ hModule = LoadLibrary(filename.ospath().c_str());
+ break;
+ case library::path_search::invalid:
+ hModule = NULL;
+ break;
+ }
+
+#endif // MPT_OS_WINDOWS_WINRT
+
+#endif
+
+ return library{hModule};
+ }
+
+ func_ptr get_address(const std::string & symbol) const {
+ return reinterpret_cast<func_ptr>(::GetProcAddress(m_hModule, symbol.c_str()));
+ }
+
+ ~library() {
+ FreeLibrary(m_hModule);
+ }
+
+#elif defined(MPT_WITH_DL)
+
+private:
+ void * handle = nullptr;
+
+ library(void * handle_)
+ : handle(handle_) {
+ return;
+ }
+
+public:
+ library(const library &) = delete;
+ library & operator=(const library &) = delete;
+
+ library(library && other) noexcept
+ : handle(other.handle) {
+ other.handle = NULL;
+ }
+
+ library & operator=(library && other) noexcept {
+ dlclose(handle);
+ m_hModule = other.handle;
+ other.handle = NULL;
+ return *this;
+ }
+
+public:
+ static std::optional<library> load(mpt::library::path path) {
+ std::optional<mpt::path> optionalfilename = path.get_effective_filename();
+ if (!optionalfilename) {
+ return std::nullopt;
+ }
+ mpt::path & filename = optionalfilename.value();
+ if (filename.empty()) {
+ return std::nullopt;
+ }
+ void * handle = dlopen(filename.ospath().c_str(), RTLD_NOW);
+ if (!handle) {
+ return std::nullopt;
+ }
+ return library{handle};
+ }
+
+ func_ptr get_address(const std::string & symbol) const {
+ return reinterpret_cast<func_ptr>(dlsym(handle, symbol.c_str()));
+ }
+
+ ~library() {
+ dlclose(handle);
+ }
+
+#elif defined(MPT_WITH_LTDL)
+
+private:
+ lt_dlhandle handle{};
+
+ library(lt_dlhandle handle_)
+ : handle(handle_) {
+ return;
+ }
+
+public:
+ library(const library &) = delete;
+ library & operator=(const library &) = delete;
+
+ library(library && other) noexcept
+ : handle(other.handle) {
+ other.handle = NULL;
+ }
+
+ library & operator=(library && other) noexcept {
+ dlclose(handle);
+ m_hModule = other.handle;
+ other.handle = NULL;
+ return *this;
+ }
+
+public:
+ static std::optional<library> load(mpt::library::path path) {
+ if (lt_dlinit() != 0) {
+ return std::nullopt;
+ }
+ std::optional<mpt::path> optionalfilename = path.get_effective_filename();
+ if (!optionalfilename) {
+ return std::nullopt;
+ }
+ mpt::path & filename = optionalfilename.value();
+ if (filename.empty()) {
+ return std::nullopt;
+ }
+ lt_dlhandle handle = lt_dlopenext(filename.ospath().c_str());
+ if (!handle) {
+ return std::nullopt;
+ }
+ return library{handle};
+ }
+
+ func_ptr get_address(const std::string & symbol) const {
+ return reinterpret_cast<func_ptr>(dlsym(handle, symbol.c_str()));
+ }
+
+ ~library() {
+ lt_dlclose(handle);
+ lt_dlexit();
+ }
+
+#else
+
+private:
+ void * handle = nullptr;
+
+ library(void * handle_)
+ : handle(handle_) {
+ return;
+ }
+
+public:
+ library(const library &) = delete;
+ library & operator=(const library &) = delete;
+
+ library(library && other) noexcept
+ : handle(other.handle) {
+ other.handle = nullptr;
+ }
+
+ library & operator=(library && other) noexcept {
+ handle = other.handle;
+ other.handle = nullptr;
+ return *this;
+ }
+
+public:
+ static std::optional<library> load(mpt::library::path path) {
+ MPT_UNUSED(path);
+ return std::nullopt;
+ }
+
+ func_ptr get_address(const std::string & symbol) const {
+ MPT_UNUSED(symbol);
+ return nullptr;
+ }
+
+ ~library() {
+ return;
+ }
+
+#endif
+
+ template <typename Tfunc>
+ bool bind(Tfunc *& f, const std::string & symbol) const {
+#if !(MPT_OS_WINDOWS && MPT_COMPILER_GCC)
+ // MinGW64 std::is_function is always false for non __cdecl functions.
+ // Issue is similar to <https://connect.microsoft.com/VisualStudio/feedback/details/774720/stl-is-function-bug>.
+ static_assert(std::is_function<Tfunc>::value);
+#endif
+ const func_ptr addr = get_address(symbol);
+ f = reinterpret_cast<Tfunc *>(addr);
+ return (addr != nullptr);
+ }
+};
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_LIBRARY_LIBRARY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/mutex/mutex.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/mutex/mutex.hpp
new file mode 100644
index 00000000..2a38eacd
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/mutex/mutex.hpp
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_MUTEX_MUTEX_HPP
+#define MPT_MUTEX_MUTEX_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+
+
+#if !MPT_PLATFORM_MULTITHREADED
+#define MPT_MUTEX_NONE 1
+#elif MPT_COMPILER_GENERIC
+#define MPT_MUTEX_STD 1
+#elif MPT_OS_WINDOWS && MPT_LIBCXX_GNU && !defined(_GLIBCXX_HAS_GTHREADS) && defined(MPT_WITH_MINGWSTDTHREADS)
+#define MPT_MUTEX_STD 1
+#elif MPT_OS_WINDOWS && MPT_LIBCXX_GNU && !defined(_GLIBCXX_HAS_GTHREADS)
+#define MPT_MUTEX_WIN32 1
+#else
+#define MPT_MUTEX_STD 1
+#endif
+
+#ifndef MPT_MUTEX_STD
+#define MPT_MUTEX_STD 0
+#endif
+#ifndef MPT_MUTEX_WIN32
+#define MPT_MUTEX_WIN32 0
+#endif
+#ifndef MPT_MUTEX_NONE
+#define MPT_MUTEX_NONE 0
+#endif
+
+#if MPT_MUTEX_STD
+#if !MPT_COMPILER_GENERIC && (defined(__MINGW32__) || defined(__MINGW64__)) && !defined(_GLIBCXX_HAS_GTHREADS) && defined(MPT_WITH_MINGWSTDTHREADS)
+#include <mingw.mutex.h>
+#else
+#include <mutex>
+#ifdef MPT_COMPILER_QUIRK_COMPLEX_STD_MUTEX
+#include <shared_mutex>
+#include <type_traits>
+#endif
+#endif
+#elif MPT_MUTEX_WIN32
+#include <windows.h>
+#endif // MPT_MUTEX
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_MUTEX_STD
+
+#ifdef MPT_COMPILER_QUIRK_COMPLEX_STD_MUTEX
+using mutex = std::conditional<sizeof(std::shared_mutex) < sizeof(std::mutex), std::shared_mutex, std::mutex>::type;
+#else
+using mutex = std::mutex;
+#endif
+using recursive_mutex = std::recursive_mutex;
+
+#elif MPT_MUTEX_WIN32
+
+#if (_WIN32_WINNT >= 0x0600) // _WIN32_WINNT_VISTA
+
+// compatible with c++11 std::mutex, can eventually be replaced without touching any usage site
+class mutex {
+private:
+ SRWLOCK impl = SRWLOCK_INIT;
+
+public:
+ mutex() = default;
+ ~mutex() = default;
+ void lock() {
+ AcquireSRWLockExclusive(&impl);
+ }
+ bool try_lock() {
+ return TryAcquireSRWLockExclusive(&impl) ? true : false;
+ }
+ void unlock() {
+ ReleaseSRWLockExclusive(&impl);
+ }
+};
+
+#else // !_WIN32_WINNT_VISTA
+
+// compatible with c++11 std::mutex, can eventually be replaced without touching any usage site
+class mutex {
+private:
+ CRITICAL_SECTION impl;
+
+public:
+ mutex() {
+ InitializeCriticalSection(&impl);
+ }
+ ~mutex() {
+ DeleteCriticalSection(&impl);
+ }
+ void lock() {
+ EnterCriticalSection(&impl);
+ }
+ bool try_lock() {
+ return TryEnterCriticalSection(&impl) ? true : false;
+ }
+ void unlock() {
+ LeaveCriticalSection(&impl);
+ }
+};
+
+#endif // _WIN32_WINNT_VISTA
+
+// compatible with c++11 std::recursive_mutex, can eventually be replaced without touching any usage site
+class recursive_mutex {
+private:
+ CRITICAL_SECTION impl;
+
+public:
+ recursive_mutex() {
+ InitializeCriticalSection(&impl);
+ }
+ ~recursive_mutex() {
+ DeleteCriticalSection(&impl);
+ }
+ void lock() {
+ EnterCriticalSection(&impl);
+ }
+ bool try_lock() {
+ return TryEnterCriticalSection(&impl) ? true : false;
+ }
+ void unlock() {
+ LeaveCriticalSection(&impl);
+ }
+};
+
+#else // MPT_MUTEX_NONE
+
+class mutex {
+public:
+ mutex() {
+ return;
+ }
+ ~mutex() {
+ return;
+ }
+ void lock() {
+ return;
+ }
+ bool try_lock() {
+ return true;
+ }
+ void unlock() {
+ return;
+ }
+};
+
+class recursive_mutex {
+public:
+ recursive_mutex() {
+ return;
+ }
+ ~recursive_mutex() {
+ return;
+ }
+ void lock() {
+ return;
+ }
+ bool try_lock() {
+ return true;
+ }
+ void unlock() {
+ return;
+ }
+};
+
+#endif // MPT_MUTEX
+
+#if MPT_MUTEX_STD
+
+template <typename T>
+using lock_guard = std::lock_guard<T>;
+
+#else // !MPT_MUTEX_STD
+
+// compatible with c++11 std::lock_guard, can eventually be replaced without touching any usage site
+template <typename mutex_type>
+class lock_guard {
+private:
+ mutex_type & mutex;
+
+public:
+ lock_guard(mutex_type & m)
+ : mutex(m) {
+ mutex.lock();
+ }
+ ~lock_guard() {
+ mutex.unlock();
+ }
+};
+
+#endif // MPT_MUTEX_STD
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_MUTEX_MUTEX_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/class.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/class.hpp
new file mode 100644
index 00000000..2c997966
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/class.hpp
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_OSINFO_CLASS_HPP
+#define MPT_OSINFO_CLASS_HPP
+
+
+
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/namespace.hpp"
+#if !MPT_OS_WINDOWS
+#include "mpt/string/buffer.hpp"
+#endif // !MPT_OS_WINDOWS
+
+#include <string>
+
+#if !MPT_OS_WINDOWS
+#include <sys/utsname.h>
+#endif // !MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace osinfo {
+
+enum class osclass {
+ Unknown,
+ Windows,
+ Linux,
+ Darwin,
+ BSD,
+ Haiku,
+ DOS,
+};
+
+inline mpt::osinfo::osclass get_class_from_sysname(const std::string & sysname) {
+ mpt::osinfo::osclass result = mpt::osinfo::osclass::Unknown;
+ if (sysname == "") {
+ result = mpt::osinfo::osclass::Unknown;
+ } else if (sysname == "Windows" || sysname == "WindowsNT" || sysname == "Windows_NT") {
+ result = mpt::osinfo::osclass::Windows;
+ } else if (sysname == "Linux") {
+ result = mpt::osinfo::osclass::Linux;
+ } else if (sysname == "Darwin") {
+ result = mpt::osinfo::osclass::Darwin;
+ } else if (sysname == "FreeBSD" || sysname == "DragonFly" || sysname == "NetBSD" || sysname == "OpenBSD" || sysname == "MidnightBSD") {
+ result = mpt::osinfo::osclass::BSD;
+ } else if (sysname == "Haiku") {
+ result = mpt::osinfo::osclass::Haiku;
+ } else if (sysname == "MS-DOS") {
+ result = mpt::osinfo::osclass::DOS;
+ }
+ return result;
+}
+
+inline mpt::osinfo::osclass get_class() {
+#if MPT_OS_WINDOWS
+ return mpt::osinfo::osclass::Windows;
+#else // !MPT_OS_WINDOWS
+ utsname uname_result;
+ if (uname(&uname_result) != 0) {
+ return mpt::osinfo::osclass::Unknown;
+ }
+ return mpt::osinfo::get_class_from_sysname(mpt::ReadAutoBuf(uname_result.sysname));
+#endif // MPT_OS_WINDOWS
+}
+
+} // namespace osinfo
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_OSINFO_CLASS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/windows_version.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/windows_version.hpp
new file mode 100644
index 00000000..33c046eb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/osinfo/windows_version.hpp
@@ -0,0 +1,452 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_OSINFO_WINDOWS_VERSION_HPP
+#define MPT_OSINFO_WINDOWS_VERSION_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace osinfo {
+
+namespace windows {
+
+
+
+class Version {
+
+public:
+ enum Number : uint64 {
+ WinNT4 = 0x0000000400000000ull,
+ Win2000 = 0x0000000500000000ull,
+ WinXP = 0x0000000500000001ull,
+ WinXP64 = 0x0000000500000002ull,
+ WinVista = 0x0000000600000000ull,
+ Win7 = 0x0000000600000001ull,
+ Win8 = 0x0000000600000002ull,
+ Win81 = 0x0000000600000003ull,
+ Win10 = 0x0000000a00000000ull,
+ WinNewer = Win10 + 1ull
+ };
+
+ struct System {
+ uint32 Major = 0;
+ uint32 Minor = 0;
+ constexpr System() noexcept
+ : Major(0)
+ , Minor(0) {
+ }
+ constexpr System(Number number) noexcept
+ : Major(static_cast<uint32>((static_cast<uint64>(number) >> 32) & 0xffffffffu))
+ , Minor(static_cast<uint32>((static_cast<uint64>(number) >> 0) & 0xffffffffu)) {
+ }
+ explicit constexpr System(uint64 number) noexcept
+ : Major(static_cast<uint32>((number >> 32) & 0xffffffffu))
+ , Minor(static_cast<uint32>((number >> 0) & 0xffffffffu)) {
+ }
+ explicit constexpr System(uint32 major, uint32 minor) noexcept
+ : Major(major)
+ , Minor(minor) {
+ }
+ constexpr operator uint64() const noexcept {
+ return (static_cast<uint64>(Major) << 32) | (static_cast<uint64>(Minor) << 0);
+ }
+ };
+
+ struct ServicePack {
+ uint16 Major = 0;
+ uint16 Minor = 0;
+ constexpr ServicePack() noexcept
+ : Major(0)
+ , Minor(0) {
+ }
+ explicit constexpr ServicePack(uint16 major, uint16 minor) noexcept
+ : Major(major)
+ , Minor(minor) {
+ }
+ constexpr bool HasServicePack() const noexcept {
+ return Major != 0 || Minor != 0;
+ }
+ constexpr operator uint32() const noexcept {
+ return (static_cast<uint32>(Major) << 16) | (static_cast<uint32>(Minor) << 0);
+ }
+ };
+
+ typedef uint32 Build;
+
+ typedef uint32 TypeId;
+
+private:
+ bool m_SystemIsWindows;
+
+ System m_System;
+ ServicePack m_ServicePack;
+ Build m_Build;
+ TypeId m_Type;
+
+private:
+ constexpr Version() noexcept
+ : m_SystemIsWindows(false)
+ , m_System()
+ , m_ServicePack()
+ , m_Build()
+ , m_Type() {
+ }
+
+public:
+ static constexpr Version NoWindows() noexcept {
+ return Version();
+ }
+
+ constexpr Version(mpt::osinfo::windows::Version::System system, mpt::osinfo::windows::Version::ServicePack servicePack, mpt::osinfo::windows::Version::Build build, mpt::osinfo::windows::Version::TypeId type) noexcept
+ : m_SystemIsWindows(true)
+ , m_System(system)
+ , m_ServicePack(servicePack)
+ , m_Build(build)
+ , m_Type(type) {
+ }
+
+public:
+#if MPT_OS_WINDOWS
+
+ static mpt::osinfo::windows::Version FromSDK() noexcept {
+ // Initialize to used SDK version
+#if defined(NTDDI_VERSION)
+#if NTDDI_VERSION >= 0x0A00000B // NTDDI_WIN10_CO Win11
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 22000, 0);
+#elif NTDDI_VERSION >= 0x0A00000A // NTDDI_WIN10_FE 21H2
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 19044, 0);
+//#elif // NTDDI_WIN10_FE 21H1
+// return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 19043, 0);
+//#elif // NTDDI_WIN10_FE 20H2
+// return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 19042, 0);
+#elif NTDDI_VERSION >= 0x0A000009 // NTDDI_WIN10_MN 2004/20H1
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 19041, 0);
+#elif NTDDI_VERSION >= 0x0A000008 // NTDDI_WIN10_VB 1909/19H2
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 18363, 0);
+#elif NTDDI_VERSION >= 0x0A000007 // NTDDI_WIN10_19H1 1903/19H1
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 18362, 0);
+#elif NTDDI_VERSION >= 0x0A000006 // NTDDI_WIN10_RS5 1809
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 17763, 0);
+#elif NTDDI_VERSION >= 0x0A000005 // NTDDI_WIN10_RS4 1803
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 17134, 0);
+#elif NTDDI_VERSION >= 0x0A000004 // NTDDI_WIN10_RS3 1709
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 16299, 0);
+#elif NTDDI_VERSION >= 0x0A000003 // NTDDI_WIN10_RS2 1703
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 15063, 0);
+#elif NTDDI_VERSION >= 0x0A000002 // NTDDI_WIN10_RS1 1607
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 14393, 0);
+#elif NTDDI_VERSION >= 0x0A000001 // NTDDI_WIN10_TH2 1511
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 10586, 0);
+#elif NTDDI_VERSION >= 0x0A000000 // NTDDI_WIN10 1507
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win10, mpt::osinfo::windows::Version::ServicePack(0, 0), 10240, 0);
+#elif NTDDI_VERSION >= 0x06030000 // NTDDI_WINBLUE
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win81, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= 0x06020000 // NTDDI_WIN8
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win8, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= 0x06010000 // NTDDI_WIN7
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win7, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= 0x06000000 // NTDDI_VISTA
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::WinVista, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= 0x05020000 // NTDDI_WS03
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::WinXP64, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= NTDDI_WINXP
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::WinXP, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#elif NTDDI_VERSION >= NTDDI_WIN2K
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::Win2000, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#else
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::WinNT4, mpt::osinfo::windows::Version::ServicePack(((NTDDI_VERSION & 0xffffu) >> 8) & 0xffu, ((NTDDI_VERSION & 0xffffu) >> 0) & 0xffu), 0, 0);
+#endif
+#elif defined(_WIN32_WINNT)
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::System((static_cast<uint64>(_WIN32_WINNT) & 0xff00u) >> 8, (static_cast<uint64>(_WIN32_WINNT) & 0x00ffu) >> 0), mpt::osinfo::windows::Version::ServicePack(0, 0), 0, 0);
+#else
+ return mpt::osinfo::windows::Version(mpt::osinfo::windows::Version::System(0, 0), mpt::osinfo::windows::Version::ServicePack(0, 0), 0, 0);
+#endif
+ }
+
+ static mpt::osinfo::windows::Version GatherWindowsVersion() noexcept {
+#if MPT_OS_WINDOWS_WINRT
+ return mpt::osinfo::windows::Version::FromSDK();
+#else // !MPT_OS_WINDOWS_WINRT
+ OSVERSIONINFOEXW versioninfoex{};
+ versioninfoex.dwOSVersionInfoSize = sizeof(versioninfoex);
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4996) // 'GetVersionExW': was declared deprecated
+#pragma warning(disable : 28159) // Consider using 'IsWindows*' instead of 'GetVersionExW'. Reason: Deprecated. Use VerifyVersionInfo* or IsWindows* macros from VersionHelpers.
+#endif // MPT_COMPILER_MSVC
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif // MPT_COMPILER_CLANG
+ if (GetVersionExW((LPOSVERSIONINFOW)&versioninfoex) == FALSE) {
+ return mpt::osinfo::windows::Version::FromSDK();
+ }
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif // MPT_COMPILER_MSVC
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif // MPT_COMPILER_CLANG
+ if (versioninfoex.dwPlatformId != VER_PLATFORM_WIN32_NT) {
+ return mpt::osinfo::windows::Version::FromSDK();
+ }
+ DWORD dwProductType = 0;
+#if (_WIN32_WINNT >= 0x0600) // _WIN32_WINNT_VISTA
+ dwProductType = PRODUCT_UNDEFINED;
+ if (GetProductInfo(versioninfoex.dwMajorVersion, versioninfoex.dwMinorVersion, versioninfoex.wServicePackMajor, versioninfoex.wServicePackMinor, &dwProductType) == FALSE) {
+ dwProductType = PRODUCT_UNDEFINED;
+ }
+#endif
+ return mpt::osinfo::windows::Version(
+ mpt::osinfo::windows::Version::System(versioninfoex.dwMajorVersion, versioninfoex.dwMinorVersion),
+ mpt::osinfo::windows::Version::ServicePack(versioninfoex.wServicePackMajor, versioninfoex.wServicePackMinor),
+ versioninfoex.dwBuildNumber,
+ dwProductType);
+#endif // MPT_OS_WINDOWS_WINRT
+ }
+
+#endif // MPT_OS_WINDOWS
+
+public:
+ static mpt::osinfo::windows::Version Current() noexcept {
+#if MPT_OS_WINDOWS
+ return GatherWindowsVersion();
+#else // !MPT_OS_WINDOWS
+ return mpt::osinfo::windows::Version::NoWindows();
+#endif // MPT_OS_WINDOWS
+ }
+
+public:
+ bool IsWindows() const noexcept {
+ return m_SystemIsWindows;
+ }
+
+ bool IsBefore(mpt::osinfo::windows::Version::System version) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ return m_System < version;
+ }
+
+ bool IsBefore(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::ServicePack servicePack) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System > version) {
+ return false;
+ }
+ if (m_System < version) {
+ return true;
+ }
+ return m_ServicePack < servicePack;
+ }
+
+ bool IsBefore(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::Build build) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System > version) {
+ return false;
+ }
+ if (m_System < version) {
+ return true;
+ }
+ return m_Build < build;
+ }
+
+ bool IsBefore(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::ServicePack servicePack, mpt::osinfo::windows::Version::Build build) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System > version) {
+ return false;
+ }
+ if (m_System < version) {
+ return true;
+ }
+ if (m_ServicePack > servicePack) {
+ return false;
+ }
+ if (m_ServicePack < servicePack) {
+ return true;
+ }
+ return m_Build < build;
+ }
+
+ bool IsBefore(mpt::osinfo::windows::Version version) const noexcept {
+ return IsBefore(version.GetSystem(), version.GetServicePack(), version.GetBuild());
+ }
+
+ bool IsAtLeast(mpt::osinfo::windows::Version::System version) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ return m_System >= version;
+ }
+
+ bool IsAtLeast(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::ServicePack servicePack) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System < version) {
+ return false;
+ }
+ if (m_System > version) {
+ return true;
+ }
+ return m_ServicePack >= servicePack;
+ }
+
+ bool IsAtLeast(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::Build build) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System < version) {
+ return false;
+ }
+ if (m_System > version) {
+ return true;
+ }
+ return m_Build >= build;
+ }
+
+ bool IsAtLeast(mpt::osinfo::windows::Version::System version, mpt::osinfo::windows::Version::ServicePack servicePack, mpt::osinfo::windows::Version::Build build) const noexcept {
+ if (!m_SystemIsWindows) {
+ return false;
+ }
+ if (m_System < version) {
+ return false;
+ }
+ if (m_System > version) {
+ return true;
+ }
+ if (m_ServicePack < servicePack) {
+ return false;
+ }
+ if (m_ServicePack > servicePack) {
+ return true;
+ }
+ return m_Build >= build;
+ }
+
+ bool IsAtLeast(mpt::osinfo::windows::Version version) const noexcept {
+ return IsAtLeast(version.GetSystem(), version.GetServicePack(), version.GetBuild());
+ }
+
+ mpt::osinfo::windows::Version::System GetSystem() const noexcept {
+ return m_System;
+ }
+
+ mpt::osinfo::windows::Version::ServicePack GetServicePack() const noexcept {
+ return m_ServicePack;
+ }
+
+ mpt::osinfo::windows::Version::Build GetBuild() const noexcept {
+ return m_Build;
+ }
+
+ mpt::osinfo::windows::Version::TypeId GetTypeId() const noexcept {
+ return m_Type;
+ }
+
+}; // class Version
+
+
+
+namespace wine {
+
+class version {
+protected:
+ bool valid = false;
+ uint8 vmajor = 0;
+ uint8 vminor = 0;
+ uint8 vupdate = 0;
+
+public:
+ version() {
+ return;
+ }
+ version(uint8 vmajor_, uint8 vminor_, uint8 vupdate_)
+ : valid(true)
+ , vmajor(vmajor_)
+ , vminor(vminor_)
+ , vupdate(vupdate_) {
+ return;
+ }
+
+public:
+ bool IsValid() const {
+ return valid;
+ }
+
+private:
+ static mpt::osinfo::windows::wine::version FromInteger(uint32 version) {
+ mpt::osinfo::windows::wine::version result;
+ result.valid = (version <= 0xffffff);
+ result.vmajor = static_cast<uint8>(version >> 16);
+ result.vminor = static_cast<uint8>(version >> 8);
+ result.vupdate = static_cast<uint8>(version >> 0);
+ return result;
+ }
+ uint32 AsInteger() const {
+ uint32 version = 0;
+ version |= static_cast<uint32>(vmajor) << 16;
+ version |= static_cast<uint32>(vminor) << 8;
+ version |= static_cast<uint32>(vupdate) << 0;
+ return version;
+ }
+
+public:
+ bool IsBefore(mpt::osinfo::windows::wine::version other) const {
+ if (!IsValid()) {
+ return false;
+ }
+ return (AsInteger() < other.AsInteger());
+ }
+ bool IsAtLeast(mpt::osinfo::windows::wine::version other) const {
+ if (!IsValid()) {
+ return false;
+ }
+ return (AsInteger() >= other.AsInteger());
+ }
+ uint8 GetMajor() const {
+ return vmajor;
+ }
+ uint8 GetMinor() const {
+ return vminor;
+ }
+ uint8 GetUpdate() const {
+ return vupdate;
+ }
+};
+
+} // namespace wine
+
+
+
+} // namespace windows
+
+} // namespace osinfo
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_OSINFO_WINDOWS_VERSION_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/out_of_memory/out_of_memory.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/out_of_memory/out_of_memory.hpp
new file mode 100644
index 00000000..d8bec8ac
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/out_of_memory/out_of_memory.hpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_OUT_OF_MEMORY_OUT_OF_MEMORY_HPP
+#define MPT_OUT_OF_MEMORY_OUT_OF_MEMORY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/detect/mfc.hpp"
+
+#include <exception>
+#if !MPT_DETECTED_MFC
+#include <new>
+#endif // !MPT_DETECTED_MFC
+
+#if MPT_DETECTED_MFC
+// cppcheck-suppress missingInclude
+#include <afx.h>
+#endif // MPT_DETECTED_MFC
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Exception handling helpers, because MFC requires explicit deletion of the exception object,
+// Thus, always call exactly one of mpt::rethrow_out_of_memory(e) or mpt::delete_out_of_memory(e).
+
+#if MPT_DETECTED_MFC
+
+using out_of_memory = CMemoryException *;
+
+[[noreturn]] inline void throw_out_of_memory() {
+ AfxThrowMemoryException();
+}
+
+[[noreturn]] inline void rethrow_out_of_memory(out_of_memory e) {
+ MPT_UNUSED(e);
+ // cppcheck false-positive
+ // cppcheck-suppress rethrowNoCurrentException
+ throw;
+}
+
+inline void delete_out_of_memory(out_of_memory & e) {
+ if (e) {
+ e->Delete();
+ e = nullptr;
+ }
+}
+
+#else // !MPT_DETECTED_MFC
+
+using out_of_memory = const std::bad_alloc &;
+
+[[noreturn]] inline void throw_out_of_memory() {
+ throw std::bad_alloc();
+}
+
+[[noreturn]] inline void rethrow_out_of_memory(out_of_memory e) {
+ MPT_UNUSED(e);
+ // cppcheck false-positive
+ // cppcheck-suppress rethrowNoCurrentException
+ throw;
+}
+
+inline void delete_out_of_memory(out_of_memory e) {
+ MPT_UNUSED(e);
+}
+
+#endif // MPT_DETECTED_MFC
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_OUT_OF_MEMORY_OUT_OF_MEMORY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/parse/parse.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/parse.hpp
new file mode 100644
index 00000000..30dc22a0
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/parse.hpp
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_PARSE_PARSE_HPP
+#define MPT_PARSE_PARSE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <ios>
+#include <locale>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+inline std::string parse_as_internal_string_type(const std::string & s) {
+ return s;
+}
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+inline std::wstring parse_as_internal_string_type(const std::wstring & s) {
+ return s;
+}
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_USTRING_MODE_WIDE
+template <typename Tstring>
+inline std::wstring parse_as_internal_string_type(const Tstring & s) {
+ return mpt::transcode<std::wstring>(s);
+}
+#else // !MPT_USTRING_MODE_WIDE
+template <typename Tstring>
+inline std::string parse_as_internal_string_type(const Tstring & s) {
+ return mpt::transcode<std::string>(mpt::common_encoding::utf8, s);
+}
+#endif // MPT_USTRING_MODE_WIDE
+
+
+template <typename T, typename Tstring>
+inline T ConvertStringTo(const Tstring & str) {
+ std::basic_istringstream<typename decltype(mpt::parse_as_internal_string_type(mpt::as_string(str)))::value_type> stream(mpt::parse_as_internal_string_type(mpt::as_string(str)));
+ stream.imbue(std::locale::classic());
+ T value;
+ if constexpr (std::is_same<T, signed char>::value) {
+ signed int tmp;
+ if (!(stream >> tmp)) {
+ return T{};
+ }
+ value = static_cast<T>(tmp);
+ } else if constexpr (std::is_same<T, unsigned char>::value) {
+ unsigned int tmp;
+ if (!(stream >> tmp)) {
+ return T{};
+ }
+ value = static_cast<T>(tmp);
+ } else {
+ if (!(stream >> value)) {
+ return T{};
+ }
+ }
+ return value;
+}
+
+
+template <typename T, typename Tstring>
+inline T ConvertHexStringTo(const Tstring & str) {
+ std::basic_istringstream<typename decltype(mpt::parse_as_internal_string_type(mpt::as_string(str)))::value_type> stream(mpt::parse_as_internal_string_type(mpt::as_string(str)));
+ stream.imbue(std::locale::classic());
+ T value;
+ if constexpr (std::is_same<T, signed char>::value) {
+ signed int tmp;
+ if (!(stream >> std::hex >> tmp)) {
+ return T{};
+ }
+ value = static_cast<T>(tmp);
+ } else if constexpr (std::is_same<T, unsigned char>::value) {
+ unsigned int tmp;
+ if (!(stream >> std::hex >> tmp)) {
+ return T{};
+ }
+ value = static_cast<T>(tmp);
+ } else {
+ if (!(stream >> std::hex >> value)) {
+ return T{};
+ }
+ }
+ return value;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_PARSE_PARSE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/parse/split.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/split.hpp
new file mode 100644
index 00000000..9166ad7e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/split.hpp
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_PARSE_SPLIT_HPP
+#define MPT_PARSE_SPLIT_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/utility.hpp"
+
+#include <vector>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename T, typename Tstring>
+std::vector<T> split_parse(const Tstring & str, const Tstring & sep = Tstring(1, char_constants<typename Tstring::value_type>::comma)) {
+ std::vector<T> vals;
+ std::size_t pos = 0;
+ while (str.find(sep, pos) != std::string::npos) {
+ vals.push_back(mpt::ConvertStringTo<T>(str.substr(pos, str.find(sep, pos) - pos)));
+ pos = str.find(sep, pos) + sep.length();
+ }
+ if (!vals.empty() || (str.substr(pos).length() > 0)) {
+ vals.push_back(mpt::ConvertStringTo<T>(str.substr(pos)));
+ }
+ return vals;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_PARSE_SPLIT_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/parse/tests/tests_parse.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/tests/tests_parse.hpp
new file mode 100644
index 00000000..6ee44e15
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/parse/tests/tests_parse.hpp
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_PARSE_TESTS_PARSE_HPP
+#define MPT_PARSE_TESTS_PARSE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <limits>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace parse {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/parse")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint32>("586"), 586u);
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint32>("2147483647"), (uint32)std::numeric_limits<int32>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint32>("4294967295"), std::numeric_limits<uint32>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<int64>("-9223372036854775808"), std::numeric_limits<int64>::min());
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<int64>("-159"), -159);
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<int64>("9223372036854775807"), std::numeric_limits<int64>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint64>("85059"), 85059u);
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint64>("9223372036854775807"), (uint64)std::numeric_limits<int64>::max());
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<uint64>("18446744073709551615"), std::numeric_limits<uint64>::max());
+
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<float>("-87.0"), -87.0f);
+#if !MPT_OS_DJGPP
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<double>("-0.5e-6"), -0.5e-6);
+#endif
+#if !MPT_OS_DJGPP
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<double>("58.65403492763"), 58.65403492763);
+#else
+ MPT_TEST_EXPECT_EQUAL(std::abs(mpt::ConvertStringTo<double>("58.65403492763") - 58.65403492763) <= 0.0001, true);
+#endif
+
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<float>(mpt::format<std::string>::val(-87.0)), -87.0f);
+#if !MPT_OS_DJGPP
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertStringTo<double>(mpt::format<std::string>::val(-0.5e-6)), -0.5e-6);
+#endif
+
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertHexStringTo<unsigned char>("fe"), 254);
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertHexStringTo<unsigned char>(L"fe"), 254);
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+ MPT_TEST_EXPECT_EQUAL(mpt::ConvertHexStringTo<unsigned int>(MPT_USTRING("ffff")), 65535u);
+}
+
+} // namespace parse
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_PARSE_TESTS_PARSE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/path/path.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/path/path.hpp
new file mode 100644
index 00000000..96f88f85
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/path/path.hpp
@@ -0,0 +1,506 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_PATH_PATH_HPP
+#define MPT_PATH_PATH_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <filesystem>
+#include <type_traits>
+#include <utility>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+// mpt::os_path is an alias to a string type that represents native operating system path encoding.
+// Note that this differs from std::filesystem::path::string_type on both Windows and Posix.
+// On Windows, we actually honor UNICODE and thus allow os_path.c_str() to be usable with WinAPI functions.
+// On Posix, we use a type-safe string type in locale encoding, in contrast to the encoding-confused supposedly UTF8 std::string in std::filesystem::path.
+
+#if MPT_OS_WINDOWS
+using os_path = mpt::winstring;
+#else // !MPT_OS_WINDOWS
+using os_path = mpt::lstring;
+#endif // MPT_OS_WINDOWS
+
+
+
+// mpt::os_path literals that do not involve runtime conversion.
+
+#if MPT_OS_WINDOWS
+#define MPT_OSPATH_CHAR(x) TEXT(x)
+#define MPT_OSPATH_LITERAL(x) TEXT(x)
+#define MPT_OSPATH(x) \
+ mpt::winstring { \
+ TEXT(x) \
+ }
+#else // !MPT_OS_WINDOWS
+#define MPT_OSPATH_CHAR(x) x
+#define MPT_OSPATH_LITERAL(x) x
+#define MPT_OSPATH(x) \
+ mpt::lstring { \
+ x \
+ }
+#endif // MPT_OS_WINDOWS
+
+
+
+template <>
+struct make_string_type<std::filesystem::path> {
+ using type = std::filesystem::path;
+};
+
+
+template <>
+struct is_string_type<std::filesystem::path> : public std::true_type { };
+
+
+
+template <>
+struct string_transcoder<std::filesystem::path> {
+ using string_type = std::filesystem::path;
+ static inline mpt::widestring decode(const string_type & src) {
+ if constexpr (std::is_same<std::filesystem::path::value_type, char>::value) {
+ // In contrast to standard recommendation and cppreference,
+ // native encoding on unix-like systems with libstdc++ or libc++ is actually *NOT* UTF8,
+ // but instead the conventional std::locale::locale("") encoding (which happens to be UTF8 on all modern systems, but this is not guaranteed).
+ // Note: libstdc++ and libc++ however assume that their internal representation is UTF8,
+ // which implies that wstring/u32string/u16string/u8string conversions are actually broken and MUST NOT be used, ever.
+ return mpt::transcode<mpt::widestring>(mpt::logical_encoding::locale, src.string());
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, wchar_t>::value) {
+ return mpt::transcode<mpt::widestring>(src.wstring());
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char32_t>::value) {
+ return mpt::transcode<mpt::widestring>(src.u32string());
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char16_t>::value) {
+ return mpt::transcode<mpt::widestring>(src.u16string());
+#if MPT_CXX_AT_LEAST(20)
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char8_t>::value) {
+ return mpt::transcode<mpt::widestring>(src.u8string());
+#endif
+ } else {
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ return mpt::transcode<mpt::widestring>(src.wstring());
+#elif MPT_OS_WINDOWS
+ return mpt::transcode<mpt::widestring>(mpt::logical_encoding::locale, src.string());
+#else
+ // completely unknown implementation, assume it can sanely convert to/from UTF16/UTF32
+ if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
+ return mpt::transcode<mpt::widestring>(src.u32string());
+ } else {
+ return mpt::transcode<mpt::widestring>(src.u16string());
+ }
+#endif
+ }
+ }
+ static inline string_type encode(const mpt::widestring & src, std::filesystem::path::format fmt) {
+ if constexpr (std::is_same<std::filesystem::path::value_type, char>::value) {
+ // In contrast to standard recommendation and cppreference,
+ // native encoding on unix-like systems with libstdc++ or libc++ is actually *NOT* UTF8,
+ // but instead the conventional std::locale::locale("") encoding (which happens to be UTF8 on all modern systems, but this is not guaranteed).
+ // Note: libstdc++ and libc++ however assume that their internal representation is UTF8,
+ // which implies that wstring/u32string/u16string/u8string conversions are actually broken and MUST NOT be used, ever.
+ return std::filesystem::path{mpt::transcode<std::string>(mpt::logical_encoding::locale, src), fmt};
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, wchar_t>::value) {
+ return std::filesystem::path{mpt::transcode<std::wstring>(src), fmt};
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char32_t>::value) {
+ return std::filesystem::path{mpt::transcode<std::u32string>(src), fmt};
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char16_t>::value) {
+ return std::filesystem::path{mpt::transcode<std::u16string>(src), fmt};
+#if MPT_CXX_AT_LEAST(20)
+ } else if constexpr (std::is_same<std::filesystem::path::value_type, char8_t>::value) {
+ return std::filesystem::path{mpt::transcode<std::u8string>(src), fmt};
+#endif
+ } else {
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ return std::filesystem::path{mpt::transcode<std::wstring>(src), fmt};
+#elif MPT_OS_WINDOWS
+ return std::filesystem::path{mpt::transcode<std::string>(mpt::logical_encoding::locale, src), fmt};
+#else
+ // completely unknown implementation, assume it can sanely convert to/from UTF16/UTF32
+ if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
+ return std::filesystem::path{mpt::transcode<std::u32string>(src), fmt};
+ } else {
+ return std::filesystem::path{mpt::transcode<std::u16string>(src), fmt};
+ }
+#endif
+ }
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return encode(src, std::filesystem::path::auto_format);
+ }
+};
+
+
+
+// Best heuristics we can come up with to define std::filesystem::path literals that do not involve (or at least only non-lossy) runtime conversion.
+
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+#define MPT_STDPATH_CHAR(x) L##x
+#define MPT_STDPATH_LITERAL(x) L##x
+#define MPT_STDPATH(x) \
+ std::filesystem::path { \
+ L##x \
+ }
+#elif MPT_OS_WINDOWS
+#define MPT_STDPATH_CHAR(x) x
+#define MPT_STDPATH_LITERAL(x) x
+#define MPT_STDPATH(x) \
+ std::filesystem::path { \
+ x \
+ }
+#elif MPT_CXX_AT_LEAST(20)
+#define MPT_STDPATH_CHAR(x) u8##x
+#define MPT_STDPATH_LITERAL(x) u8##x
+#define MPT_STDPATH(x) \
+ std::filesystem::path { \
+ u8##x \
+ }
+#else
+#define MPT_STDPATH_CHAR(x) U##x
+#define MPT_STDPATH_LITERAL(x) U##x
+#define MPT_STDPATH(x) \
+ std::filesystem::path { \
+ U##x \
+ }
+#endif
+
+
+
+// std::filesystem::path offers implicit conversions to/from types of which it is confused about their encodings.
+// The only way to work around this problem is to implement our own mpt::path that does not do such broken nonsense.
+// We offer no implicit conversions and only explicit conversions from std::filesystem::path and mpt::os_path.
+
+class path {
+public:
+ using format = std::filesystem::path::format;
+ using std_value_type = std::filesystem::path::value_type;
+ using std_string_type = std::filesystem::path;
+ static constexpr inline std_value_type std_preferred_separator = std::filesystem::path::preferred_separator;
+ using os_value_type = os_path::value_type;
+ using os_string_type = os_path;
+ static constexpr inline os_value_type os_preferred_separator = static_cast<os_value_type>(std::filesystem::path::preferred_separator);
+
+private:
+ std::filesystem::path m_path;
+
+private:
+ template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
+ explicit path(const path_type & p)
+ : m_path(p) {
+ return;
+ }
+ template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
+ explicit path(path_type && p)
+ : m_path(std::forward<path_type>(p)) {
+ return;
+ }
+
+public:
+ struct internal {
+ static inline path make_path(std::filesystem::path && p) {
+ return path{std::move(p)};
+ }
+ };
+
+public:
+ template <typename path_type, std::enable_if_t<std::is_same<path_type, std::filesystem::path>::value, bool> = true>
+ static path from_stdpath(const path_type & p) {
+ return path{p};
+ }
+ static std::filesystem::path to_stdpath(const path & p) {
+ return p.m_path;
+ }
+ static os_path to_ospath(const path & p) {
+ return mpt::transcode<os_path>(p.m_path);
+ }
+ static std::filesystem::path from_ospath(const os_path & s, std::filesystem::path::format fmt = std::filesystem::path::auto_format) {
+ return string_transcoder<std::filesystem::path>{}.encode(mpt::transcode<mpt::widestring>(s), fmt);
+ }
+
+public:
+ path() noexcept = default;
+ path(const path & p)
+ : m_path(p.m_path) {
+ return;
+ }
+ path(path && p)
+ : m_path(std::move(p.m_path)) {
+ return;
+ }
+ explicit path(const os_path & s, std::filesystem::path::format fmt = std::filesystem::path::auto_format)
+ : m_path(from_ospath(s, fmt)) {
+ return;
+ }
+ path & operator=(const path & p) {
+ m_path = p.m_path;
+ return *this;
+ }
+ path & operator=(path && p) {
+ m_path = std::move(p.m_path);
+ return *this;
+ }
+ path & assign(const path & p) {
+ m_path = p.m_path;
+ return *this;
+ }
+ path & assign(path && p) {
+ m_path = std::move(p.m_path);
+ return *this;
+ }
+ path & operator/=(const path & p) {
+ m_path /= p.m_path;
+ return *this;
+ }
+ path & operator/=(path && p) {
+ m_path /= std::move(p.m_path);
+ return *this;
+ }
+ // concatenation
+ path & append(const path & p) {
+ m_path /= p.m_path;
+ return *this;
+ }
+ path & append(path && p) {
+ m_path /= std::move(p.m_path);
+ return *this;
+ }
+ path & operator+=(const path & p) {
+ m_path += p.m_path;
+ return *this;
+ }
+ path & operator+=(path && p) {
+ m_path += std::move(p.m_path);
+ return *this;
+ }
+ path & concat(const path & p) {
+ m_path += p.m_path;
+ return *this;
+ }
+ path & concat(path && p) {
+ m_path += std::move(p.m_path);
+ return *this;
+ }
+ // modifiers
+ void clear() noexcept {
+ m_path.clear();
+ }
+ path & make_preferred() {
+ m_path.make_preferred();
+ return *this;
+ }
+ path & remove_filename() {
+ m_path.remove_filename();
+ return *this;
+ }
+ path & replace_filename(const path & replacement) {
+ m_path.replace_filename(replacement.m_path);
+ return *this;
+ }
+ path & replace_extension(const path & replacement = path()) {
+ m_path.replace_extension(replacement.m_path);
+ return *this;
+ }
+ void swap(path & other) {
+ m_path.swap(other.m_path);
+ }
+ // format observers
+ std::filesystem::path stdpath() const {
+ return m_path;
+ }
+ os_path ospath() const {
+ return to_ospath(*this);
+ }
+ // compare
+ int compare(const path & p) const noexcept {
+ return m_path.compare(p.m_path);
+ }
+ // generation
+ path lexically_normal() const {
+ return path{m_path.lexically_normal()};
+ }
+ path lexically_relative(const path & base) const {
+ return path{m_path.lexically_relative(base.m_path)};
+ }
+ path lexically_proximate(const path & base) const {
+ return path{m_path.lexically_proximate(base.m_path)};
+ }
+ // decomposition
+ path root_name() const {
+ return path{m_path.root_name()};
+ }
+ path root_directory() const {
+ return path{m_path.root_directory()};
+ }
+ path root_path() const {
+ return path{m_path.root_path()};
+ }
+ path relative_path() const {
+ return path{m_path.relative_path()};
+ }
+ path parent_path() const {
+ return path{m_path.parent_path()};
+ }
+ path filename() const {
+ return path{m_path.filename()};
+ }
+ path stem() const {
+ return path{m_path.stem()};
+ }
+ path extension() const {
+ return path{m_path.extension()};
+ }
+ // queries
+ [[nodiscard]] bool empty() const noexcept {
+ return m_path.empty();
+ }
+ bool has_root_path() const {
+ return m_path.has_root_path();
+ }
+ bool has_root_name() const {
+ return m_path.has_root_name();
+ }
+ bool has_root_directory() const {
+ return m_path.has_root_directory();
+ }
+ bool has_relative_path() const {
+ return m_path.has_relative_path();
+ }
+ bool has_parent_path() const {
+ return m_path.has_parent_path();
+ }
+ bool has_filename() const {
+ return m_path.has_filename();
+ }
+ bool has_stem() const {
+ return m_path.has_stem();
+ }
+ bool has_extension() const {
+ return m_path.has_extension();
+ }
+ bool is_absolute() const {
+ return m_path.is_absolute();
+ }
+ bool is_relative() const {
+ return m_path.is_relative();
+ }
+ // comparison operators
+ friend bool operator==(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path == rhs.m_path;
+ }
+ friend bool operator!=(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path != rhs.m_path;
+ }
+ friend bool operator<(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path < rhs.m_path;
+ }
+ friend bool operator<=(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path <= rhs.m_path;
+ }
+ friend bool operator>(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path > rhs.m_path;
+ }
+ friend bool operator>=(const path & lhs, const path & rhs) noexcept {
+ return lhs.m_path >= rhs.m_path;
+ }
+ // copncatenation operator
+ friend path operator/(const path & lhs, const path & rhs) {
+ return path{lhs.m_path / rhs.m_path};
+ }
+};
+
+
+
+// Best heuristics we can come up with to define mpt::path literals that do not involve (or at least only non-lossy) runtime conversion.
+
+#if MPT_OS_WINDOWS && !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+#define MPT_PATH_CHAR(x) L##x
+#define MPT_PATH_LITERAL(x) L##x
+#define MPT_PATH(x) mpt::path::internal::make_path(L##x)
+#elif MPT_OS_WINDOWS
+#define MPT_PATH_CHAR(x) x
+#define MPT_PATH_LITERAL(x) x
+#define MPT_PATH(x) mpt::path::internal::make_path(x)
+#elif MPT_CXX_AT_LEAST(20)
+#define MPT_PATH_CHAR(x) u8##x
+#define MPT_PATH_LITERAL(x) u8##x
+#define MPT_PATH(x) mpt::path::internal::make_path(u8##x)
+#else
+#define MPT_PATH_CHAR(x) U##x
+#define MPT_PATH_LITERAL(x) U##x
+#define MPT_PATH(x) mpt::path::internal::make_path(U##x)
+#endif
+
+
+
+template <>
+struct make_string_type<mpt::path> {
+ using type = mpt::path;
+};
+
+
+template <>
+struct is_string_type<mpt::path> : public std::true_type { };
+
+
+
+template <>
+struct string_transcoder<mpt::path> {
+ using string_type = mpt::path;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::transcode<mpt::widestring>(src.ospath());
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return mpt::path{mpt::transcode<mpt::os_path>(src)};
+ }
+};
+
+
+
+inline mpt::os_path support_long_path(const mpt::os_path & path) {
+#if MPT_OS_WINDOWS
+ if (path.length() < MAX_PATH) {
+ // path is short enough
+ return path;
+ }
+ if (path.substr(0, 4) == MPT_OSPATH_LITERAL("\\\\?\\")) {
+ // path is already in prefixed form
+ return path;
+ }
+ const mpt::os_path absolute_path = mpt::transcode<mpt::os_path>(std::filesystem::absolute(mpt::transcode<std::filesystem::path>(path)));
+ if (absolute_path.substr(0, 2) == MPT_OSPATH_LITERAL("\\\\")) {
+ // Path is a network share: \\server\foo.bar -> \\?\UNC\server\foo.bar
+ return MPT_OSPATH_LITERAL("\\\\?\\UNC") + absolute_path.substr(1);
+ } else {
+ // Regular file: C:\foo.bar -> \\?\C:\foo.bar
+ return MPT_OSPATH_LITERAL("\\\\?\\") + absolute_path;
+ }
+#else
+ return path;
+#endif
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_PATH_PATH_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/crand.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/crand.hpp
new file mode 100644
index 00000000..610b84f7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/crand.hpp
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_CRAND_HPP
+#define MPT_RANDOM_CRAND_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/random/random.hpp"
+
+#include <cstdlib>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+class crand {
+public:
+ using state_type = void;
+ using result_type = int;
+
+private:
+ static void reseed(uint32 seed) {
+ std::srand(seed);
+ }
+
+public:
+ template <typename Trd>
+ static void reseed(Trd & rd) {
+ reseed(mpt::random<uint32>(rd));
+ }
+
+public:
+ crand() = default;
+ explicit crand(const std::string &) {
+ return;
+ }
+
+public:
+ static MPT_CONSTEXPRINLINE result_type min() {
+ return 0;
+ }
+ static MPT_CONSTEXPRINLINE result_type max() {
+ return RAND_MAX;
+ }
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return mpt::lower_bound_entropy_bits(RAND_MAX);
+ }
+ result_type operator()() {
+ return std::rand();
+ }
+};
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_CRAND_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/default_engines.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/default_engines.hpp
new file mode 100644
index 00000000..74ae81f2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/default_engines.hpp
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_DEFAULT_ENGINES_HPP
+#define MPT_RANDOM_DEFAULT_ENGINES_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/random/engine.hpp"
+#include "mpt/random/engine_lcg.hpp"
+
+#include <random>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+using deterministic_fast_engine = mpt::lcg_msvc;
+using deterministic_good_engine = mpt::lcg_musl;
+
+// We cannot use std::minstd_rand here because it has not a power-of-2 sized
+// output domain which we rely upon.
+using fast_engine = mpt::lcg_msvc; // about 3 ALU operations, ~32bit of state, suited for inner loops
+using good_engine = std::ranlux48;
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_DEFAULT_ENGINES_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/device.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/device.hpp
new file mode 100644
index 00000000..7afd4898
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/device.hpp
@@ -0,0 +1,304 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_DEVICE_HPP
+#define MPT_RANDOM_DEVICE_HPP
+
+
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/crc/crc.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/mutex/mutex.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/random/engine.hpp"
+#include "mpt/random/engine_lcg.hpp"
+#include "mpt/random/random.hpp"
+
+#include <chrono>
+#include <limits>
+#include <memory>
+#include <random>
+#include <string>
+
+#include <cmath>
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+inline constexpr uint32 DETERMINISTIC_RNG_SEED = 3141592653u; // pi
+
+
+
+template <typename T>
+struct default_radom_seed_hash {
+};
+
+template <>
+struct default_radom_seed_hash<uint8> {
+ using type = mpt::crc16;
+};
+
+template <>
+struct default_radom_seed_hash<uint16> {
+ using type = mpt::crc16;
+};
+
+template <>
+struct default_radom_seed_hash<uint32> {
+ using type = mpt::crc32c;
+};
+
+template <>
+struct default_radom_seed_hash<uint64> {
+ using type = mpt::crc64_jones;
+};
+
+
+class prng_random_device_time_seeder {
+
+public:
+ template <typename T>
+ inline T generate_seed() {
+ // Note: CRC is actually not that good a choice here, but it is simple and we
+ // already have an implementaion available. Better choices for mixing entropy
+ // would be a hash function with proper avalanche characteristics or a block
+ // or stream cipher with any pre-choosen random key and IV. The only aspect we
+ // really need here is whitening of the bits.
+ typename mpt::default_radom_seed_hash<T>::type hash;
+
+ {
+ uint64be time;
+ time = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock().now().time_since_epoch()).count();
+ std::byte bytes[sizeof(time)];
+ std::memcpy(bytes, &time, sizeof(time));
+ hash(std::begin(bytes), std::end(bytes));
+ }
+#if !defined(MPT_COMPILER_QUIRK_CHRONO_NO_HIGH_RESOLUTION_CLOCK)
+ // Avoid std::chrono::high_resolution_clock on Emscripten because availability is problematic in AudioWorklet context.
+ {
+ uint64be time;
+ time = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock().now().time_since_epoch()).count();
+ std::byte bytes[sizeof(time)];
+ std::memcpy(bytes, &time, sizeof(time));
+ hash(std::begin(bytes), std::end(bytes));
+ }
+#endif // !MPT_COMPILER_QUIRK_CHRONO_NO_HIGH_RESOLUTION_CLOCK
+
+ return static_cast<T>(hash.result());
+ }
+
+public:
+ prng_random_device_time_seeder() = default;
+};
+
+
+// C++11 std::random_device may be implemented as a deterministic PRNG.
+// There is no way to seed this PRNG and it is allowed to be seeded with the
+// same value on each program invocation. This makes std::random_device
+// completely useless even as a non-cryptographic entropy pool.
+// We fallback to time-seeded std::mt19937 if std::random_device::entropy() is
+// 0 or less.
+class sane_random_device {
+private:
+ mpt::mutex m;
+ std::string token;
+#if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE)
+ std::unique_ptr<std::random_device> prd;
+ bool rd_reliable{false};
+#endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ std::unique_ptr<std::mt19937> rd_fallback;
+
+public:
+ using result_type = unsigned int;
+
+private:
+ void init_fallback() {
+ if (!rd_fallback) {
+ if (token.length() > 0) {
+ uint64 seed_val = mpt::prng_random_device_time_seeder().generate_seed<uint64>();
+ std::vector<unsigned int> seeds;
+ seeds.push_back(static_cast<uint32>(seed_val >> 32));
+ seeds.push_back(static_cast<uint32>(seed_val >> 0));
+ for (std::size_t i = 0; i < token.length(); ++i) {
+ seeds.push_back(static_cast<unsigned int>(static_cast<unsigned char>(token[i])));
+ }
+ std::seed_seq seed(seeds.begin(), seeds.end());
+ rd_fallback = std::make_unique<std::mt19937>(seed);
+ } else {
+ uint64 seed_val = mpt::prng_random_device_time_seeder().generate_seed<uint64>();
+ unsigned int seeds[2];
+ seeds[0] = static_cast<uint32>(seed_val >> 32);
+ seeds[1] = static_cast<uint32>(seed_val >> 0);
+ std::seed_seq seed(seeds + 0, seeds + 2);
+ rd_fallback = std::make_unique<std::mt19937>(seed);
+ }
+ }
+ }
+
+public:
+ sane_random_device() {
+#if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE)
+ try {
+ prd = std::make_unique<std::random_device>();
+ rd_reliable = ((*prd).entropy() > 0.0);
+ } catch (mpt::out_of_memory e) {
+ mpt::rethrow_out_of_memory(e);
+ } catch (const std::exception &) {
+ rd_reliable = false;
+ }
+ if (!rd_reliable) {
+ init_fallback();
+ }
+#else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ init_fallback();
+#endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ }
+ sane_random_device(const std::string & token_)
+ : token(token_) {
+#if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE)
+ try {
+ prd = std::make_unique<std::random_device>(token);
+ rd_reliable = ((*prd).entropy() > 0.0);
+ } catch (mpt::out_of_memory e) {
+ mpt::rethrow_out_of_memory(e);
+ } catch (const std::exception &) {
+ rd_reliable = false;
+ }
+ if (!rd_reliable) {
+ init_fallback();
+ }
+#else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ init_fallback();
+#endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ }
+ static MPT_CONSTEXPRINLINE result_type min() {
+ return std::numeric_limits<result_type>::min();
+ }
+ static MPT_CONSTEXPRINLINE result_type max() {
+ return std::numeric_limits<result_type>::max();
+ }
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return sizeof(result_type) * 8;
+ }
+ result_type operator()() {
+ mpt::lock_guard<mpt::mutex> l(m);
+ result_type result = 0;
+#if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE)
+ if (prd) {
+ try {
+ if constexpr (std::random_device::min() != 0 || !mpt::is_mask(std::random_device::max())) {
+ // insane std::random_device
+ // This implementation is not exactly uniformly distributed but good enough
+ // for OpenMPT.
+ constexpr double rd_min = static_cast<double>(std::random_device::min());
+ constexpr double rd_max = static_cast<double>(std::random_device::max());
+ constexpr double rd_range = rd_max - rd_min;
+ constexpr double rd_size = rd_range + 1.0;
+ const double rd_entropy = mpt::log2(rd_size);
+ const int iterations = static_cast<int>(std::ceil(result_bits() / rd_entropy));
+ double tmp = 0.0;
+ for (int i = 0; i < iterations; ++i) {
+ tmp = (tmp * rd_size) + (static_cast<double>((*prd)()) - rd_min);
+ }
+ double result_01 = std::floor(tmp / std::pow(rd_size, iterations));
+ result = static_cast<result_type>(std::floor(result_01 * (static_cast<double>(max() - min()) + 1.0))) + min();
+ } else {
+ // sane std::random_device
+ result = 0;
+ std::size_t rd_bits = mpt::lower_bound_entropy_bits(std::random_device::max());
+ for (std::size_t entropy = 0; entropy < (sizeof(result_type) * 8); entropy += rd_bits) {
+ if (rd_bits < (sizeof(result_type) * 8)) {
+ result = (result << rd_bits) | static_cast<result_type>((*prd)());
+ } else {
+ result = result | static_cast<result_type>((*prd)());
+ }
+ }
+ }
+ } catch (const std::exception &) {
+ rd_reliable = false;
+ init_fallback();
+ }
+ } else {
+ rd_reliable = false;
+ }
+ if (!rd_reliable) {
+ // std::random_device is unreliable
+ // XOR the generated random number with more entropy from the time-seeded
+ // PRNG.
+ // Note: This is safe even if the std::random_device itself is implemented
+ // as a std::mt19937 PRNG because we are very likely using a different
+ // seed.
+ result ^= mpt::random<result_type>(*rd_fallback);
+ }
+#else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ result ^= mpt::random<result_type>(*rd_fallback);
+#endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE
+ return result;
+ }
+};
+
+
+class prng_random_device_deterministic_seeder {
+protected:
+ template <typename T>
+ constexpr T generate_seed() noexcept {
+ return static_cast<T>(mpt::DETERMINISTIC_RNG_SEED);
+ }
+
+protected:
+ prng_random_device_deterministic_seeder() = default;
+};
+
+template <typename Trng = mpt::lcg_musl, typename seeder = mpt::prng_random_device_time_seeder>
+class prng_random_device
+ : private seeder {
+public:
+ using result_type = unsigned int;
+
+private:
+ mpt::mutex m;
+ Trng rng;
+
+public:
+ prng_random_device()
+ : rng(seeder::template generate_seed<typename Trng::state_type>()) {
+ return;
+ }
+ prng_random_device(const std::string &)
+ : rng(seeder::template generate_seed<typename Trng::state_type>()) {
+ return;
+ }
+ static MPT_CONSTEXPRINLINE result_type min() {
+ return std::numeric_limits<unsigned int>::min();
+ }
+ static MPT_CONSTEXPRINLINE result_type max() {
+ return std::numeric_limits<unsigned int>::max();
+ }
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return sizeof(unsigned int) * 8;
+ }
+ result_type operator()() {
+ mpt::lock_guard<mpt::mutex> l(m);
+ return mpt::random<unsigned int>(rng);
+ }
+};
+
+
+using deterministc_random_device = mpt::prng_random_device<mpt::lcg_musl, mpt::prng_random_device_deterministic_seeder>;
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_DEVICE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine.hpp
new file mode 100644
index 00000000..1fc36e15
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine.hpp
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_ENGINE_HPP
+#define MPT_RANDOM_ENGINE_HPP
+
+
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/random/seed.hpp"
+
+#include <memory>
+#include <random>
+
+#include <cstddef>
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+template <typename Trng>
+struct engine_traits {
+ typedef typename Trng::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return Trng::result_bits();
+ }
+ template <typename Trd>
+ static inline Trng make(Trd & rd) {
+ return Trng(rd);
+ }
+};
+
+// C++11 random does not provide any sane way to determine the amount of entropy
+// required to seed a particular engine. VERY STUPID.
+// List the ones we are likely to use.
+
+template <>
+struct engine_traits<std::mt19937> {
+ enum : std::size_t {
+ seed_bits = sizeof(std::mt19937::result_type) * 8 * std::mt19937::state_size
+ };
+ typedef std::mt19937 rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return rng_type::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ std::unique_ptr<mpt::seed_seq_values<seed_bits / sizeof(unsigned int)>> values = std::make_unique<mpt::seed_seq_values<seed_bits / sizeof(unsigned int)>>(rd);
+ std::seed_seq seed(values->begin(), values->end());
+ return rng_type(seed);
+ }
+};
+
+template <>
+struct engine_traits<std::mt19937_64> {
+ enum : std::size_t {
+ seed_bits = sizeof(std::mt19937_64::result_type) * 8 * std::mt19937_64::state_size
+ };
+ typedef std::mt19937_64 rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return rng_type::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ std::unique_ptr<mpt::seed_seq_values<seed_bits / sizeof(unsigned int)>> values = std::make_unique<mpt::seed_seq_values<seed_bits / sizeof(unsigned int)>>(rd);
+ std::seed_seq seed(values->begin(), values->end());
+ return rng_type(seed);
+ }
+};
+
+template <>
+struct engine_traits<std::ranlux24_base> {
+ enum : std::size_t {
+ seed_bits = std::ranlux24_base::word_size
+ };
+ typedef std::ranlux24_base rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return rng_type::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ mpt::seed_seq_values<seed_bits / sizeof(unsigned int)> values(rd);
+ std::seed_seq seed(values.begin(), values.end());
+ return rng_type(seed);
+ }
+};
+
+template <>
+struct engine_traits<std::ranlux48_base> {
+ enum : std::size_t {
+ seed_bits = std::ranlux48_base::word_size
+ };
+ typedef std::ranlux48_base rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return rng_type::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ mpt::seed_seq_values<seed_bits / sizeof(unsigned int)> values(rd);
+ std::seed_seq seed(values.begin(), values.end());
+ return rng_type(seed);
+ }
+};
+
+template <>
+struct engine_traits<std::ranlux24> {
+ enum : std::size_t {
+ seed_bits = std::ranlux24_base::word_size
+ };
+ typedef std::ranlux24 rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return std::ranlux24_base::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ mpt::seed_seq_values<seed_bits / sizeof(unsigned int)> values(rd);
+ std::seed_seq seed(values.begin(), values.end());
+ return rng_type(seed);
+ }
+};
+
+template <>
+struct engine_traits<std::ranlux48> {
+ enum : std::size_t {
+ seed_bits = std::ranlux48_base::word_size
+ };
+ typedef std::ranlux48 rng_type;
+ typedef rng_type::result_type result_type;
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ return std::ranlux48_base::word_size;
+ }
+ template <typename Trd>
+ static inline rng_type make(Trd & rd) {
+ mpt::seed_seq_values<seed_bits / sizeof(unsigned int)> values(rd);
+ std::seed_seq seed(values.begin(), values.end());
+ return rng_type(seed);
+ }
+};
+
+
+
+template <typename Trng, typename Trd>
+inline Trng make_prng(Trd & rd) {
+ return mpt::engine_traits<Trng>::make(rd);
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_ENGINE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine_lcg.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine_lcg.hpp
new file mode 100644
index 00000000..68bc68a7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/engine_lcg.hpp
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_ENGINE_LCG_HPP
+#define MPT_RANDOM_ENGINE_LCG_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/random/random.hpp"
+
+#include <random>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4724) // potential mod by 0
+#endif // MPT_COMPILER_MSVC
+
+template <typename Tstate, typename Tvalue, Tstate m, Tstate a, Tstate c, Tstate result_mask, int result_shift, int result_bits_>
+class lcg_engine {
+public:
+ typedef Tstate state_type;
+ typedef Tvalue result_type;
+
+private:
+ state_type state;
+
+public:
+ template <typename Trng>
+ explicit inline lcg_engine(Trng & rd)
+ : state(mpt::random<state_type>(rd)) {
+ operator()(); // we return results from the current state and update state after returning. results in better pipelining.
+ }
+ explicit inline lcg_engine(state_type seed)
+ : state(seed) {
+ operator()(); // we return results from the current state and update state after returning. results in better pipelining.
+ }
+
+public:
+ static MPT_CONSTEXPRINLINE result_type min() {
+ return static_cast<result_type>(0);
+ }
+ static MPT_CONSTEXPRINLINE result_type max() {
+ static_assert(((result_mask >> result_shift) << result_shift) == result_mask);
+ return static_cast<result_type>(result_mask >> result_shift);
+ }
+ static MPT_CONSTEXPRINLINE int result_bits() {
+ static_assert(((static_cast<Tstate>(1) << result_bits_) - 1) == (result_mask >> result_shift));
+ return result_bits_;
+ }
+ inline result_type operator()() {
+ // we return results from the current state and update state after returning. results in better pipelining.
+ state_type s = state;
+ result_type result = static_cast<result_type>((s & result_mask) >> result_shift);
+ s = mpt::modulo_if_not_zero<state_type, m>((a * s) + c);
+ state = s;
+ return result;
+ }
+};
+
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif // MPT_COMPILER_MSVC
+
+typedef lcg_engine<uint32, uint16, 0u, 214013u, 2531011u, 0x7fff0000u, 16, 15> lcg_msvc;
+typedef lcg_engine<uint32, uint16, 0x80000000u, 1103515245u, 12345u, 0x7fff0000u, 16, 15> lcg_c99;
+typedef lcg_engine<uint64, uint32, 0ull, 6364136223846793005ull, 1ull, 0xffffffff00000000ull, 32, 32> lcg_musl;
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_ENGINE_LCG_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/random.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/random.hpp
new file mode 100644
index 00000000..2f4a30c4
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/random.hpp
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_RANDOM_HPP
+#define MPT_RANDOM_RANDOM_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/random/engine.hpp"
+
+#include <type_traits>
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <typename T, typename Trng>
+inline T random(Trng & rng) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ typedef typename std::make_unsigned<T>::type unsigned_T;
+ const unsigned int rng_bits = mpt::engine_traits<Trng>::result_bits();
+ unsigned_T result = 0;
+ for (std::size_t entropy = 0; entropy < (sizeof(T) * 8); entropy += rng_bits) {
+ if constexpr (rng_bits < (sizeof(T) * 8)) {
+ constexpr unsigned int shift_bits = rng_bits % (sizeof(T) * 8); // silence utterly stupid MSVC and GCC warnings about shifting by too big amount (in which case this branch is not even taken however)
+ result = (result << shift_bits) ^ static_cast<unsigned_T>(rng());
+ } else {
+ result = static_cast<unsigned_T>(rng());
+ }
+ }
+ return static_cast<T>(result);
+}
+
+template <typename T, std::size_t required_entropy_bits, typename Trng>
+inline T random(Trng & rng) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ typedef typename std::make_unsigned<T>::type unsigned_T;
+ const unsigned int rng_bits = mpt::engine_traits<Trng>::result_bits();
+ unsigned_T result = 0;
+ for (std::size_t entropy = 0; entropy < std::min(required_entropy_bits, sizeof(T) * 8); entropy += rng_bits) {
+ if constexpr (rng_bits < (sizeof(T) * 8)) {
+ constexpr unsigned int shift_bits = rng_bits % (sizeof(T) * 8); // silence utterly stupid MSVC and GCC warnings about shifting by too big amount (in which case this branch is not even taken however)
+ result = (result << shift_bits) ^ static_cast<unsigned_T>(rng());
+ } else {
+ result = static_cast<unsigned_T>(rng());
+ }
+ }
+ if constexpr (required_entropy_bits >= (sizeof(T) * 8)) {
+ return static_cast<T>(result);
+ } else {
+ return static_cast<T>(result & ((static_cast<unsigned_T>(1) << required_entropy_bits) - static_cast<unsigned_T>(1)));
+ }
+}
+
+template <typename T, typename Trng>
+inline T random(Trng & rng, std::size_t required_entropy_bits) {
+ static_assert(std::numeric_limits<T>::is_integer);
+ typedef typename std::make_unsigned<T>::type unsigned_T;
+ const unsigned int rng_bits = mpt::engine_traits<Trng>::result_bits();
+ unsigned_T result = 0;
+ for (std::size_t entropy = 0; entropy < std::min(required_entropy_bits, sizeof(T) * 8); entropy += rng_bits) {
+ if constexpr (rng_bits < (sizeof(T) * 8)) {
+ constexpr unsigned int shift_bits = rng_bits % (sizeof(T) * 8); // silence utterly stupid MSVC and GCC warnings about shifting by too big amount (in which case this branch is not even taken however)
+ result = (result << shift_bits) ^ static_cast<unsigned_T>(rng());
+ } else {
+ result = static_cast<unsigned_T>(rng());
+ }
+ }
+ if (required_entropy_bits >= (sizeof(T) * 8)) {
+ return static_cast<T>(result);
+ } else {
+ return static_cast<T>(result & ((static_cast<unsigned_T>(1) << required_entropy_bits) - static_cast<unsigned_T>(1)));
+ }
+}
+
+template <typename T>
+struct uniform_real_distribution {
+private:
+ T a;
+ T b;
+
+public:
+ inline uniform_real_distribution(T a_, T b_)
+ : a(a_)
+ , b(b_) {
+ return;
+ }
+ template <typename Trng>
+ inline T operator()(Trng & rng) const {
+ const int mantissa_bits = std::numeric_limits<T>::digits;
+ return ((b - a) * static_cast<T>(mpt::random<uint64, mantissa_bits>(rng)) / static_cast<T>((static_cast<uint64>(1u) << mantissa_bits))) + a;
+ }
+};
+
+
+template <typename T, typename Trng>
+inline T random(Trng & rng, T min, T max) {
+ static_assert(!std::numeric_limits<T>::is_integer);
+ typedef mpt::uniform_real_distribution<T> dis_type;
+ dis_type dis(min, max);
+ return static_cast<T>(dis(rng));
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_RANDOM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/seed.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/seed.hpp
new file mode 100644
index 00000000..e6ca4bd5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/seed.hpp
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_RANDOM_SEED_HPP
+#define MPT_RANDOM_SEED_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+
+#include <array>
+#include <random>
+
+#include <cstddef>
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+template <std::size_t N>
+class seed_seq_values {
+private:
+ std::array<unsigned int, N> seeds;
+
+public:
+ template <typename Trd>
+ explicit seed_seq_values(Trd & rd) {
+ std::uniform_int_distribution<unsigned int> random_int{};
+ for (std::size_t i = 0; i < N; ++i) {
+ seeds[i] = random_int(rd);
+ }
+ }
+ const unsigned int * begin() const {
+ return seeds.data();
+ }
+ const unsigned int * end() const {
+ return seeds.data() + N;
+ }
+};
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_RANDOM_SEED_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/random/tests/tests_random.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/random/tests/tests_random.hpp
new file mode 100644
index 00000000..1076b583
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/random/tests/tests_random.hpp
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_RANDOM_HPP
+#define MPT_BASE_TESTS_RANDOM_HPP
+
+
+
+#include "mpt/base/algorithm.hpp"
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/device.hpp"
+#include "mpt/random/random.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace random {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/random")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ mpt::sane_random_device rd;
+ mpt::good_engine prng = mpt::make_prng<mpt::good_engine>(rd);
+
+ bool failed = false;
+
+ for (std::size_t i = 0; i < 10000; ++i) {
+
+ failed = failed || !mpt::is_in_range(mpt::random<uint16, 7>(prng), 0u, 127u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint16, 8>(prng), 0u, 255u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint16, 9>(prng), 0u, 511u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint64, 1>(prng), 0u, 1u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint16>(prng, 7), 0u, 127u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint16>(prng, 8), 0u, 255u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint16>(prng, 9), 0u, 511u);
+ failed = failed || !mpt::is_in_range(mpt::random<uint64>(prng, 1), 0u, 1u);
+
+ failed = failed || !mpt::is_in_range(mpt::random<int16, 7>(prng), 0, 127);
+ failed = failed || !mpt::is_in_range(mpt::random<int16, 8>(prng), 0, 255);
+ failed = failed || !mpt::is_in_range(mpt::random<int16, 9>(prng), 0, 511);
+ failed = failed || !mpt::is_in_range(mpt::random<int64, 1>(prng), 0, 1);
+ failed = failed || !mpt::is_in_range(mpt::random<int16>(prng, 7), 0, 127);
+ failed = failed || !mpt::is_in_range(mpt::random<int16>(prng, 8), 0, 255);
+ failed = failed || !mpt::is_in_range(mpt::random<int16>(prng, 9), 0, 511);
+ failed = failed || !mpt::is_in_range(mpt::random<int64>(prng, 1), 0, 1);
+
+ failed = failed || !mpt::is_in_range(mpt::random<float>(prng, 0.0f, 1.0f), 0.0f, 1.0f);
+ failed = failed || !mpt::is_in_range(mpt::random<double>(prng, 0.0, 1.0), 0.0, 1.0);
+ failed = failed || !mpt::is_in_range(mpt::random<double>(prng, -1.0, 1.0), -1.0, 1.0);
+ failed = failed || !mpt::is_in_range(mpt::random<double>(prng, -1.0, 0.0), -1.0, 0.0);
+ failed = failed || !mpt::is_in_range(mpt::random<double>(prng, 1.0, 2.0), 1.0, 2.0);
+ failed = failed || !mpt::is_in_range(mpt::random<double>(prng, 1.0, 3.0), 1.0, 3.0);
+ }
+
+ MPT_TEST_EXPECT_EXPR(!failed);
+}
+
+} // namespace random
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_RANDOM_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string/buffer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string/buffer.hpp
new file mode 100644
index 00000000..1f032bae
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string/buffer.hpp
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_BUFFER_HPP
+#define MPT_STRING_BUFFER_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/detect/mfc.hpp"
+#include "mpt/string/types.hpp"
+
+#include <algorithm>
+#include <array>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+#include <cassert>
+#include <cstddef>
+
+#if MPT_DETECTED_MFC
+// cppcheck-suppress missingInclude
+#include <afx.h>
+#endif // MPT_DETECTED_MFC
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+
+template <typename Tstring, typename Tchar>
+class StringBufRefImpl {
+private:
+ Tchar * buf;
+ std::size_t size;
+
+public:
+ // cppcheck false-positive
+ // cppcheck-suppress uninitMemberVar
+ explicit StringBufRefImpl(Tchar * buf_, std::size_t size_)
+ : buf(buf_)
+ , size(size_) {
+ static_assert(sizeof(Tchar) == sizeof(typename Tstring::value_type));
+ assert(size > 0);
+ }
+ StringBufRefImpl(const StringBufRefImpl &) = delete;
+ StringBufRefImpl(StringBufRefImpl &&) = default;
+ StringBufRefImpl & operator=(const StringBufRefImpl &) = delete;
+ StringBufRefImpl & operator=(StringBufRefImpl &&) = delete;
+ operator Tstring() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return Tstring(buf, buf + len);
+ }
+ explicit operator std::basic_string_view<Tchar>() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return std::basic_string_view<Tchar>(buf, buf + len);
+ }
+ bool empty() const {
+ return buf[0] == Tchar('\0');
+ }
+ StringBufRefImpl & operator=(const Tstring & str) {
+ std::copy(str.data(), str.data() + std::min(str.length(), size - 1), buf);
+ std::fill(buf + std::min(str.length(), size - 1), buf + size, Tchar('\0'));
+ return *this;
+ }
+};
+
+template <typename Tstring, typename Tchar>
+class StringBufRefImpl<Tstring, const Tchar> {
+private:
+ const Tchar * buf;
+ std::size_t size;
+
+public:
+ // cppcheck false-positive
+ // cppcheck-suppress uninitMemberVar
+ explicit StringBufRefImpl(const Tchar * buf_, std::size_t size_)
+ : buf(buf_)
+ , size(size_) {
+ static_assert(sizeof(Tchar) == sizeof(typename Tstring::value_type));
+ assert(size > 0);
+ }
+ StringBufRefImpl(const StringBufRefImpl &) = delete;
+ StringBufRefImpl(StringBufRefImpl &&) = default;
+ StringBufRefImpl & operator=(const StringBufRefImpl &) = delete;
+ StringBufRefImpl & operator=(StringBufRefImpl &&) = delete;
+ operator Tstring() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return Tstring(buf, buf + len);
+ }
+ explicit operator std::basic_string_view<Tchar>() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return std::basic_string_view<Tchar>(buf, len);
+ }
+ bool empty() const {
+ return buf[0] == Tchar('\0');
+ }
+};
+
+
+
+template <typename Tstring, typename Tchar, std::size_t size>
+inline StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type> ReadTypedBuf(const std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type>(buf.data(), size);
+}
+template <typename Tstring, typename Tchar, std::size_t size>
+inline StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type> ReadTypedBuf(const Tchar (&buf)[size]) {
+ return StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tstring, typename Tchar>
+inline StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type> ReadTypedBuf(const Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<Tstring, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tstring, typename Tchar, std::size_t size>
+inline StringBufRefImpl<Tstring, Tchar> WriteTypedBuf(std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<Tstring, Tchar>(buf.data(), size);
+}
+template <typename Tstring, typename Tchar, std::size_t size>
+inline StringBufRefImpl<Tstring, Tchar> WriteTypedBuf(Tchar (&buf)[size]) {
+ return StringBufRefImpl<Tstring, Tchar>(buf, size);
+}
+template <typename Tstring, typename Tchar>
+inline StringBufRefImpl<Tstring, Tchar> WriteTypedBuf(Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<Tstring, Tchar>(buf, size);
+}
+
+
+
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type> ReadAutoBuf(const std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type> ReadAutoBuf(const Tchar (&buf)[size]) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type> ReadAutoBuf(const Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar> WriteAutoBuf(std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar> WriteAutoBuf(Tchar (&buf)[size]) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar>(buf, size);
+}
+template <typename Tchar>
+inline StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar> WriteAutoBuf(Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<typename std::basic_string<typename std::remove_const<Tchar>::type>, Tchar>(buf, size);
+}
+
+
+
+#if MPT_OS_WINDOWS
+
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type> ReadWinBuf(const std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type> ReadWinBuf(const Tchar (&buf)[size]) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type> ReadWinBuf(const Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar> WriteWinBuf(std::array<Tchar, size> & buf) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar> WriteWinBuf(Tchar (&buf)[size]) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar>(buf, size);
+}
+template <typename Tchar>
+inline StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar> WriteWinBuf(Tchar * buf, std::size_t size) {
+ return StringBufRefImpl<typename mpt::windows_char_traits<typename std::remove_const<Tchar>::type>::string_type, Tchar>(buf, size);
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+
+#if MPT_DETECTED_MFC
+
+template <typename Tchar>
+class CStringBufRefImpl {
+private:
+ Tchar * buf;
+ std::size_t size;
+
+public:
+ // cppcheck false-positive
+ // cppcheck-suppress uninitMemberVar
+ explicit CStringBufRefImpl(Tchar * buf_, std::size_t size_)
+ : buf(buf_)
+ , size(size_) {
+ assert(size > 0);
+ }
+ CStringBufRefImpl(const CStringBufRefImpl &) = delete;
+ CStringBufRefImpl(CStringBufRefImpl &&) = default;
+ CStringBufRefImpl & operator=(const CStringBufRefImpl &) = delete;
+ CStringBufRefImpl & operator=(CStringBufRefImpl &&) = delete;
+ operator CString() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return CString(buf, mpt::saturate_cast<int>(len));
+ }
+ CStringBufRefImpl & operator=(const CString & str) {
+ std::copy(str.GetString(), str.GetString() + std::min(static_cast<std::size_t>(str.GetLength()), size - 1), buf);
+ std::fill(buf + std::min(static_cast<std::size_t>(str.GetLength()), size - 1), buf + size, Tchar('\0'));
+ return *this;
+ }
+};
+
+template <typename Tchar>
+class CStringBufRefImpl<const Tchar> {
+private:
+ const Tchar * buf;
+ std::size_t size;
+
+public:
+ // cppcheck false-positive
+ // cppcheck-suppress uninitMemberVar
+ explicit CStringBufRefImpl(const Tchar * buf_, std::size_t size_)
+ : buf(buf_)
+ , size(size_) {
+ assert(size > 0);
+ }
+ CStringBufRefImpl(const CStringBufRefImpl &) = delete;
+ CStringBufRefImpl(CStringBufRefImpl &&) = default;
+ CStringBufRefImpl & operator=(const CStringBufRefImpl &) = delete;
+ CStringBufRefImpl & operator=(CStringBufRefImpl &&) = delete;
+ operator CString() const {
+ std::size_t len = std::find(buf, buf + size, Tchar('\0')) - buf; // terminate at \0
+ return CString(buf, mpt::saturate_cast<int>(len));
+ }
+};
+
+template <typename Tchar, std::size_t size>
+inline CStringBufRefImpl<typename std::add_const<Tchar>::type> ReadCStringBuf(const std::array<Tchar, size> & buf) {
+ return CStringBufRefImpl<typename std::add_const<Tchar>::type>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline CStringBufRefImpl<typename std::add_const<Tchar>::type> ReadCStringBuf(const Tchar (&buf)[size]) {
+ return CStringBufRefImpl<typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar>
+inline CStringBufRefImpl<typename std::add_const<Tchar>::type> ReadCStringBuf(const Tchar * buf, std::size_t size) {
+ return CStringBufRefImpl<typename std::add_const<Tchar>::type>(buf, size);
+}
+template <typename Tchar, std::size_t size>
+inline CStringBufRefImpl<Tchar> WriteCStringBuf(std::array<Tchar, size> & buf) {
+ return CStringBufRefImpl<Tchar>(buf.data(), size);
+}
+template <typename Tchar, std::size_t size>
+inline CStringBufRefImpl<Tchar> WriteCStringBuf(Tchar (&buf)[size]) {
+ return CStringBufRefImpl<Tchar>(buf, size);
+}
+template <typename Tchar>
+inline CStringBufRefImpl<Tchar> WriteCStringBuf(Tchar * buf, std::size_t size) {
+ return CStringBufRefImpl<Tchar>(buf, size);
+}
+
+#endif // MPT_DETECTED_MFC
+
+
+
+template <std::size_t len>
+struct charbuf {
+public:
+ using Tchar = char;
+ using char_type = Tchar;
+ using string_type = std::basic_string<Tchar>;
+ using string_view_type = std::basic_string_view<Tchar>;
+ constexpr std::size_t static_length() const {
+ return len;
+ }
+
+public:
+ Tchar buf[len];
+
+public:
+ charbuf() {
+ std::fill(std::begin(buf), std::end(buf), Tchar('\0'));
+ }
+ charbuf(const charbuf &) = default;
+ charbuf(charbuf &&) = default;
+ charbuf & operator=(const charbuf &) = default;
+ charbuf & operator=(charbuf &&) = default;
+ const Tchar & operator[](std::size_t i) const {
+ return buf[i];
+ }
+ std::string str() const {
+ return static_cast<std::string>(*this);
+ }
+ operator string_type() const {
+ return mpt::ReadAutoBuf(buf);
+ }
+ explicit operator string_view_type() const {
+ return static_cast<string_view_type>(mpt::ReadAutoBuf(buf));
+ }
+ bool empty() const {
+ return mpt::ReadAutoBuf(buf).empty();
+ }
+ charbuf & operator=(const string_type & str) {
+ mpt::WriteAutoBuf(buf) = str;
+ return *this;
+ }
+
+public:
+ friend bool operator!=(const charbuf & a, const charbuf & b) {
+ return static_cast<string_view_type>(a) != static_cast<string_view_type>(b);
+ }
+ friend bool operator!=(const std::string & a, const charbuf & b) {
+ return a != static_cast<string_view_type>(b);
+ }
+ friend bool operator!=(const charbuf & a, const std::string & b) {
+ return static_cast<string_view_type>(a) != b;
+ }
+ friend bool operator==(const charbuf & a, const charbuf & b) {
+ return static_cast<string_view_type>(a) == static_cast<string_view_type>(b);
+ }
+ friend bool operator==(const std::string & a, const charbuf & b) {
+ return a == static_cast<string_view_type>(b);
+ }
+ friend bool operator==(const charbuf & a, const std::string & b) {
+ return static_cast<string_view_type>(a) == b;
+ }
+};
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_BUFFER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_buffer.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_buffer.hpp
new file mode 100644
index 00000000..2219c1e2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_buffer.hpp
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TESTS_STRING_BUFFER_HPP
+#define MPT_STRING_TESTS_STRING_BUFFER_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/buffer.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <string>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace string {
+namespace buffer {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/string/buffer")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ {
+ char buf[4] = {'x', 'x', 'x', 'x'};
+ mpt::WriteAutoBuf(buf) = std::string("foobar");
+ MPT_TEST_EXPECT_EQUAL(buf[0], 'f');
+ MPT_TEST_EXPECT_EQUAL(buf[1], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[2], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[3], '\0');
+ }
+ {
+ char buf[4] = {'x', 'x', 'x', 'x'};
+ char foobar[] = {'f', 'o', 'o', 'b', 'a', 'r', '\0'};
+ mpt::WriteTypedBuf<std::string>(buf) = (char *)foobar;
+ MPT_TEST_EXPECT_EQUAL(buf[0], 'f');
+ MPT_TEST_EXPECT_EQUAL(buf[1], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[2], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[3], '\0');
+ }
+ {
+ char buf[4] = {'x', 'x', 'x', 'x'};
+ mpt::WriteTypedBuf<std::string>(buf) = (const char *)"foobar";
+ MPT_TEST_EXPECT_EQUAL(buf[0], 'f');
+ MPT_TEST_EXPECT_EQUAL(buf[1], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[2], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[3], '\0');
+ }
+ {
+ char buf[4] = {'x', 'x', 'x', 'x'};
+ mpt::WriteTypedBuf<std::string>(buf) = "foobar";
+ MPT_TEST_EXPECT_EQUAL(buf[0], 'f');
+ MPT_TEST_EXPECT_EQUAL(buf[1], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[2], 'o');
+ MPT_TEST_EXPECT_EQUAL(buf[3], '\0');
+ }
+ {
+ const char buf[4] = {'f', 'o', 'o', 'b'};
+ std::string foo = mpt::ReadAutoBuf(buf);
+ MPT_TEST_EXPECT_EQUAL(foo, std::string("foob"));
+ }
+}
+
+} // namespace buffer
+} // namespace string
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_TESTS_STRING_BUFFER_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_utility.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_utility.hpp
new file mode 100644
index 00000000..5f24f214
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string/tests/tests_string_utility.hpp
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TESTS_STRING_UTILITY_HPP
+#define MPT_STRING_TESTS_STRING_UTILITY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+#include <string>
+
+#include <cstddef>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace string {
+namespace utility {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/string/utility")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ MPT_TEST_EXPECT_EQUAL(mpt::trim_left(std::string(" ")), "");
+ MPT_TEST_EXPECT_EQUAL(mpt::trim_right(std::string(" ")), "");
+ MPT_TEST_EXPECT_EQUAL(mpt::trim(std::string(" ")), "");
+
+ // weird things with std::string containing \0 in the middle and trimming \0
+ MPT_TEST_EXPECT_EQUAL(std::string("\0\ta\0b ", 6).length(), (std::size_t)6);
+ MPT_TEST_EXPECT_EQUAL(mpt::trim_right(std::string("\0\ta\0b ", 6)), std::string("\0\ta\0b", 5));
+ MPT_TEST_EXPECT_EQUAL(mpt::trim(std::string("\0\ta\0b\0", 6), std::string("\0", 1)), std::string("\ta\0b", 4));
+}
+
+} // namespace utility
+} // namespace string
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_TESTS_STRING_UTILITY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string/types.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string/types.hpp
new file mode 100644
index 00000000..64df0c20
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string/types.hpp
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TYPES_HPP
+#define MPT_STRING_TYPES_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/detect/mfc.hpp"
+
+#include <string>
+#include <type_traits>
+
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+enum class common_encoding {
+ utf8,
+ ascii, // strictly 7-bit ASCII
+ iso8859_1,
+ iso8859_15,
+ cp850,
+ cp437,
+ windows1252,
+ amiga,
+ riscos,
+ iso8859_1_no_c1,
+ iso8859_15_no_c1,
+ amiga_no_c1,
+};
+
+
+enum class logical_encoding {
+ locale, // CP_ACP on windows, system configured C locale otherwise
+ active_locale, // active C/C++ global locale
+};
+
+// source code / preprocessor (i.e. # token)
+inline constexpr auto source_encoding = common_encoding::ascii;
+
+// debug log files
+inline constexpr auto logfile_encoding = common_encoding::utf8;
+
+// std::clog / std::cout / std::cerr
+inline constexpr auto stdio_encoding = logical_encoding::locale;
+
+// getenv
+inline constexpr auto environment_encoding = logical_encoding::locale;
+
+// std::exception::what()
+inline constexpr auto exception_encoding = logical_encoding::active_locale;
+
+
+
+
+
+template <typename T>
+struct is_character : public std::false_type { };
+
+template <>
+struct is_character<char> : public std::true_type { };
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct is_character<wchar_t> : public std::true_type { };
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct is_character<char8_t> : public std::true_type { };
+#endif // C++20
+template <>
+struct is_character<char16_t> : public std::true_type { };
+template <>
+struct is_character<char32_t> : public std::true_type { };
+
+
+
+
+
+template <typename T, typename std::enable_if<mpt::is_character<T>::value, bool>::type = true>
+MPT_CONSTEXPRINLINE typename std::make_unsigned<T>::type char_value(T x) noexcept {
+ return static_cast<typename std::make_unsigned<T>::type>(x);
+}
+
+
+
+
+
+template <typename T>
+struct unsafe_char_converter { };
+
+template <>
+struct unsafe_char_converter<char> {
+ static constexpr char32_t decode(char c) noexcept {
+ return static_cast<char32_t>(static_cast<uint32>(static_cast<unsigned char>(c)));
+ }
+ static constexpr char encode(char32_t c) noexcept {
+ return static_cast<char>(static_cast<unsigned char>(static_cast<uint32>(c)));
+ }
+};
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct unsafe_char_converter<wchar_t> {
+ static constexpr char32_t decode(wchar_t c) noexcept {
+ return static_cast<char32_t>(static_cast<uint32>(static_cast<std::make_unsigned<wchar_t>::type>(c)));
+ }
+ static constexpr wchar_t encode(char32_t c) noexcept {
+ return static_cast<wchar_t>(static_cast<std::make_unsigned<wchar_t>::type>(static_cast<uint32>(c)));
+ }
+};
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct unsafe_char_converter<char8_t> {
+ static constexpr char32_t decode(char8_t c) noexcept {
+ return static_cast<char32_t>(static_cast<uint32>(static_cast<uint8>(c)));
+ }
+ static constexpr char8_t encode(char32_t c) noexcept {
+ return static_cast<char8_t>(static_cast<uint8>(static_cast<uint32>(c)));
+ }
+};
+#endif // C++20
+
+template <>
+struct unsafe_char_converter<char16_t> {
+ static constexpr char32_t decode(char16_t c) noexcept {
+ return static_cast<char32_t>(static_cast<uint32>(static_cast<uint16>(c)));
+ }
+ static constexpr char16_t encode(char32_t c) noexcept {
+ return static_cast<char16_t>(static_cast<uint16>(static_cast<uint32>(c)));
+ }
+};
+
+template <>
+struct unsafe_char_converter<char32_t> {
+ static constexpr char32_t decode(char32_t c) noexcept {
+ return c;
+ }
+ static constexpr char32_t encode(char32_t c) noexcept {
+ return c;
+ }
+};
+
+template <typename Tdstchar, typename Tsrcchar>
+constexpr Tdstchar unsafe_char_convert(Tsrcchar src) noexcept {
+ return mpt::unsafe_char_converter<Tdstchar>::encode(mpt::unsafe_char_converter<Tsrcchar>::decode(src));
+}
+
+
+
+
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+using widestring = std::wstring;
+using widechar = wchar_t;
+#define MPT_WIDECHAR(x) L##x
+#define MPT_WIDELITERAL(x) L##x
+#define MPT_WIDESTRING(x) std::wstring(L##x)
+#else // MPT_COMPILER_QUIRK_NO_WCHAR
+using widestring = std::u32string;
+using widechar = char32_t;
+#define MPT_WIDECHAR(x) U##x
+#define MPT_WIDELITERAL(x) U##x
+#define MPT_WIDESTRING(x) std::u32string(U##x)
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+
+
+template <common_encoding common_encoding_tag>
+struct common_encoding_char_traits : std::char_traits<char> {
+ static constexpr auto encoding() noexcept {
+ return common_encoding_tag;
+ }
+};
+
+template <logical_encoding logical_encoding_tag>
+struct logical_encoding_char_traits : std::char_traits<char> {
+ static constexpr auto encoding() noexcept {
+ return logical_encoding_tag;
+ }
+};
+
+
+
+using lstring = std::basic_string<char, mpt::logical_encoding_char_traits<logical_encoding::locale>>;
+
+using source_string = std::basic_string<char, mpt::common_encoding_char_traits<source_encoding>>;
+using exception_string = std::basic_string<char, mpt::logical_encoding_char_traits<exception_encoding>>;
+
+#if MPT_OS_WINDOWS
+
+template <typename Tchar>
+struct windows_char_traits { };
+template <>
+struct windows_char_traits<CHAR> { using string_type = mpt::lstring; };
+template <>
+struct windows_char_traits<WCHAR> { using string_type = std::wstring; };
+
+using tstring = windows_char_traits<TCHAR>::string_type;
+
+using winstring = mpt::tstring;
+
+#endif // MPT_OS_WINDOWS
+
+
+
+#if MPT_CXX_AT_LEAST(20)
+
+using u8string = std::u8string;
+using u8char = char8_t;
+#define MPT_U8CHAR(x) u8##x
+#define MPT_U8LITERAL(x) u8##x
+#define MPT_U8STRING(x) std::u8string(u8##x)
+
+#else // !C++20
+
+using u8string = std::basic_string<char, mpt::common_encoding_char_traits<common_encoding::utf8>>;
+using u8char = char;
+#define MPT_U8CHAR(x) x
+#define MPT_U8LITERAL(x) x
+#define MPT_U8STRING(x) mpt::u8string(x)
+
+// mpt::u8string is a moderately type-safe string that is meant to contain
+// UTF-8 encoded char bytes.
+//
+// mpt::u8string is not implicitely convertible to/from std::string, but
+// it is convertible to/from C strings the same way as std::string is.
+//
+// The implementation of mpt::u8string is a compromise of compatibilty
+// with implementation-defined STL details, efficiency, source code size,
+// executable bloat, type-safety and simplicity.
+//
+// mpt::u8string is not meant to be used directly though.
+// mpt::u8string is meant as an alternative implementaion to std::wstring
+// for implementing the unicode string type mpt::ustring.
+
+#endif // C++20
+
+
+
+#if !defined(MPT_USTRING_MODE_UTF8_FORCE) && (MPT_COMPILER_MSVC || (MPT_DETECTED_MFC && defined(UNICODE)))
+// Use wide strings for MSVC because this is the native encoding on
+// microsoft platforms.
+#define MPT_USTRING_MODE_WIDE 1
+#define MPT_USTRING_MODE_UTF8 0
+#else
+#define MPT_USTRING_MODE_WIDE 0
+#define MPT_USTRING_MODE_UTF8 1
+#endif
+
+// mpt::ustring
+//
+// mpt::ustring is a string type that can hold unicode strings.
+// It is implemented as a std::basic_string either based on wchar_t (i.e. the
+// same as std::wstring) or a custom-defined char_traits class that is derived
+// from std::char_traits<char>.
+// The selection of the underlying implementation is done at compile-time.
+// MPT_UCHAR, MPT_ULITERAL and MPT_USTRING are macros that ease construction
+// of ustring char literals, ustring char array literals and ustring objects
+// from ustring char literals that work consistently in both modes.
+// Note that these are not supported for non-ASCII characters appearing in
+// the macro argument.
+// Also note that, as both UTF8 and UTF16 (it is less of an issue for UTF32)
+// are variable-length encodings and mpt::ustring is implemented as a
+// std::basic_string, all member functions that require individual character
+// access will not work consistently or even at all in a meaningful way.
+// This in particular affects operator[], find() and substr().
+// The code makes no effort in preventing these or generating warnings when
+// these are used on mpt::ustring objects. However, compiling in the
+// respectively other mpt::ustring mode will catch most of these anyway.
+
+#if MPT_USTRING_MODE_WIDE
+#if MPT_USTRING_MODE_UTF8
+#error "MPT_USTRING_MODE_WIDE and MPT_USTRING_MODE_UTF8 are mutually exclusive."
+#endif
+
+using ustring = std::wstring;
+using uchar = wchar_t;
+#define MPT_UCHAR(x) L##x
+#define MPT_ULITERAL(x) L##x
+#define MPT_USTRING(x) std::wstring(L##x)
+
+#endif // MPT_USTRING_MODE_WIDE
+
+#if MPT_USTRING_MODE_UTF8
+#if MPT_USTRING_MODE_WIDE
+#error "MPT_USTRING_MODE_WIDE and MPT_USTRING_MODE_UTF8 are mutually exclusive."
+#endif
+
+using ustring = mpt::u8string;
+using uchar = mpt::u8char;
+#define MPT_UCHAR(x) MPT_U8CHAR(x)
+#define MPT_ULITERAL(x) MPT_U8LITERAL(x)
+#define MPT_USTRING(x) MPT_U8STRING(x)
+
+#endif // MPT_USTRING_MODE_UTF8
+
+
+
+template <typename T>
+struct make_string_type { };
+
+template <typename T, typename Ttraits>
+struct make_string_type<std::basic_string<T, Ttraits>> {
+ using type = std::basic_string<T, Ttraits>;
+};
+
+template <typename T>
+struct make_string_type<const T *> {
+ using type = std::basic_string<T>;
+};
+
+template <typename T>
+struct make_string_type<T *> {
+ using type = std::basic_string<T>;
+};
+
+template <typename T, std::size_t N>
+struct make_string_type<T[N]> {
+ using type = typename make_string_type<T *>::type;
+};
+
+#if MPT_DETECTED_MFC
+
+template <>
+struct make_string_type<CStringW> {
+ using type = CStringW;
+};
+
+template <>
+struct make_string_type<CStringA> {
+ using type = CStringA;
+};
+
+#endif // MPT_DETECTED_MFC
+
+
+
+template <typename T>
+struct is_string_type : public std::false_type { };
+template <>
+struct is_string_type<std::string> : public std::true_type { };
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct is_string_type<std::wstring> : public std::true_type { };
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct is_string_type<std::u8string> : public std::true_type { };
+#endif // C++20
+template <>
+struct is_string_type<std::u16string> : public std::true_type { };
+template <>
+struct is_string_type<std::u32string> : public std::true_type { };
+#if MPT_DETECTED_MFC
+template <>
+struct is_string_type<CStringW> : public std::true_type { };
+template <>
+struct is_string_type<CStringA> : public std::true_type { };
+#endif // MPT_DETECTED_MFC
+template <typename T, typename Ttraits>
+struct is_string_type<std::basic_string<T, Ttraits>> : public std::true_type { };
+
+
+
+template <typename T>
+inline typename mpt::make_string_type<T>::type as_string(const T & str) {
+ if constexpr (std::is_pointer<typename std::remove_cv<T>::type>::value) {
+ return str ? typename mpt::make_string_type<T>::type{str} : typename mpt::make_string_type<T>::type{};
+ } else {
+ return str;
+ }
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_TYPES_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string/utility.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string/utility.hpp
new file mode 100644
index 00000000..e2238a09
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string/utility.hpp
@@ -0,0 +1,359 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_UTILITY_HPP
+#define MPT_STRING_UTILITY_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/detect/mfc.hpp"
+
+#include <string>
+#include <vector>
+
+#include <cstddef>
+
+#if MPT_DETECTED_MFC
+// cppcheck-suppress missingInclude
+#include <afx.h>
+#endif // MPT_DETECTED_MFC
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+// string_traits abstract the API of underlying string classes, in particular they allow adopting to CString without having to specialize for CString explicitly
+
+template <typename Tstring>
+struct string_traits {
+
+ using string_type = Tstring;
+ using size_type = typename string_type::size_type;
+ using char_type = typename string_type::value_type;
+
+ static inline std::size_t length(const string_type & str) {
+ return str.length();
+ }
+
+ static inline void reserve(string_type & str, std::size_t size) {
+ str.reserve(size);
+ }
+
+ static inline string_type & append(string_type & str, const string_type & a) {
+ return str.append(a);
+ }
+ static inline string_type & append(string_type & str, string_type && a) {
+ return str.append(std::move(a));
+ }
+ static inline string_type & append(string_type & str, std::size_t count, char_type c) {
+ return str.append(count, c);
+ }
+
+ static inline string_type pad(string_type str, std::size_t left, std::size_t right) {
+ str.insert(str.begin(), left, char_type(' '));
+ str.insert(str.end(), right, char_type(' '));
+ return str;
+ }
+};
+
+#if MPT_DETECTED_MFC
+
+template <>
+struct string_traits<CStringA> {
+
+ using string_type = CStringA;
+ using size_type = int;
+ using char_type = typename CStringA::XCHAR;
+
+ static inline size_type length(const string_type & str) {
+ return str.GetLength();
+ }
+
+ static inline void reserve(string_type & str, size_type size) {
+ str.Preallocate(size);
+ }
+
+ static inline string_type & append(string_type & str, const string_type & a) {
+ str += a;
+ return str;
+ }
+ static inline string_type & append(string_type & str, size_type count, char_type c) {
+ while (count--) {
+ str.AppendChar(c);
+ }
+ return str;
+ }
+
+ static inline string_type pad(const string_type & str, size_type left, size_type right) {
+ string_type tmp;
+ while (left--) {
+ tmp.AppendChar(char_type(' '));
+ }
+ tmp += str;
+ while (right--) {
+ tmp.AppendChar(char_type(' '));
+ }
+ return tmp;
+ }
+};
+
+template <>
+struct string_traits<CStringW> {
+
+ using string_type = CStringW;
+ using size_type = int;
+ using char_type = typename CStringW::XCHAR;
+
+ static inline size_type length(const string_type & str) {
+ return str.GetLength();
+ }
+
+ static inline void reserve(string_type & str, size_type size) {
+ str.Preallocate(size);
+ }
+
+ static inline string_type & append(string_type & str, const string_type & a) {
+ str += a;
+ return str;
+ }
+ static inline string_type & append(string_type & str, size_type count, char_type c) {
+ while (count--) {
+ str.AppendChar(c);
+ }
+ return str;
+ }
+
+ static inline string_type pad(const string_type & str, size_type left, size_type right) {
+ string_type tmp;
+ while (left--) {
+ tmp.AppendChar(char_type(' '));
+ }
+ tmp += str;
+ while (right--) {
+ tmp.AppendChar(char_type(' '));
+ }
+ return tmp;
+ }
+};
+
+#endif // MPT_DETECTED_MFC
+
+
+template <typename Tchar>
+struct char_constants {
+ static inline constexpr Tchar space = ' ';
+ static inline constexpr Tchar a = 'a';
+ static inline constexpr Tchar z = 'z';
+ static inline constexpr Tchar A = 'A';
+ static inline constexpr Tchar Z = 'Z';
+ static inline constexpr Tchar lf = '\n';
+ static inline constexpr Tchar cr = '\r';
+ static inline constexpr Tchar tab = '\t';
+ static inline constexpr Tchar comma = ',';
+};
+
+template <>
+struct char_constants<char> {
+ static inline constexpr char space = ' ';
+ static inline constexpr char a = 'a';
+ static inline constexpr char z = 'z';
+ static inline constexpr char A = 'A';
+ static inline constexpr char Z = 'Z';
+ static inline constexpr char lf = '\n';
+ static inline constexpr char cr = '\r';
+ static inline constexpr char tab = '\t';
+ static inline constexpr char comma = ',';
+};
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct char_constants<wchar_t> {
+ static inline constexpr wchar_t space = L' ';
+ static inline constexpr wchar_t a = L'a';
+ static inline constexpr wchar_t z = L'z';
+ static inline constexpr wchar_t A = L'A';
+ static inline constexpr wchar_t Z = L'Z';
+ static inline constexpr wchar_t lf = L'\n';
+ static inline constexpr wchar_t cr = L'\r';
+ static inline constexpr wchar_t tab = L'\t';
+ static inline constexpr wchar_t comma = L',';
+};
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct char_constants<char8_t> {
+ static inline constexpr char8_t space = u8' ';
+ static inline constexpr char8_t a = u8'a';
+ static inline constexpr char8_t z = u8'z';
+ static inline constexpr char8_t A = u8'A';
+ static inline constexpr char8_t Z = u8'Z';
+ static inline constexpr char8_t lf = u8'\n';
+ static inline constexpr char8_t cr = u8'\r';
+ static inline constexpr char8_t tab = u8'\t';
+ static inline constexpr char8_t comma = u8',';
+};
+#endif
+
+template <>
+struct char_constants<char16_t> {
+ static inline constexpr char16_t space = u' ';
+ static inline constexpr char16_t a = u'a';
+ static inline constexpr char16_t z = u'z';
+ static inline constexpr char16_t A = u'A';
+ static inline constexpr char16_t Z = u'Z';
+ static inline constexpr char16_t lf = u'\n';
+ static inline constexpr char16_t cr = u'\r';
+ static inline constexpr char16_t tab = u'\t';
+ static inline constexpr char16_t comma = u',';
+};
+
+template <>
+struct char_constants<char32_t> {
+ static inline constexpr char32_t space = U' ';
+ static inline constexpr char32_t a = U'a';
+ static inline constexpr char32_t z = U'z';
+ static inline constexpr char32_t A = U'A';
+ static inline constexpr char32_t Z = U'Z';
+ static inline constexpr char32_t lf = U'\n';
+ static inline constexpr char32_t cr = U'\r';
+ static inline constexpr char32_t tab = U'\t';
+ static inline constexpr char32_t comma = U',';
+};
+
+
+template <typename Tchar>
+constexpr bool is_any_line_ending(Tchar c) noexcept {
+ return (c == char_constants<Tchar>::cr) || (c == char_constants<Tchar>::lf);
+}
+
+
+template <typename Tstring>
+inline Tstring default_whitespace() {
+ Tstring result;
+ result.reserve(4);
+ result.push_back(char_constants<typename Tstring::value_type>::space);
+ result.push_back(char_constants<typename Tstring::value_type>::lf);
+ result.push_back(char_constants<typename Tstring::value_type>::cr);
+ result.push_back(char_constants<typename Tstring::value_type>::tab);
+ return result;
+}
+
+
+// Remove whitespace at start of string
+template <typename Tstring>
+inline Tstring trim_left(Tstring str, const Tstring & whitespace = default_whitespace<Tstring>()) {
+ typename Tstring::size_type pos = str.find_first_not_of(whitespace);
+ if (pos != Tstring::npos) {
+ str.erase(str.begin(), str.begin() + pos);
+ } else if (pos == Tstring::npos && str.length() > 0 && str.find_last_of(whitespace) == str.length() - 1) {
+ return Tstring();
+ }
+ return str;
+}
+
+// Remove whitespace at end of string
+template <typename Tstring>
+inline Tstring trim_right(Tstring str, const Tstring & whitespace = default_whitespace<Tstring>()) {
+ typename Tstring::size_type pos = str.find_last_not_of(whitespace);
+ if (pos != Tstring::npos) {
+ str.erase(str.begin() + pos + 1, str.end());
+ } else if (pos == Tstring::npos && str.length() > 0 && str.find_first_of(whitespace) == 0) {
+ return Tstring();
+ }
+ return str;
+}
+
+// Remove whitespace at start and end of string
+template <typename Tstring>
+inline Tstring trim(Tstring str, const Tstring & whitespace = default_whitespace<Tstring>()) {
+ return trim_right(trim_left(str, whitespace), whitespace);
+}
+
+
+template <typename Tstring, typename Tmatch>
+inline bool starts_with(const Tstring & str, const Tmatch & match) {
+ return (str.find(typename mpt::make_string_type<Tmatch>::type{match}) == 0);
+}
+
+template <typename Tstring, typename Tmatch>
+inline bool ends_with(const Tstring & str, const Tmatch & match) {
+ return (str.rfind(typename mpt::make_string_type<Tmatch>::type{match}) == (str.length() - typename mpt::make_string_type<Tmatch>::type{match}.length()));
+}
+
+
+template <typename Tstring, typename Treplace>
+inline Tstring replace(Tstring str, const Treplace & old_str, const Treplace & new_str) {
+ std::size_t pos = 0;
+ while ((pos = str.find(typename mpt::make_string_type<Treplace>::type{old_str}, pos)) != Tstring::npos) {
+ str.replace(pos, typename mpt::make_string_type<Treplace>::type{old_str}.length(), typename mpt::make_string_type<Treplace>::type{new_str});
+ pos += typename mpt::make_string_type<Treplace>::type{new_str}.length();
+ }
+ return str;
+}
+
+
+template <typename Tstring>
+inline Tstring truncate(Tstring str, std::size_t max_len) {
+ if (str.length() > max_len) {
+ str.resize(max_len);
+ }
+ return str;
+}
+
+
+template <typename Tchar>
+inline constexpr Tchar to_lower_ascii(Tchar c) noexcept {
+ if (char_constants<Tchar>::A <= c && c <= char_constants<Tchar>::Z) {
+ c += char_constants<Tchar>::a - char_constants<Tchar>::A;
+ }
+ return c;
+}
+
+template <typename Tchar>
+inline constexpr Tchar to_upper_ascii(Tchar c) noexcept {
+ if (char_constants<Tchar>::a <= c && c <= char_constants<Tchar>::z) {
+ c -= char_constants<Tchar>::a - char_constants<Tchar>::A;
+ }
+ return c;
+}
+
+
+template <typename Tstring>
+inline std::vector<Tstring> split(const Tstring & str, const Tstring & sep = Tstring(1, char_constants<typename Tstring::value_type>::comma)) {
+ std::vector<Tstring> vals;
+ std::size_t pos = 0;
+ while (str.find(sep, pos) != std::string::npos) {
+ vals.push_back(str.substr(pos, str.find(sep, pos) - pos));
+ pos = str.find(sep, pos) + sep.length();
+ }
+ if (!vals.empty() || (str.substr(pos).length() > 0)) {
+ vals.push_back(str.substr(pos));
+ }
+ return vals;
+}
+
+
+template <typename Tstring>
+inline Tstring join(const std::vector<Tstring> & vals, const Tstring & sep = Tstring(1, char_constants<typename Tstring::value_type>::comma)) {
+ Tstring str;
+ for (std::size_t i = 0; i < vals.size(); ++i) {
+ if (i > 0) {
+ str += sep;
+ }
+ str += vals[i];
+ }
+ return str;
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_UTILITY_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/macros.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/macros.hpp
new file mode 100644
index 00000000..60630c2f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/macros.hpp
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TRANSCODE_MACROS_HPP
+#define MPT_STRING_TRANSCODE_MACROS_HPP
+
+
+
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+
+#include <string>
+
+
+
+// The MPT_UTF8_STRING allows specifying UTF8 char arrays.
+// The resulting type is mpt::ustring and the construction might require runtime translation,
+// i.e. it is NOT generally available at compile time.
+// Use explicit UTF8 encoding,
+// i.e. U+00FC (LATIN SMALL LETTER U WITH DIAERESIS) would be written as "\xC3\xBC".
+#define MPT_UTF8_STRING(x) mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, std::string{x})
+
+
+
+#endif // MPT_STRING_TRANSCODE_MACROS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/tests/tests_string_transcode.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/tests/tests_string_transcode.hpp
new file mode 100644
index 00000000..311f833b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/tests/tests_string_transcode.hpp
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TRANSCODE_TESTS_STRING_TRANSCODE_HPP
+#define MPT_STRING_TRANSCODE_TESTS_STRING_TRANSCODE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string_transcode/macros.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace string_transcode {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/string_transcode")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ // MPT_UTF8_STRING version
+
+ // Charset conversions (basic sanity checks)
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_USTRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_USTRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_USTRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "a"), MPT_USTRING("a"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "a"), MPT_USTRING("a"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "a"), MPT_USTRING("a"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_USTRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "a"), MPT_USTRING("a"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_UTF8_STRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "a"), MPT_UTF8_STRING("a"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "a"), MPT_UTF8_STRING("a"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "a"), MPT_UTF8_STRING("a"));
+
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("a")), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "a"), MPT_UTF8_STRING("a"));
+
+#if MPT_OS_EMSCRIPTEN
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("\xe2\x8c\x82")), "\xe2\x8c\x82");
+#endif // MPT_OS_EMSCRIPTEN
+
+ // Check that some character replacement is done (and not just empty strings or truncated strings are returned)
+ // We test german umlaut-a (U+00E4) (\xC3\xA4) and CJK U+5BB6 (\xE5\xAE\xB6)
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::cp437, "abc\xC3\xA4xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::cp437, "abc\xC3\xA4xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"), MPT_USTRING("abc")));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), MPT_USTRING("abc")));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::cp437, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::cp437, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc")));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz")));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc")));
+
+ // Check that characters are correctly converted
+ // We test german umlaut-a (U+00E4) and CJK U+5BB6
+
+ // cp437
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::cp437, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc\x84xyz");
+ MPT_TEST_EXPECT_EQUAL(MPT_UTF8_STRING("abc\xC3\xA4xyz"), mpt::transcode<mpt::ustring>(mpt::common_encoding::cp437, "abc\x84xyz"));
+
+ // iso8859
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc\xE4xyz");
+ MPT_TEST_EXPECT_EQUAL(MPT_UTF8_STRING("abc\xC3\xA4xyz"), mpt::transcode<mpt::ustring>(mpt::common_encoding::iso8859_1, "abc\xE4xyz"));
+
+ // utf8
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc\xC3\xA4xyz");
+ MPT_TEST_EXPECT_EQUAL(MPT_UTF8_STRING("abc\xC3\xA4xyz"), mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc\xE5\xAE\xB6xyz");
+ MPT_TEST_EXPECT_EQUAL(MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz"), mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"));
+
+ // utf16
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, std::u16string(1, char16_t{0xe4})), "\xC3\xA4");
+ MPT_TEST_EXPECT_EQUAL(std::u16string(1, char16_t{0xe4}), mpt::transcode<std::u16string>(mpt::common_encoding::utf8, "\xC3\xA4"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, std::u16string(1, char16_t{0x5BB6})), "\xE5\xAE\xB6");
+ MPT_TEST_EXPECT_EQUAL(std::u16string(1, char16_t{0x5BB6}), mpt::transcode<std::u16string>(mpt::common_encoding::utf8, "\xE5\xAE\xB6"));
+
+ // utf32
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, std::u32string(1, char32_t{0xe4})), "\xC3\xA4");
+ MPT_TEST_EXPECT_EQUAL(std::u32string(1, char32_t{0xe4}), mpt::transcode<std::u32string>(mpt::common_encoding::utf8, "\xC3\xA4"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, std::u32string(1, char32_t{0x5BB6})), "\xE5\xAE\xB6");
+ MPT_TEST_EXPECT_EQUAL(std::u32string(1, char32_t{0x5BB6}), mpt::transcode<std::u32string>(mpt::common_encoding::utf8, "\xE5\xAE\xB6"));
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+
+ // wide L"" version
+
+ // Charset conversions (basic sanity checks)
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"a"), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"a"), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::ascii, L"a"), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "a"), L"a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "a"), L"a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::wstring>(mpt::common_encoding::ascii, "a"), L"a");
+
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::logical_encoding::locale, L"a"), "a");
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::wstring>(mpt::logical_encoding::locale, "a"), L"a");
+
+ // Check that some character replacement is done (and not just empty strings or truncated strings are returned)
+ // We test german umlaut-a (U+00E4) and CJK U+5BB6
+
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4428) // universal-character-name encountered in source
+#endif
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, L"abc\u00E4xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"abc\u00E4xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, L"abc\u00E4xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u00E4xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, L"abc\u00E4xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"abc\u00E4xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, L"abc\u00E4xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u00E4xyz"), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, L"abc\u00E4xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, L"abc\u00E4xyz"), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, L"abc\u5BB6xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"abc\u5BB6xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, L"abc\u5BB6xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u5BB6xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::ascii, L"abc\u5BB6xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"abc\u5BB6xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::cp437, L"abc\u5BB6xyz"), "abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u5BB6xyz"), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, L"abc\u5BB6xyz"), "xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::string>(mpt::logical_encoding::locale, L"abc\u5BB6xyz"), "abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::cp437, "abc\xC3\xA4xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::cp437, "abc\xC3\xA4xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"), L"abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), L"abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::cp437, "abc\xE5\xAE\xB6xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::cp437, "abc\xE5\xAE\xB6xyz"), L"abc"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"), L"abc"));
+
+ MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode<std::wstring>(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), L"xyz"));
+ MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode<std::wstring>(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), L"abc"));
+
+ // Check that characters are correctly converted
+ // We test german umlaut-a (U+00E4) and CJK U+5BB6
+
+ // cp437
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::cp437, L"abc\u00E4xyz"), "abc\x84xyz");
+ MPT_TEST_EXPECT_EQUAL(L"abc\u00E4xyz", mpt::transcode<std::wstring>(mpt::common_encoding::cp437, "abc\x84xyz"));
+
+ // iso8859
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::iso8859_1, L"abc\u00E4xyz"), "abc\xE4xyz");
+ MPT_TEST_EXPECT_EQUAL(L"abc\u00E4xyz", mpt::transcode<std::wstring>(mpt::common_encoding::iso8859_1, "abc\xE4xyz"));
+
+ // utf8
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u00E4xyz"), "abc\xC3\xA4xyz");
+ MPT_TEST_EXPECT_EQUAL(L"abc\u00E4xyz", mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xC3\xA4xyz"));
+ MPT_TEST_EXPECT_EQUAL(mpt::transcode<std::string>(mpt::common_encoding::utf8, L"abc\u5BB6xyz"), "abc\xE5\xAE\xB6xyz");
+ MPT_TEST_EXPECT_EQUAL(L"abc\u5BB6xyz", mpt::transcode<std::wstring>(mpt::common_encoding::utf8, "abc\xE5\xAE\xB6xyz"));
+
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif
+
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+}
+
+} // namespace string_transcode
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_TRANSCODE_TESTS_STRING_TRANSCODE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/transcode.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/transcode.hpp
new file mode 100644
index 00000000..cdb53b61
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/string_transcode/transcode.hpp
@@ -0,0 +1,1282 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_STRING_TRANSCODE_TRANSCODE_HPP
+#define MPT_STRING_TRANSCODE_TRANSCODE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/detect/mfc.hpp"
+#include "mpt/string/types.hpp"
+
+#include <array>
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+#include <locale>
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+#include <stdexcept>
+#include <string>
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+#include <type_traits>
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+#include <vector>
+
+#if MPT_OS_DJGPP
+#include <cstring>
+#endif // MPT_OS_DJGPP
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if MPT_OS_DJGPP
+#include <dpmi.h>
+#endif // MPT_OS_DJGPP
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+
+// default 1:1 mapping
+inline constexpr char32_t CharsetTableISO8859_1[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};
+
+inline constexpr char32_t CharsetTableISO8859_15[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20ac, 0x00a5, 0x0160, 0x00a7, 0x0161, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x017d, 0x00b5, 0x00b6, 0x00b7, 0x017e, 0x00b9, 0x00ba, 0x00bb, 0x0152, 0x0153, 0x0178, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};
+
+inline constexpr char32_t CharsetTableWindows1252[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
+ 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};
+
+inline constexpr char32_t CharsetTableCP850[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00F8, 0x00a3, 0x00D8, 0x00D7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00AE, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00A2, 0x00A5, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00E3, 0x00C3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00A4,
+ 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250c, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+ 0x00D3, 0x00df, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00b5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4,
+ 0x00AD, 0x00b1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00f7, 0x00B8, 0x00b0, 0x00A8, 0x00b7, 0x00B9, 0x00B3, 0x00b2, 0x25a0, 0x00a0};
+
+inline constexpr char32_t CharsetTableCP437[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};
+
+// <https://de.wikipedia.org/wiki/Commodore_Amiga_(Zeichensatz)>
+inline constexpr char32_t CharsetTableAmiga[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2592,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x2013, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};
+
+// Based on RISCOSI.TXT from <https://www.unicode.org/L2/L2019/19025-terminals-prop.pdf>,
+// with gaps filled in from standard set at <https://en.wikipedia.org/wiki/RISC_OS_character_set>.
+inline constexpr char32_t CharsetTableRISC_OS[256] = {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x20AC, 0x0174, 0x0175, 0x25f0, 0x1fbc0, 0x0176, 0x0177, 0xfffd, 0x21e6, 0x21e8, 0x21e9, 0x21e7, 0x2026, 0x2122, 0x2030, 0x2022,
+ 0x2018, 0x2019, 0x2039, 0x203A, 0x201C, 0x201D, 0x201E, 0x2013, 0x2014, 0x2212, 0x0152, 0x0153, 0x2020, 0x2021, 0xFB01, 0xFB02,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff};
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_8bit(const Tsrcstring & str, const char32_t (&table)[256], mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ mpt::widestring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ std::size_t c = static_cast<std::size_t>(mpt::char_value(str[i]));
+ if (c < std::size(table)) {
+ res.push_back(static_cast<mpt::widechar>(table[c]));
+ } else {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_8bit(const mpt::widestring & str, const char32_t (&table)[256], char replacement = '?') {
+ Tdststring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ char32_t c = static_cast<char32_t>(str[i]);
+ bool found = false;
+ // Try non-control characters first.
+ // In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets),
+ // characters in the common range are preferred this way.
+ for (std::size_t x = 0x20; x < std::size(table); ++x) {
+ if (c == table[x]) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // try control characters
+ for (std::size_t x = 0x00; x < std::size(table) && x < 0x20; ++x) {
+ if (c == table[x]) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_8bit_no_c1(const Tsrcstring & str, const char32_t (&table)[256], mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ mpt::widestring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ std::size_t c = static_cast<std::size_t>(mpt::char_value(str[i]));
+ if ((0x80 <= c) && (c <= 0x9f)) {
+ res.push_back(replacement);
+ } else if (c < std::size(table)) {
+ res.push_back(static_cast<mpt::widechar>(table[c]));
+ } else {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_8bit_no_c1(const mpt::widestring & str, const char32_t (&table)[256], char replacement = '?') {
+ Tdststring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ char32_t c = static_cast<char32_t>(str[i]);
+ bool found = false;
+ // Try non-control characters first.
+ // In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets),
+ // characters in the common range are preferred this way.
+ for (std::size_t x = 0x20; x < std::size(table); ++x) {
+ if ((0x80 <= c) && (c <= 0x9f)) {
+ continue;
+ }
+ if (c == table[x]) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // try control characters
+ for (std::size_t x = 0x00; x < std::size(table) && x < 0x20; ++x) {
+ if (c == table[x]) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(x)));
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_ascii(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ mpt::widestring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ uint8 c = mpt::char_value(str[i]);
+ if (c <= 0x7f) {
+ res.push_back(static_cast<mpt::widechar>(static_cast<uint32>(c)));
+ } else {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_ascii(const mpt::widestring & str, char replacement = '?') {
+ Tdststring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ char32_t c = static_cast<char32_t>(str[i]);
+ if (c <= 0x7f) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(c)));
+ } else {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_iso8859_1(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ MPT_UNUSED(replacement);
+ mpt::widestring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ uint8 c = mpt::char_value(str[i]);
+ res.push_back(static_cast<mpt::widechar>(static_cast<uint32>(c)));
+ }
+ return res;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_iso8859_1(const mpt::widestring & str, char replacement = '?') {
+ Tdststring res;
+ res.reserve(str.length());
+ for (std::size_t i = 0; i < str.length(); ++i) {
+ char32_t c = static_cast<char32_t>(str[i]);
+ if (c <= 0xff) {
+ res.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint8>(c)));
+ } else {
+ res.push_back(replacement);
+ }
+ }
+ return res;
+}
+
+
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_utf8(const Tsrcstring & str, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ const Tsrcstring & in = str;
+ mpt::widestring out;
+ // state:
+ std::size_t charsleft = 0;
+ char32_t ucs4 = 0;
+ for (uint8 c : in) {
+ if (charsleft == 0) {
+ if ((c & 0x80) == 0x00) {
+ out.push_back(static_cast<mpt::widechar>(c));
+ } else if ((c & 0xE0) == 0xC0) {
+ ucs4 = c & 0x1F;
+ charsleft = 1;
+ } else if ((c & 0xF0) == 0xE0) {
+ ucs4 = c & 0x0F;
+ charsleft = 2;
+ } else if ((c & 0xF8) == 0xF0) {
+ ucs4 = c & 0x07;
+ charsleft = 3;
+ } else {
+ out.push_back(replacement);
+ ucs4 = 0;
+ charsleft = 0;
+ }
+ } else {
+ if ((c & 0xC0) != 0x80) {
+ out.push_back(replacement);
+ ucs4 = 0;
+ charsleft = 0;
+ }
+ ucs4 <<= 6;
+ ucs4 |= c & 0x3F;
+ charsleft--;
+ if (charsleft == 0) {
+ if constexpr (sizeof(mpt::widechar) == 2) {
+ if (ucs4 > 0x1fffff) {
+ out.push_back(replacement);
+ ucs4 = 0;
+ charsleft = 0;
+ }
+ if (ucs4 <= 0xffff) {
+ out.push_back(static_cast<mpt::widechar>(ucs4));
+ } else {
+ uint32 surrogate = static_cast<uint32>(ucs4) - 0x10000;
+ uint16 hi_sur = static_cast<uint16>((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1)));
+ uint16 lo_sur = static_cast<uint16>((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1)));
+ out.push_back(hi_sur);
+ out.push_back(lo_sur);
+ }
+ } else {
+ out.push_back(static_cast<mpt::widechar>(ucs4));
+ }
+ ucs4 = 0;
+ }
+ }
+ }
+ if (charsleft != 0) {
+ out.push_back(replacement);
+ ucs4 = 0;
+ charsleft = 0;
+ }
+ return out;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_utf8(const mpt::widestring & str, typename Tdststring::value_type replacement = static_cast<typename Tdststring::value_type>(mpt::char_value('?'))) {
+ const mpt::widestring & in = str;
+ Tdststring out;
+ for (std::size_t i = 0; i < in.length(); i++) {
+ mpt::widechar wc = in[i];
+ char32_t ucs4 = 0;
+ if constexpr (sizeof(mpt::widechar) == 2) {
+ uint16 c = static_cast<uint16>(wc);
+ if (i + 1 < in.length()) {
+ // check for surrogate pair
+ uint16 hi_sur = in[i + 0];
+ uint16 lo_sur = in[i + 1];
+ if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) {
+ // surrogate pair
+ ++i;
+ hi_sur &= (1 << 10) - 1;
+ lo_sur &= (1 << 10) - 1;
+ ucs4 = (static_cast<uint32>(hi_sur) << 10) | (static_cast<uint32>(lo_sur) << 0);
+ } else {
+ // no surrogate pair
+ ucs4 = static_cast<char32_t>(c);
+ }
+ } else {
+ // no surrogate possible
+ ucs4 = static_cast<char32_t>(c);
+ }
+ } else {
+ ucs4 = static_cast<char32_t>(wc);
+ }
+ if (ucs4 > 0x1fffff) {
+ out.push_back(replacement);
+ continue;
+ }
+ uint8 utf8[6];
+ std::size_t numchars = 0;
+ for (numchars = 0; numchars < 6; numchars++) {
+ utf8[numchars] = ucs4 & 0x3F;
+ ucs4 >>= 6;
+ if (ucs4 == 0) {
+ break;
+ }
+ }
+ numchars++;
+ if (numchars == 1) {
+ out.push_back(utf8[0]);
+ continue;
+ }
+ if (numchars == 2 && utf8[numchars - 1] == 0x01) {
+ // generate shortest form
+ out.push_back(utf8[0] | 0x40);
+ continue;
+ }
+ std::size_t charsleft = numchars;
+ while (charsleft > 0) {
+ if (charsleft == numchars) {
+ out.push_back(utf8[charsleft - 1] | (((1 << numchars) - 1) << (8 - numchars)));
+ } else {
+ // cppcheck false-positive
+ // cppcheck-suppress arrayIndexOutOfBounds
+ out.push_back(utf8[charsleft - 1] | 0x80);
+ }
+ charsleft--;
+ }
+ }
+ return out;
+}
+
+
+
+template <typename Tdststring, typename Tsrcstring>
+inline Tdststring utf32_from_utf16(const Tsrcstring & in, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == 2);
+ static_assert(sizeof(typename Tdststring::value_type) == 4);
+ MPT_UNUSED(replacement);
+ Tdststring out;
+ out.reserve(in.length());
+ for (std::size_t i = 0; i < in.length(); i++) {
+ char16_t wc = static_cast<char16_t>(static_cast<uint16>(in[i]));
+ char32_t ucs4 = 0;
+ uint16 c = static_cast<uint16>(wc);
+ if (i + 1 < in.length()) {
+ // check for surrogate pair
+ uint16 hi_sur = in[i + 0];
+ uint16 lo_sur = in[i + 1];
+ if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) {
+ // surrogate pair
+ ++i;
+ hi_sur &= (1 << 10) - 1;
+ lo_sur &= (1 << 10) - 1;
+ ucs4 = (static_cast<uint32>(hi_sur) << 10) | (static_cast<uint32>(lo_sur) << 0);
+ } else {
+ // no surrogate pair
+ ucs4 = static_cast<char32_t>(c);
+ }
+ } else {
+ // no surrogate possible
+ ucs4 = static_cast<char32_t>(c);
+ }
+ out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint32>(ucs4)));
+ }
+ return out;
+}
+
+template <typename Tdststring, typename Tsrcstring>
+inline Tdststring utf16_from_utf32(const Tsrcstring & in, mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == 4);
+ static_assert(sizeof(typename Tdststring::value_type) == 2);
+ Tdststring out;
+ out.reserve(in.length());
+ for (std::size_t i = 0; i < in.length(); i++) {
+ char32_t ucs4 = static_cast<char32_t>(static_cast<uint32>(in[i]));
+ if (ucs4 > 0x1fffff) {
+ out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint16>(replacement)));
+ ucs4 = 0;
+ }
+ if (ucs4 <= 0xffff) {
+ out.push_back(static_cast<typename Tdststring::value_type>(static_cast<uint16>(ucs4)));
+ } else {
+ uint32 surrogate = static_cast<uint32>(ucs4) - 0x10000;
+ uint16 hi_sur = static_cast<uint16>((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1)));
+ uint16 lo_sur = static_cast<uint16>((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1)));
+ out.push_back(static_cast<typename Tdststring::value_type>(hi_sur));
+ out.push_back(static_cast<typename Tdststring::value_type>(lo_sur));
+ }
+ }
+ return out;
+}
+
+
+
+#if MPT_OS_WINDOWS
+
+inline bool has_codepage(UINT cp) {
+ return IsValidCodePage(cp) ? true : false;
+}
+
+inline bool windows_has_encoding(common_encoding encoding) {
+ bool result = false;
+ switch (encoding) {
+ case common_encoding::utf8:
+ result = has_codepage(CP_UTF8);
+ break;
+ case common_encoding::ascii:
+ result = has_codepage(20127);
+ break;
+ case common_encoding::iso8859_1:
+ result = has_codepage(28591);
+ break;
+ case common_encoding::iso8859_15:
+ result = has_codepage(28605);
+ break;
+ case common_encoding::cp850:
+ result = has_codepage(850);
+ break;
+ case common_encoding::cp437:
+ result = has_codepage(437);
+ break;
+ case common_encoding::windows1252:
+ result = has_codepage(1252);
+ break;
+ case common_encoding::amiga:
+ result = false;
+ break;
+ case common_encoding::riscos:
+ result = false;
+ break;
+ case common_encoding::iso8859_1_no_c1:
+ result = false;
+ break;
+ case common_encoding::iso8859_15_no_c1:
+ result = false;
+ break;
+ case common_encoding::amiga_no_c1:
+ result = false;
+ break;
+ }
+ return result;
+}
+
+inline bool windows_has_encoding(logical_encoding encoding) {
+ bool result = false;
+ switch (encoding) {
+ case logical_encoding::locale:
+ result = true;
+ break;
+ case logical_encoding::active_locale:
+ result = false;
+ break;
+ }
+ return result;
+}
+
+inline UINT codepage_from_encoding(logical_encoding encoding) {
+ UINT result = 0;
+ switch (encoding) {
+ case logical_encoding::locale:
+ result = CP_ACP;
+ break;
+ case logical_encoding::active_locale:
+ throw std::domain_error("unsupported encoding");
+ break;
+ }
+ return result;
+}
+
+inline UINT codepage_from_encoding(common_encoding encoding) {
+ UINT result = 0;
+ switch (encoding) {
+ case common_encoding::utf8:
+ result = CP_UTF8;
+ break;
+ case common_encoding::ascii:
+ result = 20127;
+ break;
+ case common_encoding::iso8859_1:
+ result = 28591;
+ break;
+ case common_encoding::iso8859_15:
+ result = 28605;
+ break;
+ case common_encoding::cp850:
+ result = 850;
+ break;
+ case common_encoding::cp437:
+ result = 437;
+ break;
+ case common_encoding::windows1252:
+ result = 1252;
+ break;
+ case common_encoding::amiga:
+ throw std::domain_error("unsupported encoding");
+ break;
+ case common_encoding::riscos:
+ throw std::domain_error("unsupported encoding");
+ break;
+ case common_encoding::iso8859_1_no_c1:
+ throw std::domain_error("unsupported encoding");
+ break;
+ case common_encoding::iso8859_15_no_c1:
+ throw std::domain_error("unsupported encoding");
+ break;
+ case common_encoding::amiga_no_c1:
+ throw std::domain_error("unsupported encoding");
+ break;
+ }
+ return result;
+}
+
+template <typename Tdststring>
+inline Tdststring encode_codepage(UINT codepage, const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+ Tdststring encoded_string;
+ int required_size = WideCharToMultiByte(codepage, 0, src.data(), mpt::saturate_cast<int>(src.size()), nullptr, 0, nullptr, nullptr);
+ if (required_size > 0) {
+ encoded_string.resize(required_size);
+ WideCharToMultiByte(codepage, 0, src.data(), mpt::saturate_cast<int>(src.size()), reinterpret_cast<CHAR *>(encoded_string.data()), required_size, nullptr, nullptr);
+ }
+ return encoded_string;
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_codepage(UINT codepage, const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+ mpt::widestring decoded_string;
+ int required_size = MultiByteToWideChar(codepage, 0, reinterpret_cast<const CHAR *>(src.data()), mpt::saturate_cast<int>(src.size()), nullptr, 0);
+ if (required_size > 0) {
+ decoded_string.resize(required_size);
+ MultiByteToWideChar(codepage, 0, reinterpret_cast<const CHAR *>(src.data()), mpt::saturate_cast<int>(src.size()), decoded_string.data(), required_size);
+ }
+ return decoded_string;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+
+#if MPT_OS_DJGPP
+
+inline common_encoding djgpp_get_locale_encoding() {
+ uint16 active_codepage = 437;
+ uint16 system_codepage = 437;
+ __dpmi_regs regs;
+ std::memset(&regs, 0, sizeof(__dpmi_regs));
+ regs.x.ax = 0x6601;
+ if (__dpmi_int(0x21, &regs) == 0) {
+ int cf = (regs.x.flags >> 0) & 1;
+ if (cf == 0) {
+ active_codepage = regs.x.bx;
+ system_codepage = regs.x.dx;
+ }
+ }
+ common_encoding result = common_encoding::cp437;
+ if (active_codepage == 0) {
+ result = common_encoding::cp437;
+ } else if (active_codepage == 437) {
+ result = common_encoding::cp437;
+ } else if (active_codepage == 850) {
+ result = common_encoding::cp850;
+ } else if (system_codepage == 437) {
+ result = common_encoding::cp437;
+ } else if (system_codepage == 850) {
+ result = common_encoding::cp850;
+ } else {
+ result = common_encoding::cp437;
+ }
+ return result;
+}
+
+#endif // MPT_OS_DJGPP
+
+
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+
+// Note:
+//
+// std::codecvt::out in LLVM libc++ does not advance in and out pointers when
+// running into a non-convertible character. This can happen when no locale is
+// set on FreeBSD or MacOSX. This behaviour violates the C++ standard.
+//
+// We apply the following (albeit costly, even on other platforms) work-around:
+// If the conversion errors out and does not advance the pointers at all, we
+// retry the conversion with a space character prepended to the string. If it
+// still does error out, we retry the whole conversion character by character.
+// This is costly even on other platforms in one single case: The first
+// character is an invalid Unicode code point or otherwise not convertible. Any
+// following non-convertible characters are not a problem.
+
+inline std::wstring decode_locale_impl(const std::string & str, const std::locale & locale, wchar_t replacement = L'\uFFFD', int retry = 0, bool * progress = nullptr) {
+ if (str.empty()) {
+ return std::wstring();
+ }
+ std::vector<wchar_t> out;
+ using codecvt_type = std::codecvt<wchar_t, char, std::mbstate_t>;
+ std::mbstate_t state = std::mbstate_t();
+ const codecvt_type & facet = std::use_facet<codecvt_type>(locale);
+ codecvt_type::result result = codecvt_type::partial;
+ const char * in_begin = str.data();
+ const char * in_end = in_begin + str.size();
+ out.resize((in_end - in_begin) * (mpt::saturate_cast<std::size_t>(facet.max_length()) + 1));
+ wchar_t * out_begin = out.data();
+ wchar_t * out_end = out.data() + out.size();
+ const char * in_next = nullptr;
+ wchar_t * out_next = nullptr;
+ do {
+ if (retry == 2) {
+ for (;;) {
+ in_next = nullptr;
+ out_next = nullptr;
+ result = facet.in(state, in_begin, in_begin + 1, in_next, out_begin, out_end, out_next);
+ if (result == codecvt_type::partial && in_next == in_begin + 1) {
+ in_begin = in_next;
+ out_begin = out_next;
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else {
+ in_next = nullptr;
+ out_next = nullptr;
+ result = facet.in(state, in_begin, in_end, in_next, out_begin, out_end, out_next);
+ }
+ if (result == codecvt_type::partial || (result == codecvt_type::error && out_next == out_end)) {
+ out.resize(out.size() * 2);
+ in_begin = in_next;
+ out_begin = out.data() + (out_next - out_begin);
+ out_end = out.data() + out.size();
+ continue;
+ }
+ if (retry == 0) {
+ if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
+ bool made_progress = true;
+ decode_locale_impl(std::string(" ") + str, locale, replacement, 1, &made_progress);
+ if (!made_progress) {
+ return decode_locale_impl(str, locale, replacement, 2);
+ }
+ }
+ } else if (retry == 1) {
+ if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
+ *progress = false;
+ } else {
+ *progress = true;
+ }
+ return std::wstring();
+ }
+ if (result == codecvt_type::error) {
+ ++in_next;
+ *out_next = replacement;
+ ++out_next;
+ }
+ in_begin = in_next;
+ out_begin = out_next;
+ } while ((result == codecvt_type::error && in_next < in_end && out_next < out_end) || (retry == 2 && in_next < in_end));
+ return std::wstring(out.data(), out_next);
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode_locale(const std::locale & locale, const Tsrcstring & src) {
+ if constexpr (std::is_same<Tsrcstring, std::string>::value) {
+ return decode_locale_impl(src, locale);
+ } else {
+ return decode_locale_impl(std::string(src.begin(), src.end()), locale);
+ }
+}
+
+inline std::string encode_locale_impl(const std::wstring & str, const std::locale & locale, char replacement = '?', int retry = 0, bool * progress = nullptr) {
+ if (str.empty()) {
+ return std::string();
+ }
+ std::vector<char> out;
+ using codecvt_type = std::codecvt<wchar_t, char, std::mbstate_t>;
+ std::mbstate_t state = std::mbstate_t();
+ const codecvt_type & facet = std::use_facet<codecvt_type>(locale);
+ codecvt_type::result result = codecvt_type::partial;
+ const wchar_t * in_begin = str.data();
+ const wchar_t * in_end = in_begin + str.size();
+ out.resize((in_end - in_begin) * (mpt::saturate_cast<std::size_t>(facet.max_length()) + 1));
+ char * out_begin = out.data();
+ char * out_end = out.data() + out.size();
+ const wchar_t * in_next = nullptr;
+ char * out_next = nullptr;
+ do {
+ if (retry == 2) {
+ for (;;) {
+ in_next = nullptr;
+ out_next = nullptr;
+ result = facet.out(state, in_begin, in_begin + 1, in_next, out_begin, out_end, out_next);
+ if (result == codecvt_type::partial && in_next == in_begin + 1) {
+ in_begin = in_next;
+ out_begin = out_next;
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else {
+ in_next = nullptr;
+ out_next = nullptr;
+ result = facet.out(state, in_begin, in_end, in_next, out_begin, out_end, out_next);
+ }
+ if (result == codecvt_type::partial || (result == codecvt_type::error && out_next == out_end)) {
+ out.resize(out.size() * 2);
+ in_begin = in_next;
+ out_begin = out.data() + (out_next - out_begin);
+ out_end = out.data() + out.size();
+ continue;
+ }
+ if (retry == 0) {
+ if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
+ bool made_progress = true;
+ encode_locale_impl(std::wstring(L" ") + str, locale, replacement, 1, &made_progress);
+ if (!made_progress) {
+ return encode_locale_impl(str, locale, replacement, 2);
+ }
+ }
+ } else if (retry == 1) {
+ if (result == codecvt_type::error && in_next == in_begin && out_next == out_begin) {
+ *progress = false;
+ } else {
+ *progress = true;
+ }
+ return std::string();
+ }
+ if (result == codecvt_type::error) {
+ ++in_next;
+ *out_next = replacement;
+ ++out_next;
+ }
+ in_begin = in_next;
+ out_begin = out_next;
+ } while ((result == codecvt_type::error && in_next < in_end && out_next < out_end) || (retry == 2 && in_next < in_end));
+ return std::string(out.data(), out_next);
+}
+
+template <typename Tdststring>
+inline Tdststring encode_locale(const std::locale & locale, const mpt::widestring & src) {
+ if constexpr (std::is_same<Tdststring, std::string>::value) {
+ return encode_locale_impl(src, locale);
+ } else {
+ const std::string tmp = encode_locale_impl(src, locale);
+ return Tdststring(tmp.begin(), tmp.end());
+ }
+}
+
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+
+
+#if MPT_OS_WINDOWS
+template <typename Tdststring>
+inline Tdststring encode(UINT codepage, const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+ return encode_codepage<Tdststring>(codepage, src);
+}
+#endif // MPT_OS_WINDOWS
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <typename Tdststring>
+inline Tdststring encode(const std::locale & locale, const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+ return encode_locale<Tdststring>(src, locale);
+}
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+template <typename Tdststring>
+inline Tdststring encode(const char32_t (&table)[256], const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+ return encode_8bit<Tdststring>(src, table);
+}
+
+template <typename Tdststring>
+inline Tdststring encode(common_encoding encoding, const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+#if MPT_OS_WINDOWS
+ if (windows_has_encoding(encoding)) {
+ return encode_codepage<Tdststring>(codepage_from_encoding(encoding), src);
+ }
+#endif
+ switch (encoding) {
+ case common_encoding::utf8:
+ return encode_utf8<Tdststring>(src);
+ break;
+ case common_encoding::ascii:
+ return encode_ascii<Tdststring>(src);
+ break;
+ case common_encoding::iso8859_1:
+ return encode_iso8859_1<Tdststring>(src);
+ break;
+ case common_encoding::iso8859_15:
+ return encode_8bit<Tdststring>(src, CharsetTableISO8859_15);
+ break;
+ case common_encoding::cp437:
+ return encode_8bit<Tdststring>(src, CharsetTableCP437);
+ break;
+ case common_encoding::cp850:
+ return encode_8bit<Tdststring>(src, CharsetTableCP850);
+ break;
+ case common_encoding::windows1252:
+ return encode_8bit<Tdststring>(src, CharsetTableWindows1252);
+ break;
+ case common_encoding::amiga:
+ return encode_8bit<Tdststring>(src, CharsetTableAmiga);
+ break;
+ case common_encoding::riscos:
+ return encode_8bit<Tdststring>(src, CharsetTableRISC_OS);
+ break;
+ case common_encoding::iso8859_1_no_c1:
+ return encode_8bit_no_c1<Tdststring>(src, CharsetTableISO8859_1);
+ break;
+ case common_encoding::iso8859_15_no_c1:
+ return encode_8bit_no_c1<Tdststring>(src, CharsetTableISO8859_15);
+ break;
+ case common_encoding::amiga_no_c1:
+ return encode_8bit_no_c1<Tdststring>(src, CharsetTableAmiga);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+}
+
+template <typename Tdststring>
+inline Tdststring encode(logical_encoding encoding, const mpt::widestring & src) {
+ static_assert(sizeof(typename Tdststring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tdststring::value_type>::value);
+#if MPT_OS_WINDOWS
+ if (windows_has_encoding(encoding)) {
+ return encode_codepage<Tdststring>(codepage_from_encoding(encoding), src);
+ }
+#endif
+#if MPT_OS_DJGPP
+ switch (encoding) {
+ case logical_encoding::locale:
+ return encode<Tdststring>(djgpp_get_locale_encoding(), src);
+ break;
+ case logical_encoding::active_locale:
+ return encode<Tdststring>(djgpp_get_locale_encoding(), src);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+#elif !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ switch (encoding) {
+ case logical_encoding::locale:
+ return encode_locale<Tdststring>(std::locale(""), src);
+ break;
+ case logical_encoding::active_locale:
+ return encode_locale<Tdststring>(std::locale(), src);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+#else
+ throw std::domain_error("unsupported encoding");
+#endif
+}
+
+#if MPT_OS_WINDOWS
+template <typename Tsrcstring>
+inline mpt::widestring decode(UINT codepage, const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+ return decode_codepage(codepage, src);
+}
+#endif // MPT_OS_WINDOWS
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <typename Tsrcstring>
+inline mpt::widestring decode(const std::locale & locale, const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+ return decode_locale(src, locale);
+}
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+template <typename Tsrcstring>
+inline mpt::widestring decode(const char32_t (&table)[256], const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+ return decode_8bit(src, table);
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode(common_encoding encoding, const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+#if MPT_OS_WINDOWS
+ if (windows_has_encoding(encoding)) {
+ return decode_codepage(codepage_from_encoding(encoding), src);
+ }
+#endif
+ switch (encoding) {
+ case common_encoding::utf8:
+ return decode_utf8(src);
+ break;
+ case common_encoding::ascii:
+ return decode_ascii(src);
+ break;
+ case common_encoding::iso8859_1:
+ return decode_iso8859_1(src);
+ break;
+ case common_encoding::iso8859_15:
+ return decode_8bit(src, CharsetTableISO8859_15);
+ break;
+ case common_encoding::cp437:
+ return decode_8bit(src, CharsetTableCP437);
+ break;
+ case common_encoding::cp850:
+ return decode_8bit(src, CharsetTableCP850);
+ break;
+ case common_encoding::windows1252:
+ return decode_8bit(src, CharsetTableWindows1252);
+ break;
+ case common_encoding::amiga:
+ return decode_8bit(src, CharsetTableAmiga);
+ break;
+ case common_encoding::riscos:
+ return decode_8bit(src, CharsetTableRISC_OS);
+ break;
+ case common_encoding::iso8859_1_no_c1:
+ return decode_8bit_no_c1(src, CharsetTableISO8859_1);
+ break;
+ case common_encoding::iso8859_15_no_c1:
+ return decode_8bit_no_c1(src, CharsetTableISO8859_15);
+ break;
+ case common_encoding::amiga_no_c1:
+ return decode_8bit_no_c1(src, CharsetTableAmiga);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+}
+
+template <typename Tsrcstring>
+inline mpt::widestring decode(logical_encoding encoding, const Tsrcstring & src) {
+ static_assert(sizeof(typename Tsrcstring::value_type) == sizeof(char));
+ static_assert(mpt::is_character<typename Tsrcstring::value_type>::value);
+#if MPT_OS_WINDOWS
+ if (windows_has_encoding(encoding)) {
+ return decode_codepage(codepage_from_encoding(encoding), src);
+ }
+#endif
+#if MPT_OS_DJGPP
+ switch (encoding) {
+ case logical_encoding::locale:
+ return decode(djgpp_get_locale_encoding(), src);
+ break;
+ case logical_encoding::active_locale:
+ return decode(djgpp_get_locale_encoding(), src);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+#elif !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+ switch (encoding) {
+ case logical_encoding::locale:
+ return decode_locale(std::locale(""), src);
+ break;
+ case logical_encoding::active_locale:
+ return decode_locale(std::locale(), src);
+ break;
+ }
+ throw std::domain_error("unsupported encoding");
+#else
+ throw std::domain_error("unsupported encoding");
+#endif
+}
+
+
+
+inline bool is_utf8(const std::string & str) {
+ return (str == encode<std::string>(common_encoding::utf8, decode<std::string>(common_encoding::utf8, str)));
+}
+
+
+
+template <typename Tstring>
+struct string_transcoder {
+};
+
+template <logical_encoding encoding>
+struct string_transcoder<std::basic_string<char, logical_encoding_char_traits<encoding>>> {
+ using string_type = std::basic_string<char, logical_encoding_char_traits<encoding>>;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::decode<string_type>(encoding, src);
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return mpt::encode<string_type>(encoding, src);
+ }
+};
+
+template <common_encoding encoding>
+struct string_transcoder<std::basic_string<char, common_encoding_char_traits<encoding>>> {
+ using string_type = std::basic_string<char, common_encoding_char_traits<encoding>>;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::decode<string_type>(encoding, src);
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return mpt::encode<string_type>(encoding, src);
+ }
+};
+
+#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct string_transcoder<std::wstring> {
+ using string_type = std::wstring;
+ static inline mpt::widestring decode(const string_type & src) {
+ return src;
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return src;
+ }
+};
+#endif // !MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_CXX_AT_LEAST(20)
+template <>
+struct string_transcoder<std::u8string> {
+ using string_type = std::u8string;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::decode_utf8<string_type>(src);
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return mpt::encode_utf8<string_type>(src);
+ }
+};
+#endif // C++10
+
+template <>
+struct string_transcoder<std::u16string> {
+ using string_type = std::u16string;
+ static inline mpt::widestring decode(const string_type & src) {
+ if constexpr (sizeof(mpt::widechar) == sizeof(char16_t)) {
+ return mpt::widestring(src.begin(), src.end());
+ } else {
+ return utf32_from_utf16<mpt::widestring, std::u16string>(src);
+ }
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ if constexpr (sizeof(mpt::widechar) == sizeof(char16_t)) {
+ return string_type(src.begin(), src.end());
+ } else {
+ return utf16_from_utf32<std::u16string, mpt::widestring>(src);
+ }
+ }
+};
+
+#if defined(MPT_COMPILER_QUIRK_NO_WCHAR)
+template <>
+struct string_transcoder<std::u32string> {
+ using string_type = std::u32string;
+ static inline mpt::widestring decode(const string_type & src) {
+ return src;
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return src;
+ }
+};
+#else // !MPT_COMPILER_QUIRK_NO_WCHAR
+template <>
+struct string_transcoder<std::u32string> {
+ using string_type = std::u32string;
+ static inline mpt::widestring decode(const string_type & src) {
+ if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
+ return mpt::widestring(src.begin(), src.end());
+ } else {
+ return utf16_from_utf32<mpt::widestring, std::u32string>(src);
+ }
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ if constexpr (sizeof(mpt::widechar) == sizeof(char32_t)) {
+ return string_type(src.begin(), src.end());
+ } else {
+ return utf32_from_utf16<std::u32string, mpt::widestring>(src);
+ }
+ }
+};
+#endif // MPT_COMPILER_QUIRK_NO_WCHAR
+
+#if MPT_DETECTED_MFC
+
+template <>
+struct string_transcoder<CStringW> {
+ using string_type = CStringW;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::widestring(src.GetString());
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return src.c_str();
+ }
+};
+
+template <>
+struct string_transcoder<CStringA> {
+ using string_type = CStringA;
+ static inline mpt::widestring decode(const string_type & src) {
+ return mpt::decode<std::string>(mpt::logical_encoding::locale, std::string(src.GetString()));
+ }
+ static inline string_type encode(const mpt::widestring & src) {
+ return mpt::encode<std::string>(mpt::logical_encoding::locale, src).c_str();
+ }
+};
+
+#endif // MPT_DETECTED_MFC
+
+template <typename Tdststring, typename Tsrcstring, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<Tsrcstring>::type>::value, bool> = true>
+inline Tdststring transcode(const Tsrcstring & src) {
+ if constexpr (std::is_same<Tdststring, typename mpt::make_string_type<Tsrcstring>::type>::value) {
+ return mpt::as_string(src);
+ } else {
+ return string_transcoder<Tdststring>::encode(string_transcoder<decltype(mpt::as_string(src))>::decode(mpt::as_string(src)));
+ }
+}
+
+template <typename Tdststring, typename Tsrcstring, typename Tencoding, std::enable_if_t<std::is_same<Tdststring, std::string>::value, bool> = true, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<Tsrcstring>::type>::value, bool> = true>
+inline Tdststring transcode(Tencoding to, const Tsrcstring & src) {
+ return mpt::encode<Tdststring>(to, string_transcoder<decltype(mpt::as_string(src))>::decode(mpt::as_string(src)));
+}
+
+template <typename Tdststring, typename Tsrcstring, typename Tencoding, std::enable_if_t<std::is_same<typename mpt::make_string_type<Tsrcstring>::type, std::string>::value, bool> = true, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<Tsrcstring>::type>::value, bool> = true>
+inline Tdststring transcode(Tencoding from, const Tsrcstring & src) {
+ return string_transcoder<Tdststring>::encode(mpt::decode<decltype(mpt::as_string(src))>(from, mpt::as_string(src)));
+}
+
+template <typename Tdststring, typename Tsrcstring, typename Tto, typename Tfrom, std::enable_if_t<mpt::is_string_type<typename mpt::make_string_type<Tsrcstring>::type>::value, bool> = true>
+inline Tdststring transcode(Tto to, Tfrom from, const Tsrcstring & src) {
+ return mpt::encode<Tdststring>(to, mpt::decode<decltype(mpt::as_string(src))>(from, mpt::as_string(src)));
+}
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_STRING_TRANSCODE_TRANSCODE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/system_error/system_error.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/system_error/system_error.hpp
new file mode 100644
index 00000000..c15b5bd6
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/system_error/system_error.hpp
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_SYSTEM_ERROR_SYSTEM_ERROR_HPP
+#define MPT_SYSTEM_ERROR_SYSTEM_ERROR_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/format/message.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+
+#if MPT_OS_WINDOWS
+#include <stdexcept>
+#if MPT_OS_WINDOWS_WINRT
+#include <vector>
+#endif // MPT_OS_WINDOWS_WINRT
+#endif // MPT_OS_WINDOWS
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_OS_WINDOWS
+
+
+
+namespace windows {
+
+
+
+inline mpt::ustring GetErrorMessage(DWORD errorCode, HANDLE hModule = NULL) {
+#if MPT_OS_WINDOWS_WINRT
+ std::vector<TCHAR> msgbuf(65536);
+ if (FormatMessage(
+ (hModule ? FORMAT_MESSAGE_FROM_HMODULE : 0) | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ hModule,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ msgbuf.data(),
+ mpt::saturate_cast<DWORD>(msgbuf.size()),
+ NULL)
+ == 0)
+ {
+ DWORD e = GetLastError();
+ if ((e == ERROR_NOT_ENOUGH_MEMORY) || (e == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ return {};
+ }
+ return mpt::transcode<mpt::ustring>(mpt::winstring{msgbuf.data()});
+#else
+ mpt::ustring message;
+ void * lpMsgBuf = nullptr;
+ if (FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | (hModule ? FORMAT_MESSAGE_FROM_HMODULE : 0) | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ hModule,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&lpMsgBuf,
+ 0,
+ NULL)
+ == 0)
+ {
+ DWORD e = GetLastError();
+ if (lpMsgBuf) {
+ LocalFree(lpMsgBuf);
+ }
+ if ((e == ERROR_NOT_ENOUGH_MEMORY) || (e == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ return {};
+ }
+ if (!lpMsgBuf) {
+ return {};
+ }
+ try {
+ message = mpt::transcode<mpt::ustring>(mpt::winstring{static_cast<LPTSTR>(lpMsgBuf)});
+ } catch (mpt::out_of_memory e) {
+ LocalFree(lpMsgBuf);
+ mpt::rethrow_out_of_memory(e);
+ }
+ LocalFree(lpMsgBuf);
+ return message;
+#endif
+}
+
+
+class error
+ : public std::runtime_error {
+public:
+ error(DWORD errorCode, HANDLE hModule = NULL)
+ : std::runtime_error(mpt::transcode<std::string>(mpt::exception_encoding, MPT_UFORMAT_MESSAGE("Windows Error: 0x{}: {}")(mpt::format<mpt::ustring>::hex0<8>(errorCode), GetErrorMessage(errorCode, hModule)))) {
+ return;
+ }
+};
+
+
+inline HANDLE CheckFileHANDLE(HANDLE handle) {
+ if (handle == INVALID_HANDLE_VALUE) {
+ DWORD err = ::GetLastError();
+ if ((err == ERROR_NOT_ENOUGH_MEMORY) || (err == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ throw windows::error(err);
+ }
+ return handle;
+}
+
+
+inline HANDLE CheckHANDLE(HANDLE handle) {
+ if (handle == NULL) {
+ DWORD err = ::GetLastError();
+ if ((err == ERROR_NOT_ENOUGH_MEMORY) || (err == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ throw windows::error(err);
+ }
+ return handle;
+}
+
+
+inline void CheckBOOL(BOOL result) {
+ if (result == FALSE) {
+ DWORD err = ::GetLastError();
+ if ((err == ERROR_NOT_ENOUGH_MEMORY) || (err == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ throw windows::error(err);
+ }
+}
+
+
+inline void ExpectError(DWORD expected) {
+ DWORD err = ::GetLastError();
+ if (err != expected) {
+ if ((err == ERROR_NOT_ENOUGH_MEMORY) || (err == ERROR_OUTOFMEMORY)) {
+ mpt::throw_out_of_memory();
+ }
+ throw windows::error(err);
+ }
+}
+
+
+
+} // namespace windows
+
+
+
+#endif // MPT_OS_WINDOWS
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_SYSTEM_ERROR_SYSTEM_ERROR_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/test/test.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/test/test.hpp
new file mode 100644
index 00000000..babb5214
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/test/test.hpp
@@ -0,0 +1,564 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_TEST_TEST_HPP
+#define MPT_TEST_TEST_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/source_location.hpp"
+
+#include <functional>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <stdexcept>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+#include <variant>
+
+#include <cstddef>
+#include <cstdlib>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace test {
+
+
+
+template <typename S, typename T, typename = void>
+struct is_to_stream_writable : std::false_type { };
+
+template <typename S, typename T>
+struct is_to_stream_writable<S, T, std::void_t<decltype(std::declval<S &>() << std::declval<T>())>> : std::true_type { };
+
+template <typename T>
+inline auto format(const T & x) -> typename std::enable_if<mpt::test::is_to_stream_writable<std::ostringstream, T>::value, std::string>::type {
+ std::ostringstream s;
+ s << x;
+ return s.str();
+}
+
+template <typename T>
+inline auto format(const T & x) -> typename std::enable_if<!mpt::test::is_to_stream_writable<std::ostringstream, T>::value, std::string>::type {
+ return typeid(x).name();
+}
+
+inline std::string get_exception_text() {
+ std::string result;
+ try {
+ // cppcheck false-positive
+ // cppcheck-suppress rethrowNoCurrentException
+ throw;
+ } catch (const std::exception & e) {
+ result = e.what();
+ } catch (...) {
+ result = "unknown exception";
+ }
+ return result;
+}
+
+struct result_success {
+};
+struct result_failure {
+ std::string text{};
+};
+struct result_unexpected_exception {
+ std::string text{};
+};
+
+struct result {
+ std::variant<std::monostate, result_success, result_failure, result_unexpected_exception> info{std::monostate{}};
+};
+
+struct statistics_counters {
+ std::size_t total{0};
+ std::size_t run{0};
+ std::size_t successes{0};
+ std::size_t failures{0};
+ std::size_t unexpected_exceptions{0};
+ std::size_t completed{0};
+ constexpr statistics_counters & operator+=(const statistics_counters & other) noexcept {
+ total += other.total;
+ run += other.run;
+ successes += other.successes;
+ failures += other.failures;
+ unexpected_exceptions += other.unexpected_exceptions;
+ completed += other.completed;
+ return *this;
+ }
+};
+
+struct group_statistics {
+ statistics_counters tests{};
+ statistics_counters cases{};
+ statistics_counters local_cases{};
+};
+
+struct global_statistics {
+ statistics_counters groups{};
+ statistics_counters tests{};
+ statistics_counters cases{};
+ std::map<std::string, group_statistics> individual_group_statistics{};
+ explicit constexpr operator bool() noexcept {
+ return succeeded();
+ }
+ constexpr bool operator!() noexcept {
+ return failed();
+ }
+ constexpr bool succeeded() noexcept {
+ return groups.successes == groups.run;
+ }
+ constexpr bool failed() noexcept {
+ return groups.failures > 0 || groups.unexpected_exceptions > 0;
+ }
+};
+
+class reporter_interface {
+protected:
+ virtual ~reporter_interface() = default;
+
+public:
+ virtual void run_begin(const mpt::source_location & loc) = 0;
+ virtual void group_begin(const mpt::source_location & loc, const char * name) = 0;
+ virtual void test_begin(const mpt::source_location & loc, const char * name) = 0;
+ virtual void case_run(const mpt::source_location & loc) = 0;
+ virtual void case_run(const mpt::source_location & loc, const char * text_e) = 0;
+ virtual void case_run(const mpt::source_location & loc, const char * text_ex, const char * text_e) = 0;
+ virtual void case_run(const mpt::source_location & loc, const char * text_a, const char * text_cmp, const char * text_b) = 0;
+ virtual void case_result(const mpt::source_location & loc, const mpt::test::result & result) = 0;
+ virtual void test_end(const mpt::source_location & loc, const char * name, const statistics_counters & counters) = 0;
+ virtual void group_end(const mpt::source_location & loc, const char * name, const group_statistics & statistics) = 0;
+ virtual void run_end(const mpt::source_location & loc, const global_statistics & statistics) = 0;
+ virtual void immediate_breakpoint() = 0;
+};
+
+class silent_reporter
+ : public reporter_interface {
+public:
+ silent_reporter() = default;
+ ~silent_reporter() override = default;
+
+public:
+ virtual void run_begin(const mpt::source_location &) override {
+ }
+ virtual void group_begin(const mpt::source_location &, const char *) override {
+ }
+ virtual void test_begin(const mpt::source_location &, const char *) override {
+ }
+ virtual void case_run(const mpt::source_location &) override {
+ }
+ virtual void case_run(const mpt::source_location &, const char *) override {
+ }
+ virtual void case_run(const mpt::source_location &, const char *, const char *) override {
+ }
+ virtual void case_run(const mpt::source_location &, const char *, const char *, const char *) override {
+ }
+ virtual void case_result(const mpt::source_location &, const mpt::test::result &) override {
+ }
+ virtual void test_end(const mpt::source_location &, const char *, const statistics_counters &) override {
+ }
+ virtual void group_end(const mpt::source_location &, const char *, const group_statistics &) override {
+ }
+ virtual void run_end(const mpt::source_location &, const global_statistics &) override {
+ }
+ virtual void immediate_breakpoint() override {
+ }
+};
+
+class simple_reporter : public reporter_interface {
+private:
+ std::ostream & s;
+
+public:
+ simple_reporter(std::ostream & s_)
+ : s(s_) {
+ s.flush();
+ }
+ ~simple_reporter() override {
+ s.flush();
+ }
+
+public:
+ void run_begin(const mpt::source_location & loc) override {
+ static_cast<void>(loc);
+ s << "Running test suite ..." << std::endl;
+ }
+ void group_begin(const mpt::source_location & loc, const char * name) override {
+ static_cast<void>(loc);
+ s << "Running group '" << name << "' ..." << std::endl;
+ }
+ void test_begin(const mpt::source_location & loc, const char * name) override {
+ static_cast<void>(loc);
+ s << " Running test '" << name << "' ..." << std::endl;
+ }
+ void case_run(const mpt::source_location & loc) override {
+ static_cast<void>(loc);
+ s << " Checking ..." << std::endl;
+ }
+ void case_run(const mpt::source_location & loc, const char * text_e) override {
+ static_cast<void>(loc);
+ s << " Checking '" << text_e << "' ..." << std::endl;
+ }
+ void case_run(const mpt::source_location & loc, const char * text_ex, const char * text_e) override {
+ static_cast<void>(loc);
+ if (text_ex) {
+ s << " Checking '" << text_e << " throws " << text_ex << "' ..." << std::endl;
+ } else {
+ s << " Checking '" << text_e << " throws' ..." << std::endl;
+ }
+ }
+ void case_run(const mpt::source_location & loc, const char * text_a, const char * text_cmp, const char * text_b) override {
+ static_cast<void>(loc);
+ s << " Checking '" << text_a << " " << text_cmp << " " << text_b << "' ..." << std::endl;
+ }
+ void case_result(const mpt::source_location & loc, const mpt::test::result & result) override {
+ static_cast<void>(loc);
+ s << " Checking done: ";
+ if (std::holds_alternative<result_success>(result.info)) {
+ s << "Success.";
+ } else if (std::holds_alternative<result_failure>(result.info)) {
+ s << "FAILURE: " << std::get<result_failure>(result.info).text;
+ } else if (std::holds_alternative<result_unexpected_exception>(result.info)) {
+ s << "UNEXPECTED EXCEPTION: " << std::get<result_unexpected_exception>(result.info).text;
+ }
+ s << std::endl;
+ }
+ void test_end(const mpt::source_location & loc, const char * name, const statistics_counters & counters) override {
+ static_cast<void>(loc);
+ static_cast<void>(counters);
+ s << " Running test '" << name << "' done." << std::endl;
+ }
+ void group_end(const mpt::source_location & loc, const char * name, const group_statistics & statistics) override {
+ static_cast<void>(loc);
+ static_cast<void>(statistics);
+ s << "Running group '" << name << "' done." << std::endl;
+ }
+ void run_end(const mpt::source_location & loc, const global_statistics & statistics) override {
+ static_cast<void>(loc);
+ s << "Running test suite done." << std::endl;
+ s << "groups: " << statistics.groups.total << " | " << statistics.groups.successes << " passed";
+ if (statistics.groups.failures || statistics.groups.unexpected_exceptions) {
+ s << " | " << statistics.groups.failures << " FAILED";
+ if (statistics.groups.unexpected_exceptions) {
+ s << " | " << statistics.groups.unexpected_exceptions << " UNEXPECTED EXCEPTIONS";
+ }
+ }
+ s << std::endl;
+ s << "tests: " << statistics.tests.total << " | " << statistics.tests.successes << " passed";
+ if (statistics.tests.failures || statistics.tests.unexpected_exceptions) {
+ s << " | " << statistics.tests.failures << " FAILED";
+ if (statistics.tests.unexpected_exceptions) {
+ s << " | " << statistics.tests.unexpected_exceptions << " UNEXPECTED EXCEPTIONS";
+ }
+ }
+ s << std::endl;
+ s << "checks: " << statistics.cases.total << " | " << statistics.cases.successes << " passed";
+ if (statistics.cases.failures || statistics.cases.unexpected_exceptions) {
+ s << " | " << statistics.cases.failures << " FAILED";
+ if (statistics.cases.unexpected_exceptions) {
+ s << " | " << statistics.cases.unexpected_exceptions << " UNEXPECTED EXCEPTIONS";
+ }
+ }
+ s << std::endl;
+ }
+ void immediate_breakpoint() override {
+ return;
+ }
+};
+
+struct group;
+
+struct context {
+ mpt::test::group & group;
+ mpt::test::reporter_interface & reporter;
+ mpt::test::group_statistics statistics{};
+};
+
+using void_context_function = void (*)(mpt::test::context &);
+
+struct group {
+ group * next{nullptr};
+ const char * name{""};
+ void_context_function func{nullptr};
+ inline group(const char * name_, void_context_function f)
+ : name(name_)
+ , func(f) {
+ next = group_list();
+ group_list() = this;
+ }
+ group_statistics run(mpt::test::reporter_interface & reporter, const mpt::source_location & loc = mpt::source_location::current()) {
+ mpt::test::context context{*this, reporter};
+ context.reporter.group_begin(loc, name);
+ if (func) {
+ func(context);
+ }
+ context.reporter.group_end(loc, name, context.statistics);
+ return context.statistics;
+ }
+
+public:
+ [[nodiscard]] static inline group *& group_list() noexcept {
+ static group * group_list = nullptr;
+ return group_list;
+ }
+};
+
+inline global_statistics run_all(mpt::test::reporter_interface & reporter, const mpt::source_location & loc = mpt::source_location::current()) {
+ global_statistics statistics{};
+ reporter.run_begin(loc);
+ for (group * g = group::group_list(); g; g = g->next) {
+ statistics.groups.total++;
+ statistics.groups.run++;
+ group_statistics s = g->run(reporter, loc);
+ if (s.tests.unexpected_exceptions) {
+ statistics.groups.unexpected_exceptions++;
+ } else if (s.tests.failures) {
+ statistics.groups.failures++;
+ } else {
+ statistics.groups.successes++;
+ }
+ statistics.tests += s.tests;
+ statistics.cases += s.cases;
+ statistics.groups.completed++;
+ statistics.individual_group_statistics[g->name] = s;
+ }
+ reporter.run_end(loc, statistics);
+ return statistics;
+}
+
+struct test {
+
+ mpt::test::context & context;
+ const char * name{""};
+ mpt::source_location source_location{mpt::source_location::current()};
+ void (*breakpoint)(void){nullptr};
+
+ test(const test &) = delete;
+ test & operator=(const test &) = delete;
+
+ inline test(mpt::test::context & context_, void (*breakpoint_)(void) = nullptr, const mpt::source_location & source_location_ = mpt::source_location::current())
+ : context(context_)
+ , source_location(source_location_)
+ , breakpoint(breakpoint_) {
+ report_test_begin();
+ }
+ inline test(mpt::test::context & context_, const char * name_, void (*breakpoint_)(void) = nullptr, const mpt::source_location & source_location_ = mpt::source_location::current())
+ : context(context_)
+ , name(name_)
+ , source_location(source_location_)
+ , breakpoint(breakpoint_) {
+ report_test_begin();
+ }
+
+ inline ~test() {
+ report_test_end();
+ }
+
+ inline void immediate_breakpoint() {
+ if (breakpoint) {
+ breakpoint();
+ } else {
+ context.reporter.immediate_breakpoint();
+ }
+ }
+
+ void report_test_begin() {
+ context.statistics.tests.total++;
+ context.statistics.tests.run++;
+ context.statistics.local_cases = statistics_counters{};
+ context.reporter.test_begin(source_location, name);
+ }
+
+ void report_run() {
+ context.statistics.local_cases.total++;
+ context.statistics.local_cases.run++;
+ context.reporter.case_run(source_location);
+ }
+ void report_run(const char * text_e) {
+ context.statistics.local_cases.total++;
+ context.statistics.local_cases.run++;
+ context.reporter.case_run(source_location, text_e);
+ }
+ void report_run(const char * text_ex, const char * text_e) {
+ context.statistics.local_cases.total++;
+ context.statistics.local_cases.run++;
+ context.reporter.case_run(source_location, text_ex, text_e);
+ }
+ void report_run(const char * text_a, const char * text_cmp, const char * text_b) {
+ context.statistics.local_cases.total++;
+ context.statistics.local_cases.run++;
+ context.reporter.case_run(source_location, text_a, text_cmp, text_b);
+ }
+
+ void report_result(mpt::test::result result) {
+ if (std::holds_alternative<result_success>(result.info)) {
+ context.statistics.local_cases.successes++;
+ } else if (std::holds_alternative<result_failure>(result.info)) {
+ context.statistics.local_cases.failures++;
+ } else if (std::holds_alternative<result_unexpected_exception>(result.info)) {
+ context.statistics.local_cases.unexpected_exceptions++;
+ }
+ context.statistics.local_cases.completed++;
+ context.reporter.case_result(source_location, result);
+ }
+
+ void report_test_end() {
+ context.statistics.cases += context.statistics.local_cases;
+ if (context.statistics.local_cases.unexpected_exceptions) {
+ context.statistics.tests.unexpected_exceptions++;
+ } else if (context.statistics.local_cases.failures) {
+ context.statistics.tests.failures++;
+ } else {
+ context.statistics.tests.successes++;
+ }
+ context.statistics.tests.completed++;
+ context.reporter.test_end(source_location, name, context.statistics.local_cases);
+ }
+
+ template <typename Texception, typename Tcallable, typename std::enable_if<std::is_invocable<Tcallable>::value, bool>::type = true>
+ inline test & expect_throws(Tcallable c, const char * text_ex = nullptr, const char * text_e = nullptr) {
+ const std::type_info & tiexception = typeid(Texception);
+ const std::type_info & tic = typeid(decltype(c()));
+ report_run(text_ex ? text_ex : tiexception.name(), text_e ? text_e : tic.name());
+ mpt::test::result result;
+ try {
+ c();
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{};
+ } catch (const Texception &) {
+ result.info = mpt::test::result_success{};
+ } catch (...) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_unexpected_exception{mpt::test::get_exception_text()};
+ }
+ report_result(result);
+ return *this;
+ }
+
+ template <typename Tcallable, typename std::enable_if<std::is_invocable<Tcallable>::value, bool>::type = true>
+ inline test & expect_throws_any(Tcallable c, const char * text_e = nullptr) {
+ const std::type_info & tic = typeid(decltype(c()));
+ report_run(nullptr, text_e ? text_e : tic.name());
+ mpt::test::result result;
+ try {
+ c();
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{};
+ } catch (...) {
+ result.info = mpt::test::result_success{};
+ }
+ report_result(result);
+ return *this;
+ }
+
+ template <typename Texpr, typename std::enable_if<std::is_invocable<Texpr>::value, bool>::type = true>
+ inline test & expect(Texpr e, const char * text_e = nullptr) {
+ const std::type_info & tie = typeid(decltype(std::invoke(e)));
+ report_run(text_e ? text_e : tie.name());
+ mpt::test::result result;
+ try {
+ const auto ve = std::invoke(e);
+ if (!ve) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{/*mpt::test::format(ve)*/};
+ } else {
+ result.info = mpt::test::result_success{};
+ }
+ } catch (...) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_unexpected_exception{mpt::test::get_exception_text()};
+ }
+ report_result(result);
+ return *this;
+ }
+
+ template <typename Ta, typename Tcmp, typename Tb, typename std::enable_if<std::is_invocable<Ta>::value, bool>::type = true, typename std::enable_if<std::is_invocable<Tb>::value, bool>::type = true>
+ inline test & expect(Ta && a, Tcmp cmp, Tb && b, const char * text_a = nullptr, const char * text_cmp = nullptr, const char * text_b = nullptr) {
+ const std::type_info & tia = typeid(decltype(std::invoke(a)));
+ const std::type_info & ticmp = typeid(decltype(cmp));
+ const std::type_info & tib = typeid(decltype(std::invoke(b)));
+ report_run(text_a ? text_a : tia.name(), text_cmp ? text_cmp : ticmp.name(), text_b ? text_b : tib.name());
+ mpt::test::result result;
+ try {
+ const auto va = std::invoke(a);
+ const auto vb = std::invoke(b);
+ if (!cmp(va, vb)) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{mpt::test::format(va) + " " + mpt::test::format(cmp) + " " + mpt::test::format(vb)};
+ } else {
+ result.info = mpt::test::result_success{};
+ }
+ } catch (...) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_unexpected_exception{mpt::test::get_exception_text()};
+ }
+ report_result(result);
+ return *this;
+ }
+
+ template <typename Texpr, typename std::enable_if<!std::is_invocable<Texpr>::value, bool>::type = true>
+ inline test & expect(Texpr && e, const char * text_e = nullptr) {
+ const std::type_info & tie = typeid(decltype(std::forward<Texpr>(e)));
+ report_run(text_e ? text_e : tie.name());
+ mpt::test::result result;
+ try {
+ const auto ve = std::forward<Texpr>(e);
+ if (!ve) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{/*mpt::test::format(ve)*/};
+ } else {
+ result.info = mpt::test::result_success{};
+ }
+ } catch (...) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_unexpected_exception{mpt::test::get_exception_text()};
+ }
+ report_result(result);
+ return *this;
+ }
+
+ template <typename Ta, typename Tcmp, typename Tb, typename std::enable_if<!std::is_invocable<Ta>::value, bool>::type = true, typename std::enable_if<!std::is_invocable<Tb>::value, bool>::type = true>
+ inline test & expect(Ta && a, Tcmp cmp, Tb && b, const char * text_a = nullptr, const char * text_cmp = nullptr, const char * text_b = nullptr) {
+ const std::type_info & tia = typeid(decltype(std::forward<Ta>(a)));
+ const std::type_info & ticmp = typeid(decltype(cmp));
+ const std::type_info & tib = typeid(decltype(std::forward<Tb>(b)));
+ report_run(text_a ? text_a : tia.name(), text_cmp ? text_cmp : ticmp.name(), text_b ? text_b : tib.name());
+ mpt::test::result result;
+ try {
+ const auto va = std::forward<Ta>(a);
+ const auto vb = std::forward<Tb>(b);
+ if (!cmp(va, vb)) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_failure{mpt::test::format(va) + " " + mpt::test::format(cmp) + " " + mpt::test::format(vb)};
+ } else {
+ result.info = mpt::test::result_success{};
+ }
+ } catch (...) {
+ immediate_breakpoint();
+ result.info = mpt::test::result_unexpected_exception{mpt::test::get_exception_text()};
+ }
+ report_result(result);
+ return *this;
+ }
+};
+
+
+
+} // namespace test
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_TEST_TEST_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/test/test_macros.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/test/test_macros.hpp
new file mode 100644
index 00000000..23d296f5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/test/test_macros.hpp
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_TEST_TEST_MACROS_HPP
+#define MPT_TEST_TEST_MACROS_HPP
+
+
+
+#include "mpt/base/namespace.hpp"
+#include "mpt/base/preprocessor.hpp"
+#include "mpt/test/test.hpp"
+
+#include <functional>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace test {
+
+
+
+#define MPT_TEST_GROUP_BEGIN(name) \
+ inline mpt::test::group MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_name) { \
+ name, [](mpt::test::context & mpt_test_context) {
+#define MPT_TEST_GROUP_END() \
+ } \
+ } \
+ ;
+
+#define MPT_TEST_GROUP_INLINE_IDENTIFIER(identifier, name) \
+ inline void MPT_PP_JOIN(mpt_test_group_func_, identifier)(mpt::test::context & mpt_test_context); \
+ inline mpt::test::group MPT_PP_JOIN(mpt_test_group_name_, identifier){ \
+ name, [](mpt::test::context & mpt_test_context) { \
+ MPT_PP_JOIN(mpt_test_group_func_, identifier) \
+ (mpt_test_context); \
+ }}; \
+ inline void MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func)(mpt::test::context & mpt_test_context)
+
+#define MPT_TEST_GROUP_INLINE(name) \
+ inline void MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func)(mpt::test::context & mpt_test_context); \
+ inline mpt::test::group MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_name){ \
+ name, [](mpt::test::context & mpt_test_context) { \
+ MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func) \
+ (mpt_test_context); \
+ }}; \
+ inline void MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func)(mpt::test::context & mpt_test_context)
+
+#define MPT_TEST_GROUP_STATIC(name) \
+ static void MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func)(mpt::test::context & mpt_test_context); \
+ static mpt::test::group MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_name){ \
+ name, [](mpt::test::context & mpt_test_context) { \
+ MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func) \
+ (mpt_test_context); \
+ }}; \
+ static void MPT_PP_UNIQUE_IDENTIFIER(mpt_test_group_func)(mpt::test::context & mpt_test_context)
+
+#define MPT_TEST_DELAYED(x) [&] { \
+ return x; \
+}
+
+#define MPT_TEST_EXPECT mpt::test::test{context}.expect
+
+#define MPT_TEST_EXPECT_EXPR(e) mpt::test::test{mpt_test_context}.expect([&] { return e; }, #e)
+#define MPT_TEST_EXPECT_CMP(a, cmp, b) mpt::test::test{mpt_test_context}.expect([&] { return a; }, [](const auto & a_, const auto & b_) { return a_ cmp b_; }, [&] { return b; }, #a, #cmp, #b)
+#define MPT_TEST_EXPECT_THROWS_ANY(expr) mpt::test::test{mpt_test_context}.expect_throws_any([&] { expr; }, #expr)
+#define MPT_TEST_EXPECT_THROWS(exception, expr) mpt::test::test{mpt_test_context}.expect_throws<exception>([&] { expr; }, #exception, #expr)
+
+#define MPT_TEST_EXPECT_EQUAL(a, b) mpt::test::test{mpt_test_context}.expect([&] { return a; }, std::equal_to<>{}, [&] { return b; }, #a, "==", #b)
+
+
+
+} // namespace test
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_TEST_TEST_MACROS_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/guid.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/guid.hpp
new file mode 100644
index 00000000..b941b753
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/guid.hpp
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_UUID_GUID_HPP
+#define MPT_UUID_GUID_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+#include <stdexcept>
+#include <vector>
+
+#if MPT_OS_WINDOWS
+#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600)
+#include <guiddef.h>
+#endif // _WIN32_WINNT
+#include <objbase.h>
+#include <rpc.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_OS_WINDOWS
+
+
+
+// COM CLSID<->string conversion
+// A CLSID string is not necessarily a standard UUID string,
+// it might also be a symbolic name for the interface.
+// (see CLSIDFromString ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms680589%28v=vs.85%29.aspx ))
+
+inline mpt::winstring CLSIDToString(CLSID clsid) {
+ std::wstring str;
+ LPOLESTR tmp = nullptr;
+ switch (::StringFromCLSID(clsid, &tmp)) {
+ case S_OK:
+ break;
+ case E_OUTOFMEMORY:
+ if (tmp) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ }
+ mpt::throw_out_of_memory();
+ break;
+ default:
+ if (tmp) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ }
+ throw std::logic_error("StringFromCLSID() failed.");
+ break;
+ }
+ if (!tmp) {
+ throw std::logic_error("StringFromCLSID() failed.");
+ }
+ try {
+ str = tmp;
+ } catch (mpt::out_of_memory e) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ mpt::rethrow_out_of_memory(e);
+ }
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ return mpt::transcode<mpt::winstring>(str);
+}
+
+inline CLSID StringToCLSID(const mpt::winstring & str_) {
+ const std::wstring str = mpt::transcode<std::wstring>(str_);
+ CLSID clsid = CLSID();
+ std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
+ switch (::CLSIDFromString(tmp.data(), &clsid)) {
+ case NOERROR:
+ // nothing
+ break;
+ case E_INVALIDARG:
+ clsid = CLSID();
+ break;
+ case CO_E_CLASSSTRING:
+ clsid = CLSID();
+ break;
+ case REGDB_E_CLASSNOTREG:
+ clsid = CLSID();
+ break;
+ case REGDB_E_READREGDB:
+ clsid = CLSID();
+ throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
+ break;
+ default:
+ clsid = CLSID();
+ throw std::logic_error("CLSIDFromString() failed.");
+ break;
+ }
+ return clsid;
+}
+
+inline bool VerifyStringToCLSID(const mpt::winstring & str_, CLSID & clsid) {
+ const std::wstring str = mpt::transcode<std::wstring>(str_);
+ bool result = false;
+ clsid = CLSID();
+ std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
+ switch (::CLSIDFromString(tmp.data(), &clsid)) {
+ case NOERROR:
+ result = true;
+ break;
+ case E_INVALIDARG:
+ result = false;
+ break;
+ case CO_E_CLASSSTRING:
+ result = false;
+ break;
+ case REGDB_E_CLASSNOTREG:
+ result = false;
+ break;
+ case REGDB_E_READREGDB:
+ throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
+ break;
+ default:
+ throw std::logic_error("CLSIDFromString() failed.");
+ break;
+ }
+ return result;
+}
+
+inline bool IsCLSID(const mpt::winstring & str_) {
+ const std::wstring str = mpt::transcode<std::wstring>(str_);
+ bool result = false;
+ CLSID clsid = CLSID();
+ std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
+ switch (::CLSIDFromString(tmp.data(), &clsid)) {
+ case NOERROR:
+ result = true;
+ break;
+ case E_INVALIDARG:
+ result = false;
+ break;
+ case CO_E_CLASSSTRING:
+ result = false;
+ break;
+ case REGDB_E_CLASSNOTREG:
+ result = false;
+ break;
+ case REGDB_E_READREGDB:
+ result = false;
+ throw std::runtime_error("CLSIDFromString() failed: REGDB_E_READREGDB.");
+ break;
+ default:
+ result = false;
+ throw std::logic_error("CLSIDFromString() failed.");
+ break;
+ }
+ return result;
+}
+
+
+// COM IID<->string conversion
+
+inline IID StringToIID(const mpt::winstring & str_) {
+ const std::wstring str = mpt::transcode<std::wstring>(str_);
+ IID iid = IID();
+ std::vector<OLECHAR> tmp(str.c_str(), str.c_str() + str.length() + 1);
+ switch (::IIDFromString(tmp.data(), &iid)) {
+ case S_OK:
+ // nothing
+ break;
+ case E_OUTOFMEMORY:
+ iid = IID();
+ mpt::throw_out_of_memory();
+ break;
+ case E_INVALIDARG:
+ iid = IID();
+ break;
+ default:
+ iid = IID();
+ throw std::logic_error("IIDFromString() failed.");
+ break;
+ }
+ return iid;
+}
+
+inline mpt::winstring IIDToString(IID iid) {
+ std::wstring str;
+ LPOLESTR tmp = nullptr;
+ switch (::StringFromIID(iid, &tmp)) {
+ case S_OK:
+ break;
+ case E_OUTOFMEMORY:
+ if (tmp) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ }
+ mpt::throw_out_of_memory();
+ break;
+ default:
+ if (tmp) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ }
+ throw std::logic_error("StringFromIID() failed.");
+ break;
+ }
+ if (!tmp) {
+ throw std::logic_error("StringFromIID() failed.");
+ }
+ try {
+ str = tmp;
+ } catch (mpt::out_of_memory e) {
+ ::CoTaskMemFree(tmp);
+ tmp = nullptr;
+ mpt::rethrow_out_of_memory(e);
+ }
+ return mpt::transcode<mpt::winstring>(str);
+}
+
+
+// General GUID<->string conversion.
+// The string must/will be in standard GUID format: {4F9A455D-E7EF-4367-B2F0-0C83A38A5C72}
+
+inline GUID StringToGUID(const mpt::winstring & str) {
+ return StringToIID(str);
+}
+
+inline mpt::winstring GUIDToString(GUID guid) {
+ std::vector<OLECHAR> tmp(256);
+ if (::StringFromGUID2(guid, tmp.data(), static_cast<int>(tmp.size())) <= 0) {
+ throw std::logic_error("StringFromGUID2() failed.");
+ }
+ return mpt::transcode<mpt::winstring>(tmp.data());
+}
+
+
+// Create a COM GUID
+
+inline GUID CreateGUID() {
+ GUID guid = GUID();
+ switch (::CoCreateGuid(&guid)) {
+ case S_OK:
+ // nothing
+ break;
+ default:
+ guid = GUID();
+ throw std::runtime_error("CoCreateGuid() failed.");
+ }
+ return guid;
+}
+
+
+// Checks the UUID against the NULL UUID. Returns false if it is NULL, true otherwise.
+
+inline bool IsValid(::UUID uuid) {
+ return false
+ || uuid.Data1 != 0
+ || uuid.Data2 != 0
+ || uuid.Data3 != 0
+ || uuid.Data4[0] != 0
+ || uuid.Data4[1] != 0
+ || uuid.Data4[2] != 0
+ || uuid.Data4[3] != 0
+ || uuid.Data4[4] != 0
+ || uuid.Data4[5] != 0
+ || uuid.Data4[6] != 0
+ || uuid.Data4[7] != 0;
+}
+
+
+
+#endif // MPT_OS_WINDOWS
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_UUID_GUID_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/tests/tests_uuid.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/tests/tests_uuid.hpp
new file mode 100644
index 00000000..7d1f0007
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/tests/tests_uuid.hpp
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_UUID_HPP
+#define MPT_BASE_TESTS_UUID_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/device.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+#include <cstddef>
+#include <cstring>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace uuid {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/uuid")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ using namespace mpt::uuid_literals;
+
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x2ed6593au, 0xdfe6, 0x4cf8, 0xb2e575ad7f600c32ull).ToUString(), MPT_USTRING("2ed6593a-dfe6-4cf8-b2e5-75ad7f600c32"));
+#if MPT_OS_WINDOWS
+ constexpr mpt::UUID uuid_tmp = "2ed6593a-dfe6-4cf8-b2e5-75ad7f600c32"_uuid;
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x2ed6593au, 0xdfe6, 0x4cf8, 0xb2e575ad7f600c32ull), uuid_tmp);
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x2ed6593au, 0xdfe6, 0x4cf8, 0xb2e575ad7f600c32ull), mpt::UUID(mpt::StringToGUID(TEXT("{2ed6593a-dfe6-4cf8-b2e5-75ad7f600c32}"))));
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x2ed6593au, 0xdfe6, 0x4cf8, 0xb2e575ad7f600c32ull), mpt::UUID(mpt::StringToCLSID(TEXT("{2ed6593a-dfe6-4cf8-b2e5-75ad7f600c32}"))));
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x00112233u, 0x4455, 0x6677, 0x8899AABBCCDDEEFFull), mpt::UUID(mpt::StringToGUID(TEXT("{00112233-4455-6677-8899-AABBCCDDEEFF}"))));
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x00112233u, 0x4455, 0x6677, 0xC899AABBCCDDEEFFull), mpt::UUID(mpt::StringToGUID(TEXT("{00112233-4455-6677-C899-AABBCCDDEEFF}"))));
+ MPT_TEST_EXPECT_EQUAL(mpt::GUIDToString(mpt::UUID(0x00112233u, 0x4455, 0x6677, 0x8899AABBCCDDEEFFull)), TEXT("{00112233-4455-6677-8899-AABBCCDDEEFF}"));
+ MPT_TEST_EXPECT_EQUAL(mpt::GUIDToString(mpt::UUID(0x00112233u, 0x4455, 0x6677, 0xC899AABBCCDDEEFFull)), TEXT("{00112233-4455-6677-C899-AABBCCDDEEFF}"));
+#endif // MPT_OS_WINDOWS
+
+ mpt::sane_random_device rd;
+ mpt::good_engine prng = mpt::make_prng<mpt::good_engine>(rd);
+
+#if MPT_OS_WINDOWS
+ MPT_TEST_EXPECT_EQUAL(mpt::IsValid(mpt::CreateGUID()), true);
+ {
+ mpt::UUID uuid = mpt::UUID::Generate(prng);
+ MPT_TEST_EXPECT_EQUAL(uuid, mpt::UUID::FromString(mpt::UUID(uuid).ToUString()));
+ MPT_TEST_EXPECT_EQUAL(uuid, mpt::UUID(mpt::StringToGUID(mpt::GUIDToString(uuid))));
+ MPT_TEST_EXPECT_EQUAL(uuid, mpt::UUID(mpt::StringToIID(mpt::IIDToString(uuid))));
+ MPT_TEST_EXPECT_EQUAL(uuid, mpt::UUID(mpt::StringToCLSID(mpt::CLSIDToString(uuid))));
+ }
+ {
+ GUID guid = mpt::UUID::Generate(prng);
+ MPT_TEST_EXPECT_EQUAL(IsEqualGUID(guid, static_cast<GUID>(mpt::UUID::FromString(mpt::UUID(guid).ToUString()))), TRUE);
+ MPT_TEST_EXPECT_EQUAL(IsEqualGUID(guid, mpt::StringToGUID(mpt::GUIDToString(guid))), TRUE);
+ MPT_TEST_EXPECT_EQUAL(IsEqualGUID(guid, mpt::StringToIID(mpt::IIDToString(guid))), TRUE);
+ MPT_TEST_EXPECT_EQUAL(IsEqualGUID(guid, mpt::StringToCLSID(mpt::CLSIDToString(guid))), TRUE);
+ }
+#endif // MPT_OS_WINDOWS
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID::Generate(prng).IsValid(), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID::GenerateLocalUseOnly(prng).IsValid(), true);
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID::Generate(prng) != mpt::UUID::Generate(prng), true);
+ mpt::UUID a = mpt::UUID::Generate(prng);
+ MPT_TEST_EXPECT_EQUAL(a, mpt::UUID::FromString(a.ToUString()));
+ std::byte uuiddata[16]{};
+ for (std::size_t i = 0; i < 16; ++i) {
+ uuiddata[i] = mpt::byte_cast<std::byte>(static_cast<uint8>(i));
+ }
+ static_assert(sizeof(mpt::UUID) == 16);
+ mpt::UUIDbin uuid2;
+ std::memcpy(&uuid2, uuiddata, 16);
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(uuid2).ToUString(), MPT_USTRING("00010203-0405-0607-0809-0a0b0c0d0e0f"));
+
+ constexpr mpt::UUID uuid3 = "2ed6593a-dfe6-4cf8-b2e5-75ad7f600c32"_uuid;
+ MPT_TEST_EXPECT_EQUAL(mpt::UUID(0x2ed6593au, 0xdfe6, 0x4cf8, 0xb2e575ad7f600c32ull), uuid3);
+}
+
+} // namespace uuid
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_UUID_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/uuid.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/uuid.hpp
new file mode 100644
index 00000000..3e56c06d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid/uuid.hpp
@@ -0,0 +1,419 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_UUID_UUID_HPP
+#define MPT_UUID_UUID_HPP
+
+
+
+#include "mpt/base/constexpr_throw.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/integer.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/endian/integer.hpp"
+#include "mpt/format/default_formatter.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/random/random.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+
+#if MPT_OS_WINDOWS
+#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600)
+#include <guiddef.h>
+#endif // _WIN32_WINNT
+#include <objbase.h>
+#include <rpc.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+// Microsoft on-disk layout
+struct GUIDms {
+ uint32le Data1;
+ uint16le Data2;
+ uint16le Data3;
+ uint64be Data4; // yes, big endian here
+};
+constexpr bool declare_binary_safe(const GUIDms &) {
+ return true;
+}
+static_assert(mpt::check_binary_size<GUIDms>(16));
+
+// RFC binary format
+struct UUIDbin {
+ uint32be Data1;
+ uint16be Data2;
+ uint16be Data3;
+ uint64be Data4;
+};
+constexpr bool declare_binary_safe(const UUIDbin &) {
+ return true;
+}
+static_assert(mpt::check_binary_size<UUIDbin>(16));
+
+
+
+struct UUID {
+private:
+ uint32 Data1;
+ uint16 Data2;
+ uint16 Data3;
+ uint64 Data4;
+
+public:
+ MPT_CONSTEXPRINLINE uint32 GetData1() const noexcept {
+ return Data1;
+ }
+ MPT_CONSTEXPRINLINE uint16 GetData2() const noexcept {
+ return Data2;
+ }
+ MPT_CONSTEXPRINLINE uint16 GetData3() const noexcept {
+ return Data3;
+ }
+ MPT_CONSTEXPRINLINE uint64 GetData4() const noexcept {
+ return Data4;
+ }
+
+public:
+ MPT_CONSTEXPRINLINE uint64 GetData64_1() const noexcept {
+ return (static_cast<uint64>(Data1) << 32) | (static_cast<uint64>(Data2) << 16) | (static_cast<uint64>(Data3) << 0);
+ }
+ MPT_CONSTEXPRINLINE uint64 GetData64_2() const noexcept {
+ return Data4;
+ }
+
+public:
+ // xxxxxxxx-xxxx-Mmxx-Nnxx-xxxxxxxxxxxx
+ // <--32-->-<16>-<16>-<-------64------>
+ MPT_CONSTEXPRINLINE bool IsNil() const noexcept {
+ return (Data1 == 0) && (Data2 == 0) && (Data3 == 0) && (Data4 == 0);
+ }
+ MPT_CONSTEXPRINLINE bool IsValid() const noexcept {
+ return (Data1 != 0) || (Data2 != 0) || (Data3 != 0) || (Data4 != 0);
+ }
+ MPT_CONSTEXPRINLINE uint8 Variant() const noexcept {
+ return Nn() >> 4u;
+ }
+ MPT_CONSTEXPRINLINE uint8 Version() const noexcept {
+ return Mm() >> 4u;
+ }
+ MPT_CONSTEXPRINLINE bool IsRFC4122() const noexcept {
+ return (Variant() & 0xcu) == 0x8u;
+ }
+
+private:
+ MPT_CONSTEXPRINLINE uint8 Mm() const noexcept {
+ return static_cast<uint8>((Data3 >> 8) & 0xffu);
+ }
+ MPT_CONSTEXPRINLINE uint8 Nn() const noexcept {
+ return static_cast<uint8>((Data4 >> 56) & 0xffu);
+ }
+ void MakeRFC4122(uint8 version) noexcept {
+ // variant
+ uint8 Nn = static_cast<uint8>((Data4 >> 56) & 0xffu);
+ Data4 &= 0x00ffffffffffffffull;
+ Nn &= ~(0xc0u);
+ Nn |= 0x80u;
+ Data4 |= static_cast<uint64>(Nn) << 56;
+ // version
+ version &= 0x0fu;
+ uint8 Mm = static_cast<uint8>((Data3 >> 8) & 0xffu);
+ Data3 &= 0x00ffu;
+ Mm &= ~(0xf0u);
+ Mm |= (version << 4u);
+ Data3 |= static_cast<uint16>(Mm) << 8;
+ }
+#if MPT_OS_WINDOWS
+private:
+ static mpt::UUID UUIDFromWin32(::UUID uuid) {
+ return mpt::UUID(uuid.Data1, uuid.Data2, uuid.Data3, (static_cast<uint64>(0) | (static_cast<uint64>(uuid.Data4[0]) << 56) | (static_cast<uint64>(uuid.Data4[1]) << 48) | (static_cast<uint64>(uuid.Data4[2]) << 40) | (static_cast<uint64>(uuid.Data4[3]) << 32) | (static_cast<uint64>(uuid.Data4[4]) << 24) | (static_cast<uint64>(uuid.Data4[5]) << 16) | (static_cast<uint64>(uuid.Data4[6]) << 8) | (static_cast<uint64>(uuid.Data4[7]) << 0)));
+ }
+ static ::UUID UUIDToWin32(mpt::UUID uuid) {
+ ::UUID result = ::UUID();
+ result.Data1 = uuid.GetData1();
+ result.Data2 = uuid.GetData2();
+ result.Data3 = uuid.GetData3();
+ result.Data4[0] = static_cast<uint8>(uuid.GetData4() >> 56);
+ result.Data4[1] = static_cast<uint8>(uuid.GetData4() >> 48);
+ result.Data4[2] = static_cast<uint8>(uuid.GetData4() >> 40);
+ result.Data4[3] = static_cast<uint8>(uuid.GetData4() >> 32);
+ result.Data4[4] = static_cast<uint8>(uuid.GetData4() >> 24);
+ result.Data4[5] = static_cast<uint8>(uuid.GetData4() >> 16);
+ result.Data4[6] = static_cast<uint8>(uuid.GetData4() >> 8);
+ result.Data4[7] = static_cast<uint8>(uuid.GetData4() >> 0);
+ return result;
+ }
+
+public:
+ explicit UUID(::UUID uuid) {
+ *this = UUIDFromWin32(uuid);
+ }
+ operator ::UUID() const {
+ return UUIDToWin32(*this);
+ }
+#endif // MPT_OS_WINDOWS
+private:
+ static MPT_CONSTEXPRINLINE uint8 NibbleFromChar(char x) {
+ return ('0' <= x && x <= '9') ? static_cast<uint8>(x - '0' + 0) : ('a' <= x && x <= 'z') ? static_cast<uint8>(x - 'a' + 10)
+ : ('A' <= x && x <= 'Z') ? static_cast<uint8>(x - 'A' + 10)
+ : mpt::constexpr_throw<uint8>(std::domain_error(""));
+ }
+ static MPT_CONSTEXPRINLINE uint8 ByteFromHex(char x, char y) {
+ return static_cast<uint8>(uint8(0) | (NibbleFromChar(x) << 4) | (NibbleFromChar(y) << 0));
+ }
+ static MPT_CONSTEXPRINLINE uint16 ParseHex16(const char * str) {
+ return static_cast<uint16>(uint16(0) | (static_cast<uint16>(ByteFromHex(str[0], str[1])) << 8) | (static_cast<uint16>(ByteFromHex(str[2], str[3])) << 0));
+ }
+ static MPT_CONSTEXPRINLINE uint32 ParseHex32(const char * str) {
+ return static_cast<uint32>(uint32(0) | (static_cast<uint32>(ByteFromHex(str[0], str[1])) << 24) | (static_cast<uint32>(ByteFromHex(str[2], str[3])) << 16) | (static_cast<uint32>(ByteFromHex(str[4], str[5])) << 8) | (static_cast<uint32>(ByteFromHex(str[6], str[7])) << 0));
+ }
+
+public:
+ static MPT_CONSTEXPRINLINE UUID ParseLiteral(const char * str, std::size_t len) {
+ return (len == 36 && str[8] == '-' && str[13] == '-' && str[18] == '-' && str[23] == '-') ? mpt::UUID(
+ ParseHex32(str + 0),
+ ParseHex16(str + 9),
+ ParseHex16(str + 14),
+ uint64(0)
+ | (static_cast<uint64>(ParseHex16(str + 19)) << 48)
+ | (static_cast<uint64>(ParseHex16(str + 24)) << 32)
+ | (static_cast<uint64>(ParseHex32(str + 28)) << 0))
+ : mpt::constexpr_throw<mpt::UUID>(std::domain_error(""));
+ }
+
+public:
+ MPT_CONSTEXPRINLINE UUID() noexcept
+ : Data1(0)
+ , Data2(0)
+ , Data3(0)
+ , Data4(0) {
+ return;
+ }
+ MPT_CONSTEXPRINLINE explicit UUID(uint32 Data1, uint16 Data2, uint16 Data3, uint64 Data4) noexcept
+ : Data1(Data1)
+ , Data2(Data2)
+ , Data3(Data3)
+ , Data4(Data4) {
+ return;
+ }
+ explicit UUID(UUIDbin uuid) {
+ Data1 = uuid.Data1.get();
+ Data2 = uuid.Data2.get();
+ Data3 = uuid.Data3.get();
+ Data4 = uuid.Data4.get();
+ }
+ explicit UUID(GUIDms guid) {
+ Data1 = guid.Data1.get();
+ Data2 = guid.Data2.get();
+ Data3 = guid.Data3.get();
+ Data4 = guid.Data4.get();
+ }
+ operator UUIDbin() const {
+ UUIDbin result{};
+ result.Data1 = GetData1();
+ result.Data2 = GetData2();
+ result.Data3 = GetData3();
+ result.Data4 = GetData4();
+ return result;
+ }
+ operator GUIDms() const {
+ GUIDms result{};
+ result.Data1 = GetData1();
+ result.Data2 = GetData2();
+ result.Data3 = GetData3();
+ result.Data4 = GetData4();
+ return result;
+ }
+
+public:
+ // Create a UUID
+ template <typename Trng>
+ static UUID Generate(Trng & rng) {
+#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
+#if (_WIN32_WINNT >= 0x0602)
+ ::GUID guid = ::GUID();
+ HRESULT result = CoCreateGuid(&guid);
+ if (result != S_OK) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ return mpt::UUID::UUIDFromWin32(guid);
+#else
+ return mpt::UUID::RFC4122Random(rng);
+#endif
+#elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
+ ::UUID uuid = ::UUID();
+ RPC_STATUS status = ::UuidCreate(&uuid);
+ if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ status = RPC_S_OK;
+ if (UuidIsNil(&uuid, &status) != FALSE) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ if (status != RPC_S_OK) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ return mpt::UUID::UUIDFromWin32(uuid);
+#else
+ return RFC4122Random(rng);
+#endif
+ }
+ // Create a UUID that contains local, traceable information.
+ // Safe for local use. May be faster.
+ template <typename Trng>
+ static UUID GenerateLocalUseOnly(Trng & rng) {
+#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
+#if (_WIN32_WINNT >= 0x0602)
+ ::GUID guid = ::GUID();
+ HRESULT result = CoCreateGuid(&guid);
+ if (result != S_OK) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ return mpt::UUID::UUIDFromWin32(guid);
+#else
+ return mpt::UUID::RFC4122Random(rng);
+#endif
+#elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
+#if _WIN32_WINNT >= 0x0501
+ // Available since Win2000, but we check for WinXP in order to not use this
+ // function in Win32old builds. It is not available on some non-fully
+ // patched Win98SE installs in the wild.
+ ::UUID uuid = ::UUID();
+ RPC_STATUS status = ::UuidCreateSequential(&uuid);
+ if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {
+ return Generate(rng);
+ }
+ status = RPC_S_OK;
+ if (UuidIsNil(&uuid, &status) != FALSE) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ if (status != RPC_S_OK) {
+ return mpt::UUID::RFC4122Random(rng);
+ }
+ return mpt::UUID::UUIDFromWin32(uuid);
+#else
+ // Fallback to ::UuidCreate is safe as ::UuidCreateSequential is only a
+ // tiny performance optimization.
+ return Generate(rng);
+#endif
+#else
+ return RFC4122Random(rng);
+#endif
+ }
+ // Create a RFC4122 Random UUID.
+ template <typename Trng>
+ static UUID RFC4122Random(Trng & prng) {
+ UUID result;
+ result.Data1 = mpt::random<uint32>(prng);
+ result.Data2 = mpt::random<uint16>(prng);
+ result.Data3 = mpt::random<uint16>(prng);
+ result.Data4 = mpt::random<uint64>(prng);
+ result.MakeRFC4122(4);
+ return result;
+ }
+ friend UUID UUIDRFC4122NamespaceV3(const UUID & ns, const mpt::ustring & name);
+ friend UUID UUIDRFC4122NamespaceV5(const UUID & ns, const mpt::ustring & name);
+
+public:
+ // General UUID<->string conversion.
+ // The string must/will be in standard UUID format: 4f9a455d-e7ef-4367-b2f0-0c83a38a5c72
+ static UUID FromString(const mpt::ustring & str) {
+ std::vector<mpt::ustring> segments = mpt::split<mpt::ustring>(str, MPT_ULITERAL("-"));
+ if (segments.size() != 5) {
+ return UUID();
+ }
+ if (segments[0].length() != 8) {
+ return UUID();
+ }
+ if (segments[1].length() != 4) {
+ return UUID();
+ }
+ if (segments[2].length() != 4) {
+ return UUID();
+ }
+ if (segments[3].length() != 4) {
+ return UUID();
+ }
+ if (segments[4].length() != 12) {
+ return UUID();
+ }
+ UUID result;
+ result.Data1 = mpt::ConvertHexStringTo<uint32>(segments[0]);
+ result.Data2 = mpt::ConvertHexStringTo<uint16>(segments[1]);
+ result.Data3 = mpt::ConvertHexStringTo<uint16>(segments[2]);
+ result.Data4 = mpt::ConvertHexStringTo<uint64>(segments[3] + segments[4]);
+ return result;
+ }
+ std::string ToAString() const {
+ return std::string()
+ + mpt::format<std::string>::hex0<8>(GetData1())
+ + std::string("-")
+ + mpt::format<std::string>::hex0<4>(GetData2())
+ + std::string("-")
+ + mpt::format<std::string>::hex0<4>(GetData3())
+ + std::string("-")
+ + mpt::format<std::string>::hex0<4>(static_cast<uint16>(GetData4() >> 48))
+ + std::string("-")
+ + mpt::format<std::string>::hex0<4>(static_cast<uint16>(GetData4() >> 32))
+ + mpt::format<std::string>::hex0<8>(static_cast<uint32>(GetData4() >> 0));
+ }
+ mpt::ustring ToUString() const {
+ return mpt::ustring()
+ + mpt::format<mpt::ustring>::hex0<8>(GetData1())
+ + MPT_USTRING("-")
+ + mpt::format<mpt::ustring>::hex0<4>(GetData2())
+ + MPT_USTRING("-")
+ + mpt::format<mpt::ustring>::hex0<4>(GetData3())
+ + MPT_USTRING("-")
+ + mpt::format<mpt::ustring>::hex0<4>(static_cast<uint16>(GetData4() >> 48))
+ + MPT_USTRING("-")
+ + mpt::format<mpt::ustring>::hex0<4>(static_cast<uint16>(GetData4() >> 32))
+ + mpt::format<mpt::ustring>::hex0<8>(static_cast<uint32>(GetData4() >> 0));
+ }
+};
+
+MPT_CONSTEXPRINLINE bool operator==(const mpt::UUID & a, const mpt::UUID & b) noexcept {
+ return (a.GetData1() == b.GetData1()) && (a.GetData2() == b.GetData2()) && (a.GetData3() == b.GetData3()) && (a.GetData4() == b.GetData4());
+}
+
+MPT_CONSTEXPRINLINE bool operator!=(const mpt::UUID & a, const mpt::UUID & b) noexcept {
+ return (a.GetData1() != b.GetData1()) || (a.GetData2() != b.GetData2()) || (a.GetData3() != b.GetData3()) || (a.GetData4() != b.GetData4());
+}
+
+
+namespace uuid_literals {
+
+MPT_CONSTEXPRINLINE mpt::UUID operator"" _uuid(const char * str, std::size_t len) {
+ return mpt::UUID::ParseLiteral(str, len);
+}
+
+} // namespace uuid_literals
+
+
+template <typename Tstring>
+inline Tstring uuid_to_string(mpt::UUID uuid) {
+ return mpt::transcode<Tstring>(uuid.ToUString());
+}
+
+template <>
+inline std::string uuid_to_string<std::string>(mpt::UUID uuid) {
+ return uuid.ToAString();
+}
+
+template <typename Tstring, typename T, std::enable_if_t<std::is_same<T, mpt::UUID>::value, bool> = true>
+inline Tstring format_value_default(const T & x) {
+ return mpt::transcode<Tstring>(mpt::uuid_to_string<typename mpt::select_format_string_type<Tstring>::type>(x));
+}
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_UUID_UUID_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/tests/tests_uuid_namespace.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/tests/tests_uuid_namespace.hpp
new file mode 100644
index 00000000..9c1812ff
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/tests/tests_uuid_namespace.hpp
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_BASE_TESTS_UUID_NAMESPACE_HPP
+#define MPT_BASE_TESTS_UUID_NAMESPACE_HPP
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/test/test.hpp"
+#include "mpt/test/test_macros.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "mpt/uuid_namespace/uuid_namespace.hpp"
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+namespace tests {
+namespace uuid_namespace {
+
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+MPT_TEST_GROUP_INLINE("mpt/uuid_namespace")
+#if MPT_COMPILER_CLANG
+#pragma clang diagnostic pop
+#endif
+{
+ using namespace mpt::uuid_literals;
+ constexpr mpt::UUID uuid_ns_dns = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"_uuid;
+ constexpr mpt::UUID expected = "74738ff5-5367-5958-9aee-98fffdcd1876"_uuid;
+ mpt::UUID gotten = mpt::UUIDRFC4122NamespaceV5(uuid_ns_dns, MPT_USTRING("www.example.org"));
+ MPT_TEST_EXPECT_EQUAL(gotten, expected);
+}
+
+} // namespace uuid_namespace
+} // namespace tests
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_BASE_TESTS_UUID_NAMESPACE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/uuid_namespace.hpp b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/uuid_namespace.hpp
new file mode 100644
index 00000000..9114e9f9
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/mpt/uuid_namespace/uuid_namespace.hpp
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
+
+#ifndef MPT_UUID_NAMESPACE_UUID_NAMESPACE_HPP
+#define MPT_UUID_NAMESPACE_UUID_NAMESPACE_HPP
+
+
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/base/namespace.hpp"
+#include "mpt/crypto/hash.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+#include <algorithm>
+#include <array>
+#include <string>
+#include <vector>
+
+
+
+namespace mpt {
+inline namespace MPT_INLINE_NS {
+
+
+
+#if MPT_OS_WINDOWS
+
+// Create a RFC4122 Version 3 namespace UUID
+inline mpt::UUID UUIDRFC4122NamespaceV3(const mpt::UUID & ns, const mpt::ustring & name) {
+ mpt::UUIDbin binns = ns;
+ std::vector<std::byte> buf;
+ buf.resize(sizeof(mpt::UUIDbin));
+ std::copy(mpt::as_raw_memory(binns).data(), mpt::as_raw_memory(binns).data() + sizeof(mpt::UUIDbin), buf.data());
+ std::string utf8name = mpt::transcode<std::string>(mpt::common_encoding::utf8, name);
+ buf.resize(buf.size() + utf8name.length());
+ std::transform(utf8name.begin(), utf8name.end(), buf.data() + sizeof(mpt::UUIDbin), [](char c) { return mpt::byte_cast<std::byte>(c); });
+ std::array<std::byte, 16> hash = mpt::crypto::hash::MD5().process(mpt::as_span(buf)).result();
+ mpt::UUIDbin uuidbin;
+ std::copy(hash.begin(), hash.begin() + 16, mpt::as_raw_memory(uuidbin).data());
+ mpt::UUID uuid{uuidbin};
+ uuid.MakeRFC4122(3);
+ return uuid;
+}
+
+// Create a RFC4122 Version 5 namespace UUID
+inline mpt::UUID UUIDRFC4122NamespaceV5(const mpt::UUID & ns, const mpt::ustring & name) {
+ mpt::UUIDbin binns = ns;
+ std::vector<std::byte> buf;
+ buf.resize(sizeof(mpt::UUIDbin));
+ std::copy(mpt::as_raw_memory(binns).data(), mpt::as_raw_memory(binns).data() + sizeof(mpt::UUIDbin), buf.data());
+ std::string utf8name = mpt::transcode<std::string>(mpt::common_encoding::utf8, name);
+ buf.resize(buf.size() + utf8name.length());
+ std::transform(utf8name.begin(), utf8name.end(), buf.data() + sizeof(mpt::UUIDbin), [](char c) { return mpt::byte_cast<std::byte>(c); });
+ std::array<std::byte, 20> hash = mpt::crypto::hash::SHA1().process(mpt::as_span(buf)).result();
+ UUIDbin uuidbin;
+ std::copy(hash.begin(), hash.begin() + 16, mpt::as_raw_memory(uuidbin).data());
+ mpt::UUID uuid{uuidbin};
+ uuid.MakeRFC4122(5);
+ return uuid;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+
+} // namespace MPT_INLINE_NS
+} // namespace mpt
+
+
+
+#endif // MPT_UUID_NAMESPACE_UUID_NAMESPACE_HPP
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp
new file mode 100644
index 00000000..feab6041
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/detect_quirks.hpp"
+
+
+
+#if defined(MODPLUG_TRACKER) || defined(LIBOPENMPT_BUILD)
+#include "BuildSettings.h"
+#else
+
+
+
+#include "mpt/base/namespace.hpp"
+
+
+
+#ifndef OPENMPT_NAMESPACE
+#define OPENMPT_NAMESPACE OpenMPT
+#endif
+
+#ifndef OPENMPT_NAMESPACE_BEGIN
+#define OPENMPT_NAMESPACE_BEGIN \
+ namespace OPENMPT_NAMESPACE \
+ { \
+ inline namespace MPT_INLINE_NS \
+ {
+#endif
+#ifndef OPENMPT_NAMESPACE_END
+#define OPENMPT_NAMESPACE_END \
+ } \
+ }
+#endif
+
+
+
+#ifdef __cplusplus
+OPENMPT_NAMESPACE_BEGIN
+namespace mpt
+{
+#ifndef MPT_NO_NAMESPACE
+using namespace ::mpt;
+#endif
+} // namespace mpt
+OPENMPT_NAMESPACE_END
+#endif
+
+
+
+#endif
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp
new file mode 100644
index 00000000..eb177e0a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/endian/floatingpoint.hpp"
+#include "mpt/endian/integer.hpp"
+#include "openmpt/base/Types.hpp"
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using int64le = mpt::packed<int64, mpt::LittleEndian_tag>;
+using int32le = mpt::packed<int32, mpt::LittleEndian_tag>;
+using int16le = mpt::packed<int16, mpt::LittleEndian_tag>;
+using int8le = mpt::packed<int8, mpt::LittleEndian_tag>;
+using uint64le = mpt::packed<uint64, mpt::LittleEndian_tag>;
+using uint32le = mpt::packed<uint32, mpt::LittleEndian_tag>;
+using uint16le = mpt::packed<uint16, mpt::LittleEndian_tag>;
+using uint8le = mpt::packed<uint8, mpt::LittleEndian_tag>;
+
+using int64be = mpt::packed<int64, mpt::BigEndian_tag>;
+using int32be = mpt::packed<int32, mpt::BigEndian_tag>;
+using int16be = mpt::packed<int16, mpt::BigEndian_tag>;
+using int8be = mpt::packed<int8, mpt::BigEndian_tag>;
+using uint64be = mpt::packed<uint64, mpt::BigEndian_tag>;
+using uint32be = mpt::packed<uint32, mpt::BigEndian_tag>;
+using uint16be = mpt::packed<uint16, mpt::BigEndian_tag>;
+using uint8be = mpt::packed<uint8, mpt::BigEndian_tag>;
+
+
+
+using IEEE754binary32LE = mpt::IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32LE;
+using IEEE754binary32BE = mpt::IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32BE;
+using IEEE754binary64LE = mpt::IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64LE;
+using IEEE754binary64BE = mpt::IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64BE;
+
+
+// unaligned
+
+using float32le = mpt::IEEE754binary32EmulatedLE;
+using float32be = mpt::IEEE754binary32EmulatedBE;
+using float64le = mpt::IEEE754binary64EmulatedLE;
+using float64be = mpt::IEEE754binary64EmulatedBE;
+
+
+// potentially aligned
+
+using float32le_fast = mpt::IEEE754binary32LE;
+using float32be_fast = mpt::IEEE754binary32BE;
+using float64le_fast = mpt::IEEE754binary64LE;
+using float64be_fast = mpt::IEEE754binary64BE;
+
+
+
+#define MPT_BINARY_STRUCT(type, size) \
+ constexpr bool declare_binary_safe(const type &) \
+ { \
+ return true; \
+ } \
+ static_assert(mpt::check_binary_size<type>(size)); \
+ /**/
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp
new file mode 100644
index 00000000..3f741e43
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp
@@ -0,0 +1,460 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+/*
+ * Originally based on <http://stackoverflow.com/questions/4226960/type-safer-bitflags-in-c>.
+ * Rewritten to be standard-conforming.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Type-safe wrapper around an enum, that can represent all enum values and bitwise compositions thereof.
+// Conversions to and from plain integers as well as conversions to the base enum are always explicit.
+template <typename enum_t>
+// cppcheck-suppress copyCtorAndEqOperator
+class enum_value_type
+{
+public:
+ using enum_type = enum_t;
+ using value_type = enum_value_type;
+ using store_type = typename std::make_unsigned<enum_t>::type;
+
+private:
+ store_type bits;
+
+public:
+ MPT_CONSTEXPRINLINE enum_value_type() noexcept
+ : bits(0)
+ {
+ }
+ MPT_CONSTEXPRINLINE enum_value_type(const enum_value_type &x) noexcept
+ : bits(x.bits)
+ {
+ }
+ MPT_CONSTEXPRINLINE enum_value_type(enum_type x) noexcept
+ : bits(static_cast<store_type>(x))
+ {
+ }
+
+private:
+ explicit MPT_CONSTEXPRINLINE enum_value_type(store_type x) noexcept
+ : bits(x)
+ {
+ } // private in order to prevent accidental conversions. use from_bits.
+ MPT_CONSTEXPRINLINE operator store_type() const noexcept { return bits; } // private in order to prevent accidental conversions. use as_bits.
+public:
+ static MPT_CONSTEXPRINLINE enum_value_type from_bits(store_type bits) noexcept { return value_type(bits); }
+ MPT_CONSTEXPRINLINE enum_type as_enum() const noexcept { return static_cast<enum_t>(bits); }
+ MPT_CONSTEXPRINLINE store_type as_bits() const noexcept { return bits; }
+
+public:
+ MPT_CONSTEXPRINLINE operator bool() const noexcept { return bits != store_type(); }
+ MPT_CONSTEXPRINLINE bool operator!() const noexcept { return bits == store_type(); }
+
+ MPT_CONSTEXPRINLINE const enum_value_type operator~() const noexcept { return enum_value_type(~bits); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_value_type a, enum_value_type b) noexcept { return a.bits == b.bits; }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_value_type a, enum_value_type b) noexcept { return a.bits != b.bits; }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_value_type a, enum_t b) noexcept { return a == enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_value_type a, enum_t b) noexcept { return a != enum_value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) == b; }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) != b; }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits | b.bits); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits & b.bits); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits ^ b.bits); }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_value_type a, enum_t b) noexcept { return a | enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_value_type a, enum_t b) noexcept { return a & enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_value_type a, enum_t b) noexcept { return a ^ enum_value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) | b; }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) & b; }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) ^ b; }
+
+ MPT_CONSTEXPRINLINE enum_value_type &operator|=(enum_value_type b) noexcept
+ {
+ *this = *this | b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator&=(enum_value_type b) noexcept
+ {
+ *this = *this & b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator^=(enum_value_type b) noexcept
+ {
+ *this = *this ^ b;
+ return *this;
+ }
+
+ MPT_CONSTEXPRINLINE enum_value_type &operator|=(enum_t b) noexcept
+ {
+ *this = *this | b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator&=(enum_t b) noexcept
+ {
+ *this = *this & b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator^=(enum_t b) noexcept
+ {
+ *this = *this ^ b;
+ return *this;
+ }
+};
+
+
+// Type-safe enum wrapper that allows type-safe bitwise testing.
+template <typename enum_t>
+class Enum
+{
+public:
+ using self_type = Enum;
+ using enum_type = enum_t;
+ using value_type = enum_value_type<enum_t>;
+ using store_type = typename value_type::store_type;
+
+private:
+ enum_type value;
+
+public:
+ explicit MPT_CONSTEXPRINLINE Enum(enum_type val) noexcept
+ : value(val)
+ {
+ }
+ MPT_CONSTEXPRINLINE operator enum_type() const noexcept { return value; }
+ MPT_CONSTEXPRINLINE Enum &operator=(enum_type val) noexcept
+ {
+ value = val;
+ return *this;
+ }
+
+public:
+ MPT_CONSTEXPRINLINE const value_type operator~() const { return ~value_type(value); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, value_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, value_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(value_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(value_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, enum_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, enum_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, value_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, value_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, value_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(value_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(value_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(value_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, enum_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, enum_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, enum_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(enum_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(enum_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(enum_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+};
+
+
+template <typename enum_t, typename store_t = typename enum_value_type<enum_t>::store_type>
+// cppcheck-suppress copyCtorAndEqOperator
+class FlagSet
+{
+public:
+ using self_type = FlagSet;
+ using enum_type = enum_t;
+ using value_type = enum_value_type<enum_t>;
+ using store_type = store_t;
+
+private:
+ // support truncated store_type ... :
+ store_type bits_;
+ static MPT_CONSTEXPRINLINE store_type store_from_value(value_type bits) noexcept { return static_cast<store_type>(bits.as_bits()); }
+ static MPT_CONSTEXPRINLINE value_type value_from_store(store_type bits) noexcept { return value_type::from_bits(static_cast<typename value_type::store_type>(bits)); }
+
+ MPT_CONSTEXPRINLINE FlagSet &store(value_type bits) noexcept
+ {
+ bits_ = store_from_value(bits);
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE value_type load() const noexcept { return value_from_store(bits_); }
+
+public:
+ // Default constructor (no flags set)
+ MPT_CONSTEXPRINLINE FlagSet() noexcept
+ : bits_(store_from_value(value_type()))
+ {
+ }
+
+ // Copy constructor
+ MPT_CONSTEXPRINLINE FlagSet(const FlagSet &flags) noexcept
+ : bits_(flags.bits_)
+ {
+ }
+
+ // Value constructor
+ MPT_CONSTEXPRINLINE FlagSet(value_type flags) noexcept
+ : bits_(store_from_value(value_type(flags)))
+ {
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet(enum_type flag) noexcept
+ : bits_(store_from_value(value_type(flag)))
+ {
+ }
+
+ explicit MPT_CONSTEXPRINLINE FlagSet(store_type flags) noexcept
+ : bits_(store_from_value(value_type::from_bits(flags)))
+ {
+ }
+
+ MPT_CONSTEXPRINLINE explicit operator bool() const noexcept
+ {
+ return load();
+ }
+ MPT_CONSTEXPRINLINE explicit operator store_type() const noexcept
+ {
+ return load().as_bits();
+ }
+
+ MPT_CONSTEXPRINLINE value_type value() const noexcept
+ {
+ return load();
+ }
+
+ MPT_CONSTEXPRINLINE operator value_type() const noexcept
+ {
+ return load();
+ }
+
+ // Test if one or more flags are set. Returns true if at least one of the given flags is set.
+ MPT_CONSTEXPRINLINE bool operator[](value_type flags) const noexcept
+ {
+ return test(flags);
+ }
+
+ // Set one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &set(value_type flags) noexcept
+ {
+ return store(load() | flags);
+ }
+
+ // Set or clear one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &set(value_type flags, bool val) noexcept
+ {
+ return store((val ? (load() | flags) : (load() & ~flags)));
+ }
+
+ // Clear or flags.
+ MPT_CONSTEXPRINLINE FlagSet &reset() noexcept
+ {
+ return store(value_type());
+ }
+
+ // Clear one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &reset(value_type flags) noexcept
+ {
+ return store(load() & ~flags);
+ }
+
+ // Toggle all flags.
+ MPT_CONSTEXPRINLINE FlagSet &flip() noexcept
+ {
+ return store(~load());
+ }
+
+ // Toggle one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &flip(value_type flags) noexcept
+ {
+ return store(load() ^ flags);
+ }
+
+ // Returns the size of the flag set in bytes
+ MPT_CONSTEXPRINLINE std::size_t size() const noexcept
+ {
+ return sizeof(store_type);
+ }
+
+ // Returns the size of the flag set in bits
+ MPT_CONSTEXPRINLINE std::size_t size_bits() const noexcept
+ {
+ return size() * 8;
+ }
+
+ // Test if one or more flags are set. Returns true if at least one of the given flags is set.
+ MPT_CONSTEXPRINLINE bool test(value_type flags) const noexcept
+ {
+ return (load() & flags);
+ }
+
+ // Test if all specified flags are set.
+ MPT_CONSTEXPRINLINE bool test_all(value_type flags) const noexcept
+ {
+ return (load() & flags) == flags;
+ }
+
+ // Test if any but the specified flags are set.
+ MPT_CONSTEXPRINLINE bool test_any_except(value_type flags) const noexcept
+ {
+ return (load() & ~flags);
+ }
+
+ // Test if any flag is set.
+ MPT_CONSTEXPRINLINE bool any() const noexcept
+ {
+ return load();
+ }
+
+ // Test if no flags are set.
+ MPT_CONSTEXPRINLINE bool none() const noexcept
+ {
+ return !load();
+ }
+
+ MPT_CONSTEXPRINLINE store_type GetRaw() const noexcept
+ {
+ return bits_;
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &SetRaw(store_type flags) noexcept
+ {
+ bits_ = flags;
+ return *this;
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(value_type flags) noexcept
+ {
+ return store(flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(enum_type flag) noexcept
+ {
+ return store(flag);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(FlagSet flags) noexcept
+ {
+ return store(flags.load());
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator&=(value_type flags) noexcept
+ {
+ return store(load() & flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator|=(value_type flags) noexcept
+ {
+ return store(load() | flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator^=(value_type flags) noexcept
+ {
+ return store(load() ^ flags);
+ }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, self_type b) noexcept { return a.load() == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, self_type b) noexcept { return a.load() != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, value_type b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, value_type b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(value_type a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(value_type a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, enum_type b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, enum_type b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_type a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_type a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, Enum<enum_type> b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, Enum<enum_type> b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(Enum<enum_type> a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(Enum<enum_type> a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, self_type b) noexcept { return a.load() | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, self_type b) noexcept { return a.load() & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, self_type b) noexcept { return a.load() ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, value_type b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, value_type b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, value_type b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(value_type a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(value_type a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(value_type a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, enum_type b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, enum_type b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, enum_type b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(enum_type a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(enum_type a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(enum_type a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, Enum<enum_type> b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, Enum<enum_type> b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, Enum<enum_type> b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(Enum<enum_type> a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(Enum<enum_type> a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(Enum<enum_type> a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+};
+
+
+// Declare typesafe logical operators for enum_t
+#define MPT_DECLARE_ENUM(enum_t) \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator|(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) | enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator&(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) & enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator^(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) ^ enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator~(enum_t a) noexcept \
+ { \
+ return ~enum_value_type<enum_t>(a); \
+ } \
+/**/
+
+// backwards compatibility
+#define DECLARE_FLAGSET MPT_DECLARE_ENUM
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp
new file mode 100644
index 00000000..6fcbb707
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/endian/int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <limits>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using uint24 = mpt::uint24;
+static_assert(sizeof(uint24) == 3);
+inline constexpr uint32 uint24_min = std::numeric_limits<uint24>::min();
+inline constexpr uint32 uint24_max = std::numeric_limits<uint24>::max();
+using int24 = mpt::int24;
+static_assert(sizeof(int24) == 3);
+inline constexpr int32 int24_min = std::numeric_limits<int24>::min();
+inline constexpr int32 int24_max = std::numeric_limits<int24>::max();
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp
new file mode 100644
index 00000000..b4def28a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/floatingpoint.hpp"
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using int8 = mpt::int8;
+using int16 = mpt::int16;
+using int32 = mpt::int32;
+using int64 = mpt::int64;
+using uint8 = mpt::uint8;
+using uint16 = mpt::uint16;
+using uint32 = mpt::uint32;
+using uint64 = mpt::uint64;
+
+using nativefloat = mpt::nativefloat;
+using float32 = mpt::float32;
+using float64 = mpt::float64;
+using namespace mpt::float_literals;
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp
new file mode 100644
index 00000000..af5f0741
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/source_location.hpp"
+#include "mpt/string/types.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+enum LogLevel
+{
+ LogDebug = 5,
+ LogInformation = 4,
+ LogNotification = 3,
+ LogWarning = 2,
+ LogError = 1
+};
+
+class ILogger
+{
+protected:
+ virtual ~ILogger() = default;
+
+public:
+ virtual bool IsLevelActive(LogLevel level) const noexcept = 0;
+ // facility: ASCII
+ virtual bool IsFacilityActive(const char *facility) const noexcept = 0;
+ // facility: ASCII
+ virtual void SendLogMessage(const mpt::source_location &loc, LogLevel level, const char *facility, const mpt::ustring &text) const = 0;
+};
+
+#define MPT_LOG(logger, level, facility, text) \
+ do \
+ { \
+ if((logger).IsLevelActive((level))) \
+ { \
+ if((logger).IsFacilityActive((facility))) \
+ { \
+ (logger).SendLogMessage(MPT_SOURCE_LOCATION_CURRENT(), (level), (facility), (text)); \
+ } \
+ } \
+ } while(0)
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp
new file mode 100644
index 00000000..c075f9cf
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <limits>
+#include <type_traits>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace mpt
+{
+
+
+namespace rng
+{
+
+
+template <typename Tstate, typename Tvalue, Tstate x1, Tstate x2, Tstate x3, Tstate x4, int rol1, int rol2>
+class modplug
+{
+public:
+ typedef Tstate state_type;
+ typedef Tvalue result_type;
+
+private:
+ state_type state1;
+ state_type state2;
+
+public:
+ template <typename Trng>
+ explicit inline modplug(Trng &rd)
+ : state1(mpt::random<state_type>(rd))
+ , state2(mpt::random<state_type>(rd))
+ {
+ }
+ explicit inline modplug(state_type seed1, state_type seed2)
+ : state1(seed1)
+ , state2(seed2)
+ {
+ }
+
+public:
+ static MPT_CONSTEXPRINLINE result_type min()
+ {
+ return static_cast<result_type>(0);
+ }
+ static MPT_CONSTEXPRINLINE result_type max()
+ {
+ return std::numeric_limits<result_type>::max();
+ }
+ static MPT_CONSTEXPRINLINE int result_bits()
+ {
+ static_assert(std::is_integral<result_type>::value);
+ static_assert(std::is_unsigned<result_type>::value);
+ return std::numeric_limits<result_type>::digits;
+ }
+ inline result_type operator()()
+ {
+ state_type a = state1;
+ state_type b = state2;
+ a = mpt::rotl(a, rol1);
+ a ^= x1;
+ a += x2 + (b * x3);
+ b += mpt::rotl(a, rol2) * x4;
+ state1 = a;
+ state2 = b;
+ result_type result = static_cast<result_type>(b);
+ return result;
+ }
+};
+
+typedef modplug<uint32, uint32, 0x10204080u, 0x78649E7Du, 4, 5, 1, 16> modplug_dither;
+
+
+} // namespace rng
+
+
+} // namespace mpt
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp
new file mode 100644
index 00000000..cf5995b2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/audio/span.hpp"
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+
+#include <cassert>
+#include <cstddef>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudio(TBufOut buf_out, TBufIn buf_in)
+{
+ assert(buf_in.size_frames() == buf_out.size_frames());
+ assert(buf_in.size_channels() == buf_out.size_channels());
+ std::size_t countFrames = std::min(buf_in.size_frames(), buf_out.size_frames());
+ std::size_t channels = std::min(buf_in.size_channels(), buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudio(TBufOut buf_out, TBufIn buf_in, std::size_t countFrames)
+{
+ assert(countFrames <= buf_in.size_frames());
+ assert(countFrames <= buf_out.size_frames());
+ assert(buf_in.size_channels() == buf_out.size_channels());
+ std::size_t channels = std::min(buf_in.size_channels(), buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudioChannels(TBufOut buf_out, TBufIn buf_in, std::size_t channels, std::size_t countFrames)
+{
+ assert(countFrames <= buf_in.size_frames());
+ assert(countFrames <= buf_out.size_frames());
+ assert(channels <= buf_in.size_channels());
+ assert(channels <= buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+// Copy numChannels interleaved sample streams.
+template <typename Tin, typename Tout>
+void CopyAudioChannelsInterleaved(Tout *MPT_RESTRICT outBuf, const Tin *MPT_RESTRICT inBuf, std::size_t numChannels, std::size_t countFrames)
+{
+ CopyAudio(mpt::audio_span_interleaved<Tout>(outBuf, numChannels, countFrames), mpt::audio_span_interleaved<const Tin>(inBuf, numChannels, countFrames));
+}
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp
new file mode 100644
index 00000000..1dd8de37
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+
+#include "mpt/audio/span.hpp"
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/SampleClip.hpp"
+#include "openmpt/soundbase/SampleClipFixedPoint.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+#include "openmpt/soundbase/SampleConvertFixedPoint.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+template <int fractionalBits, bool clipOutput, typename TOutBuf, typename TInBuf, typename Tdither>
+void ConvertBufferMixInternalFixedToBuffer(TOutBuf outBuf, TInBuf inBuf, Tdither &dither, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ constexpr int ditherBits = SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).IsInt()
+ ? SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).GetBitsPerSample()
+ : 0;
+ SC::ClipFixed<int32, fractionalBits, clipOutput> clip;
+ SC::ConvertFixedPoint<TOutSample, TInSample, fractionalBits> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(clip(dither.template process<ditherBits>(channel, inBuf(channel, i))));
+ }
+ }
+}
+
+
+template <int fractionalBits, typename TOutBuf, typename TInBuf>
+void ConvertBufferToBufferMixInternalFixed(TOutBuf outBuf, TInBuf inBuf, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ SC::ConvertToFixedPoint<TOutSample, TInSample, fractionalBits> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(inBuf(channel, i));
+ }
+ }
+}
+
+
+template <bool clipOutput, typename TOutBuf, typename TInBuf, typename Tdither>
+void ConvertBufferMixInternalToBuffer(TOutBuf outBuf, TInBuf inBuf, Tdither &dither, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ constexpr int ditherBits = SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).IsInt()
+ ? SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).GetBitsPerSample()
+ : 0;
+ SC::Clip<TInSample, clipOutput> clip;
+ SC::Convert<TOutSample, TInSample> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(clip(dither.template process<ditherBits>(channel, inBuf(channel, i))));
+ }
+ }
+}
+
+
+template <typename TOutBuf, typename TInBuf>
+void ConvertBufferToBufferMixInternal(TOutBuf outBuf, TInBuf inBuf, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ SC::Convert<TOutSample, TInSample> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(inBuf(channel, i));
+ }
+ }
+}
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp
new file mode 100644
index 00000000..2ba7153c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/engine.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+
+#include <vector>
+#include <variant>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tdither>
+class MultiChannelDither
+{
+private:
+ std::vector<Tdither> DitherChannels;
+ typename Tdither::prng_type prng;
+
+public:
+ template <typename Trd>
+ MultiChannelDither(Trd &rd, std::size_t channels)
+ : DitherChannels(channels)
+ , prng(Tdither::prng_init(rd))
+ {
+ return;
+ }
+ void Reset()
+ {
+ for(std::size_t channel = 0; channel < DitherChannels.size(); ++channel)
+ {
+ DitherChannels[channel] = Tdither{};
+ }
+ }
+ std::size_t GetChannels() const
+ {
+ return DitherChannels.size();
+ }
+ template <uint32 targetbits>
+ MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample)
+ {
+ return DitherChannels[channel].template process<targetbits>(sample, prng);
+ }
+ template <uint32 targetbits>
+ MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample)
+ {
+ return DitherChannels[channel].template process<targetbits>(sample, prng);
+ }
+};
+
+
+template <typename AvailableDithers, typename DitherNames, std::size_t defaultChannels, std::size_t defaultDither, std::size_t noDither, typename seeding_random_engine = mpt::good_engine>
+class Dithers
+ : public DitherNames
+{
+
+public:
+ static constexpr std::size_t NoDither = noDither;
+ static constexpr std::size_t DefaultDither = defaultDither;
+ static constexpr std::size_t DefaultChannels = defaultChannels;
+
+private:
+ seeding_random_engine m_PRNG;
+ AvailableDithers m_Dithers;
+
+public:
+ template <typename Trd>
+ Dithers(Trd &rd, std::size_t mode = defaultDither, std::size_t channels = defaultChannels)
+ : m_PRNG(mpt::make_prng<seeding_random_engine>(rd))
+ , m_Dithers(std::in_place_index<defaultDither>, m_PRNG, channels)
+ {
+ SetMode(mode, channels);
+ }
+
+ AvailableDithers &Variant()
+ {
+ return m_Dithers;
+ }
+
+ static std::size_t GetNumDithers()
+ {
+ return std::variant_size<AvailableDithers>::value;
+ }
+
+ static std::size_t GetDefaultDither()
+ {
+ return defaultDither;
+ }
+
+ static std::size_t GetNoDither()
+ {
+ return noDither;
+ }
+
+private:
+ template <std::size_t i = 0>
+ void set_mode(std::size_t mode, std::size_t channels)
+ {
+ if constexpr(i < std::variant_size<AvailableDithers>::value)
+ {
+ if(mode == i)
+ {
+ m_Dithers.template emplace<i>(m_PRNG, channels);
+ } else
+ {
+ this->template set_mode<i + 1>(mode, channels);
+ }
+ } else
+ {
+ MPT_UNUSED(mode);
+ m_Dithers.template emplace<defaultDither>(m_PRNG, channels);
+ }
+ }
+
+public:
+ void SetMode(std::size_t mode, std::size_t channels)
+ {
+ if((mode == GetMode()) && (channels == GetChannels()))
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ return;
+ }
+ set_mode(mode, channels);
+ }
+ void SetMode(std::size_t mode)
+ {
+ if(mode == GetMode())
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ return;
+ }
+ set_mode(mode, GetChannels());
+ }
+ void SetChannels(std::size_t channels)
+ {
+ if(channels == GetChannels())
+ {
+ return;
+ }
+ set_mode(GetMode(), channels);
+ }
+ void Reset()
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ }
+ std::size_t GetMode() const
+ {
+ return m_Dithers.index();
+ }
+ std::size_t GetChannels() const
+ {
+ return std::visit([](auto &dither)
+ { return dither.GetChannels(); },
+ m_Dithers);
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp
new file mode 100644
index 00000000..fcca4f45
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/random/ModPlug.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/MixSampleConvert.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+struct Dither_ModPlug
+{
+public:
+ using prng_type = mpt::rng::modplug_dither;
+ template <typename Trd>
+ static prng_type prng_init(Trd &)
+ {
+ return prng_type{0, 0};
+ }
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &rng)
+ {
+ if constexpr(targetbits == 0)
+ {
+ MPT_UNREFERENCED_PARAMETER(rng);
+ return sample;
+ } else if constexpr(targetbits + MixSampleIntTraits::mix_headroom_bits + 1 >= 32)
+ {
+ MPT_UNREFERENCED_PARAMETER(rng);
+ return sample;
+ } else
+ {
+ sample += mpt::rshift_signed(static_cast<int32>(mpt::random<uint32>(rng)), (targetbits + MixSampleIntTraits::mix_headroom_bits + 1));
+ return sample;
+ }
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng)
+ {
+ return mix_sample_cast<MixSampleFloat>(process<targetbits>(mix_sample_cast<MixSampleInt>(sample), prng));
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp
new file mode 100644
index 00000000..98700c04
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+struct Dither_None
+{
+public:
+ using prng_type = struct
+ {
+ };
+ template <typename Trd>
+ static prng_type prng_init(Trd &)
+ {
+ return prng_type{};
+ }
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &)
+ {
+ return sample;
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &)
+ {
+ return sample;
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp
new file mode 100644
index 00000000..5a60ef52
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/random/engine.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/MixSampleConvert.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <int ditherdepth = 1, bool triangular = false, bool shaped = true>
+struct Dither_SimpleImpl
+{
+public:
+ using prng_type = mpt::fast_engine;
+ template <typename Trd>
+ static prng_type prng_init(Trd &rd)
+ {
+ return mpt::make_prng<prng_type>(rd);
+ }
+
+private:
+ int32 error = 0;
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &prng)
+ {
+ if constexpr(targetbits == 0)
+ {
+ MPT_UNREFERENCED_PARAMETER(prng);
+ return sample;
+ } else
+ {
+ static_assert(sizeof(MixSampleInt) == 4);
+ constexpr int rshift = (32 - targetbits) - MixSampleIntTraits::mix_headroom_bits;
+ if constexpr(rshift <= 1)
+ {
+ MPT_UNREFERENCED_PARAMETER(prng);
+ // nothing to dither
+ return sample;
+ } else
+ {
+ constexpr int rshiftpositive = (rshift > 1) ? rshift : 1; // work-around warnings about negative shift with C++14 compilers
+ constexpr int round_mask = ~((1 << rshiftpositive) - 1);
+ constexpr int round_offset = 1 << (rshiftpositive - 1);
+ constexpr int noise_bits = rshiftpositive + (ditherdepth - 1);
+ constexpr int noise_bias = (1 << (noise_bits - 1));
+ int32 e = error;
+ unsigned int unoise = 0;
+ if constexpr(triangular)
+ {
+ unoise = (mpt::random<unsigned int>(prng, noise_bits) + mpt::random<unsigned int>(prng, noise_bits)) >> 1;
+ } else
+ {
+ unoise = mpt::random<unsigned int>(prng, noise_bits);
+ }
+ int noise = static_cast<int>(unoise) - noise_bias; // un-bias
+ int val = sample;
+ if constexpr(shaped)
+ {
+ val += (e >> 1);
+ }
+ int rounded = (val + noise + round_offset) & round_mask;
+ e = val - rounded;
+ sample = rounded;
+ error = e;
+ return sample;
+ }
+ }
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng)
+ {
+ return mix_sample_cast<MixSampleFloat>(process<targetbits>(mix_sample_cast<MixSampleInt>(sample), prng));
+ }
+};
+
+using Dither_Simple = Dither_SimpleImpl<>;
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp
new file mode 100644
index 00000000..2188d3b2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/audio/sample.hpp"
+#include "mpt/base/floatingpoint.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tsample, std::size_t MIX_HEADROOM_BITS, std::size_t FILTER_HEADROOM_BITS>
+struct FixedPointSampleTraits
+{
+ static_assert(std::is_integral<Tsample>::value);
+ static_assert(std::is_signed<Tsample>::value);
+ static_assert((sizeof(Tsample) * 8u) - 1 > MIX_HEADROOM_BITS);
+ static_assert((sizeof(Tsample) * 8u) - 1 > FILTER_HEADROOM_BITS);
+ using sample_type = Tsample;
+ enum class sample_type_strong : sample_type
+ {
+ };
+ static constexpr int mix_headroom_bits = static_cast<int>(MIX_HEADROOM_BITS);
+ static constexpr int mix_precision_bits = static_cast<int>((sizeof(Tsample) * 8) - MIX_HEADROOM_BITS); // including sign bit
+ static constexpr int mix_fractional_bits = static_cast<int>((sizeof(Tsample) * 8) - 1 - MIX_HEADROOM_BITS); // excluding sign bit
+ static constexpr sample_type mix_clip_max = ((sample_type(1) << mix_fractional_bits) - sample_type(1));
+ static constexpr sample_type mix_clip_min = -((sample_type(1) << mix_fractional_bits) - sample_type(1));
+ static constexpr int filter_headroom_bits = static_cast<int>(FILTER_HEADROOM_BITS);
+ static constexpr int filter_precision_bits = static_cast<int>((sizeof(Tsample) * 8) - FILTER_HEADROOM_BITS); // including sign bit
+ static constexpr int filter_fractional_bits = static_cast<int>((sizeof(Tsample) * 8) - 1 - FILTER_HEADROOM_BITS); // excluding sign bit
+ template <typename Tfloat>
+ static constexpr Tfloat mix_scale = static_cast<Tfloat>(sample_type(1) << mix_fractional_bits);
+};
+
+using MixSampleIntTraits = FixedPointSampleTraits<int32, 4, 8>;
+
+using MixSampleInt = MixSampleIntTraits::sample_type;
+using MixSampleFloat = mpt::audio_sample_float;
+
+using MixSample = std::conditional<mpt::float_traits<nativefloat>::is_hard, MixSampleFloat, MixSampleInt>::type;
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp
new file mode 100644
index 00000000..f34f3e06
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+#include "openmpt/soundbase/SampleConvertFixedPoint.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tdst, typename Tsrc>
+struct ConvertMixSample;
+
+template <>
+struct ConvertMixSample<MixSampleInt, MixSampleInt>
+{
+ MPT_FORCEINLINE MixSampleInt conv(MixSampleInt src)
+ {
+ return src;
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleFloat, MixSampleFloat>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(MixSampleFloat src)
+ {
+ return src;
+ }
+};
+
+template <typename Tsrc>
+struct ConvertMixSample<MixSampleInt, Tsrc>
+{
+ MPT_FORCEINLINE MixSampleInt conv(Tsrc src)
+ {
+ return SC::ConvertToFixedPoint<MixSampleInt, Tsrc, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <typename Tdst>
+struct ConvertMixSample<Tdst, MixSampleInt>
+{
+ MPT_FORCEINLINE Tdst conv(MixSampleInt src)
+ {
+ return SC::ConvertFixedPoint<Tdst, MixSampleInt, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <typename Tsrc>
+struct ConvertMixSample<MixSampleFloat, Tsrc>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(Tsrc src)
+ {
+ return SC::Convert<MixSampleFloat, Tsrc>{}(src);
+ }
+};
+
+template <typename Tdst>
+struct ConvertMixSample<Tdst, MixSampleFloat>
+{
+ MPT_FORCEINLINE Tdst conv(MixSampleFloat src)
+ {
+ return SC::Convert<Tdst, MixSampleFloat>{}(src);
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleInt, MixSampleFloat>
+{
+ MPT_FORCEINLINE MixSampleInt conv(MixSampleFloat src)
+ {
+ return SC::ConvertToFixedPoint<MixSampleInt, MixSampleFloat, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleFloat, MixSampleInt>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(MixSampleInt src)
+ {
+ return SC::ConvertFixedPoint<MixSampleFloat, MixSampleInt, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+
+template <typename Tdst, typename Tsrc>
+MPT_FORCEINLINE Tdst mix_sample_cast(Tsrc src)
+{
+ return ConvertMixSample<Tdst, Tsrc>{}.conv(src);
+}
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp
new file mode 100644
index 00000000..733d61de
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tsample, bool clipOutput>
+struct Clip;
+
+template <bool clipOutput>
+struct Clip<uint8, clipOutput>
+{
+ using input_t = uint8;
+ using output_t = uint8;
+ MPT_FORCEINLINE uint8 operator()(uint8 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int8, clipOutput>
+{
+ using input_t = int8;
+ using output_t = int8;
+ MPT_FORCEINLINE int8 operator()(int8 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int16, clipOutput>
+{
+ using input_t = int16;
+ using output_t = int16;
+ MPT_FORCEINLINE int16 operator()(int16 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int24, clipOutput>
+{
+ using input_t = int24;
+ using output_t = int24;
+ MPT_FORCEINLINE int24 operator()(int24 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int32, clipOutput>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE int32 operator()(int32 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int64, clipOutput>
+{
+ using input_t = int64;
+ using output_t = int64;
+ MPT_FORCEINLINE int64 operator()(int64 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<float, clipOutput>
+{
+ using input_t = float;
+ using output_t = float;
+ MPT_FORCEINLINE float operator()(float val)
+ {
+ if constexpr(clipOutput)
+ {
+ if(val < -1.0f) val = -1.0f;
+ if(val > 1.0f) val = 1.0f;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+template <bool clipOutput>
+struct Clip<double, clipOutput>
+{
+ using input_t = double;
+ using output_t = double;
+ MPT_FORCEINLINE double operator()(double val)
+ {
+ if constexpr(clipOutput)
+ {
+ if(val < -1.0) val = -1.0;
+ if(val > 1.0) val = 1.0;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp
new file mode 100644
index 00000000..fdf51dd5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tfixed, int fractionalBits, bool clipOutput>
+struct ClipFixed
+{
+ using input_t = Tfixed;
+ using output_t = Tfixed;
+ MPT_FORCEINLINE Tfixed operator()(Tfixed val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ if constexpr(clipOutput)
+ {
+ constexpr Tfixed clip_max = (Tfixed(1) << fractionalBits) - Tfixed(1);
+ constexpr Tfixed clip_min = Tfixed(0) - (Tfixed(1) << fractionalBits);
+ if(val < clip_min) val = clip_min;
+ if(val > clip_max) val = clip_max;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp
new file mode 100644
index 00000000..d10e25b1
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp
@@ -0,0 +1,754 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <type_traits>
+
+#include <cmath>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+
+#if MPT_COMPILER_MSVC
+template <typename Tfloat>
+MPT_FORCEINLINE Tfloat fastround(Tfloat x)
+{
+ static_assert(std::is_floating_point<Tfloat>::value);
+ return std::floor(x + static_cast<Tfloat>(0.5));
+}
+#else
+template <typename Tfloat>
+MPT_FORCEINLINE Tfloat fastround(Tfloat x)
+{
+ static_assert(std::is_floating_point<Tfloat>::value);
+ return mpt::round(x);
+}
+#endif
+
+
+
+// Shift input_t down by shift and saturate to output_t.
+template <typename Tdst, typename Tsrc, int shift>
+struct ConvertShift
+{
+ using input_t = Tsrc;
+ using output_t = Tdst;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::saturate_cast<output_t>(mpt::rshift_signed(val, shift));
+ }
+};
+
+
+
+// Shift input_t up by shift and saturate to output_t.
+template <typename Tdst, typename Tsrc, int shift>
+struct ConvertShiftUp
+{
+ using input_t = Tsrc;
+ using output_t = Tdst;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::saturate_cast<output_t>(mpt::lshift_signed(val, shift));
+ }
+};
+
+
+
+
+// Every sample conversion functor has to typedef its input_t and output_t.
+// The input_t argument is taken by value because we only deal with per-single-sample conversions here.
+
+
+// straight forward type conversions, clamping when converting from floating point.
+template <typename Tdst, typename Tsrc>
+struct Convert;
+
+template <typename Tid>
+struct Convert<Tid, Tid>
+{
+ using input_t = Tid;
+ using output_t = Tid;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val;
+ }
+};
+
+template <>
+struct Convert<uint8, int8>
+{
+ using input_t = int8;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(val + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int16>
+{
+ using input_t = int16;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 8)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int24>
+{
+ using input_t = int24;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(static_cast<int>(val), 16)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int32>
+{
+ using input_t = int32;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 24)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int64>
+{
+ using input_t = int64;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 56)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, float32>
+{
+ using input_t = float32;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 128.0f;
+ return static_cast<uint8>(mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, double>
+{
+ using input_t = double;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 128.0;
+ return static_cast<uint8>(mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))) + 0x80);
+ }
+};
+
+template <>
+struct Convert<int8, uint8>
+{
+ using input_t = uint8;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(static_cast<int>(val) - 0x80);
+ }
+};
+
+template <>
+struct Convert<int8, int16>
+{
+ using input_t = int16;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int8, int24>
+{
+ using input_t = int24;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(static_cast<int>(val), 16));
+ }
+};
+
+template <>
+struct Convert<int8, int32>
+{
+ using input_t = int32;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 24));
+ }
+};
+
+template <>
+struct Convert<int8, int64>
+{
+ using input_t = int64;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 56));
+ }
+};
+
+template <>
+struct Convert<int8, float32>
+{
+ using input_t = float32;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 128.0f;
+ return mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int8, double>
+{
+ using input_t = double;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 128.0;
+ return mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int16, uint8>
+{
+ using input_t = uint8;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 8));
+ }
+};
+
+template <>
+struct Convert<int16, int8>
+{
+ using input_t = int8;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::lshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int16, int24>
+{
+ using input_t = int24;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(static_cast<int>(val), 8));
+ }
+};
+
+template <>
+struct Convert<int16, int32>
+{
+ using input_t = int32;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int16, int64>
+{
+ using input_t = int64;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(val, 48));
+ }
+};
+
+template <>
+struct Convert<int16, float32>
+{
+ using input_t = float32;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 32768.0f;
+ return mpt::saturate_cast<int16>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int16, double>
+{
+ using input_t = double;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 32768.0;
+ return mpt::saturate_cast<int16>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int24, uint8>
+{
+ using input_t = uint8;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 16));
+ }
+};
+
+template <>
+struct Convert<int24, int8>
+{
+ using input_t = int8;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int24, int16>
+{
+ using input_t = int16;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int24, int32>
+{
+ using input_t = int32;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::rshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int24, int64>
+{
+ using input_t = int64;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::rshift_signed(val, 40));
+ }
+};
+
+template <>
+struct Convert<int24, float32>
+{
+ using input_t = float32;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 2147483648.0f;
+ return static_cast<int24>(mpt::rshift_signed(mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))), 8));
+ }
+};
+
+template <>
+struct Convert<int24, double>
+{
+ using input_t = double;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 2147483648.0;
+ return static_cast<int24>(mpt::rshift_signed(mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))), 8));
+ }
+};
+
+template <>
+struct Convert<int32, uint8>
+{
+ using input_t = uint8;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 24));
+ }
+};
+
+template <>
+struct Convert<int32, int8>
+{
+ using input_t = int8;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(val, 24));
+ }
+};
+
+template <>
+struct Convert<int32, int16>
+{
+ using input_t = int16;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int32, int24>
+{
+ using input_t = int24;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(static_cast<int>(val), 8));
+ }
+};
+
+template <>
+struct Convert<int32, int64>
+{
+ using input_t = int64;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::rshift_signed(val, 32));
+ }
+};
+
+template <>
+struct Convert<int32, float32>
+{
+ using input_t = float32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 2147483648.0f;
+ return mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int32, double>
+{
+ using input_t = double;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 2147483648.0;
+ return mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int64, uint8>
+{
+ using input_t = uint8;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val) - 0x80, 56);
+ }
+};
+
+template <>
+struct Convert<int64, int8>
+{
+ using input_t = int8;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 56);
+ }
+};
+
+template <>
+struct Convert<int64, int16>
+{
+ using input_t = int16;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 48);
+ }
+};
+
+template <>
+struct Convert<int64, int24>
+{
+ using input_t = int24;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 40);
+ }
+};
+
+template <>
+struct Convert<int64, int32>
+{
+ using input_t = int32;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 32);
+ }
+};
+
+template <>
+struct Convert<int64, float32>
+{
+ using input_t = float32;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= static_cast<float>(uint64(1) << 63);
+ return mpt::saturate_cast<int64>(SC::fastround(val));
+ }
+};
+
+template <>
+struct Convert<int64, double>
+{
+ using input_t = double;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= static_cast<double>(uint64(1) << 63);
+ return mpt::saturate_cast<int64>(SC::fastround(val));
+ }
+};
+
+template <>
+struct Convert<float32, uint8>
+{
+ using input_t = uint8;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return (static_cast<int>(val) - 0x80) * (1.0f / static_cast<float32>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<float32, int8>
+{
+ using input_t = int8;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<float32, int16>
+{
+ using input_t = int16;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 15));
+ }
+};
+
+template <>
+struct Convert<float32, int24>
+{
+ using input_t = int24;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 23));
+ }
+};
+
+template <>
+struct Convert<float32, int32>
+{
+ using input_t = int32;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 31));
+ }
+};
+
+template <>
+struct Convert<float32, int64>
+{
+ using input_t = int64;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<uint64>(1) << 63));
+ }
+};
+
+template <>
+struct Convert<double, uint8>
+{
+ using input_t = uint8;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return (static_cast<int>(val) - 0x80) * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<double, int8>
+{
+ using input_t = int8;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<double, int16>
+{
+ using input_t = int16;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 15));
+ }
+};
+
+template <>
+struct Convert<double, int24>
+{
+ using input_t = int24;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 23));
+ }
+};
+
+template <>
+struct Convert<double, int32>
+{
+ using input_t = int32;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 31));
+ }
+};
+
+template <>
+struct Convert<double, int64>
+{
+ using input_t = int64;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<uint64>(1) << 63));
+ }
+};
+
+template <>
+struct Convert<double, float>
+{
+ using input_t = float;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<double>(val);
+ }
+};
+
+template <>
+struct Convert<float, double>
+{
+ using input_t = double;
+ using output_t = float;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<float>(val);
+ }
+};
+
+
+
+template <typename Tdst, typename Tsrc>
+MPT_FORCEINLINE Tdst sample_cast(Tsrc src)
+{
+ return SC::Convert<Tdst, Tsrc>{}(src);
+}
+
+
+
+} // namespace SC
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp
new file mode 100644
index 00000000..65b5d912
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+#include <limits>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tdst, typename Tsrc, int fractionalBits>
+struct ConvertFixedPoint;
+
+template <int fractionalBits>
+struct ConvertFixedPoint<uint8, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = uint8;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int8>::min()) val = std::numeric_limits<int8>::min();
+ if(val > std::numeric_limits<int8>::max()) val = std::numeric_limits<int8>::max();
+ return static_cast<uint8>(val + 0x80); // unsigned
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int8, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int8;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int8>::min()) val = std::numeric_limits<int8>::min();
+ if(val > std::numeric_limits<int8>::max()) val = std::numeric_limits<int8>::max();
+ return static_cast<int8>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int16, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int16;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int16>::min()) val = std::numeric_limits<int16>::min();
+ if(val > std::numeric_limits<int16>::max()) val = std::numeric_limits<int16>::max();
+ return static_cast<int16>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int24, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int24;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int24>::min()) val = std::numeric_limits<int24>::min();
+ if(val > std::numeric_limits<int24>::max()) val = std::numeric_limits<int24>::max();
+ return static_cast<int24>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return static_cast<int32>(std::clamp(val, static_cast<int32>(-((1 << fractionalBits) - 1)), static_cast<int32>(1 << fractionalBits) - 1)) << (sizeof(input_t) * 8 - 1 - fractionalBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<float32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = float32;
+ const float factor;
+ MPT_FORCEINLINE ConvertFixedPoint()
+ : factor(1.0f / static_cast<float>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return val * factor;
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<float64, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = float64;
+ const double factor;
+ MPT_FORCEINLINE ConvertFixedPoint()
+ : factor(1.0 / static_cast<double>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return val * factor;
+ }
+};
+
+
+template <typename Tdst, typename Tsrc, int fractionalBits>
+struct ConvertToFixedPoint;
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, uint8, fractionalBits>
+{
+ using input_t = uint8;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(static_cast<int>(val) - 0x80), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int8, fractionalBits>
+{
+ using input_t = int8;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int16, fractionalBits>
+{
+ using input_t = int16;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int24, fractionalBits>
+{
+ using input_t = int24;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ return mpt::rshift_signed(static_cast<output_t>(val), (sizeof(input_t) * 8 - 1 - fractionalBits));
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, float32, fractionalBits>
+{
+ using input_t = float32;
+ using output_t = int32;
+ const float factor;
+ MPT_FORCEINLINE ConvertToFixedPoint()
+ : factor(static_cast<float>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ val = mpt::sanitize_nan(val);
+ return mpt::saturate_cast<output_t>(SC::fastround(val * factor));
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, float64, fractionalBits>
+{
+ using input_t = float64;
+ using output_t = int32;
+ const double factor;
+ MPT_FORCEINLINE ConvertToFixedPoint()
+ : factor(static_cast<double>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ val = mpt::sanitize_nan(val);
+ return mpt::saturate_cast<output_t>(SC::fastround(val * factor));
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp
new file mode 100644
index 00000000..164004b0
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/floatingpoint.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "openmpt/base/Endian.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <algorithm>
+
+#include <cmath>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Byte offsets, from lowest significant to highest significant byte (for various functor template parameters)
+#define littleEndian64 0, 1, 2, 3, 4, 5, 6, 7
+#define littleEndian32 0, 1, 2, 3
+#define littleEndian24 0, 1, 2
+#define littleEndian16 0, 1
+
+#define bigEndian64 7, 6, 5, 4, 3, 2, 1, 0
+#define bigEndian32 3, 2, 1, 0
+#define bigEndian24 2, 1, 0
+#define bigEndian16 1, 0
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+// Every sample decoding functor has to typedef its input_t and output_t
+// and has to provide a static constexpr input_inc member
+// which describes by how many input_t elements inBuf has to be incremented between invocations.
+// input_inc is normally 1 except when decoding e.g. bigger sample values
+// from multiple std::byte values.
+
+
+struct DecodeInt8
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return mpt::byte_cast<int8>(*inBuf);
+ }
+};
+
+struct DecodeUint8
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return static_cast<int8>(static_cast<int>(mpt::byte_cast<uint8>(*inBuf)) - 128);
+ }
+};
+
+struct DecodeInt8Delta
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ uint8 delta;
+ DecodeInt8Delta()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(*inBuf);
+ return static_cast<int8>(delta);
+ }
+};
+
+struct DecodeInt16uLaw
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 1;
+ // clang-format off
+ static constexpr std::array<int16, 256> uLawTable =
+ {
+ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+ -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+ -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+ -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, -1,
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+ };
+ // clang-format on
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return uLawTable[mpt::byte_cast<uint8>(*inBuf)];
+ }
+};
+
+struct DecodeInt16ALaw
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 1;
+ // clang-format off
+ static constexpr std::array<int16, 256> ALawTable =
+ {
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+ -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+ -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+ -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+ -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+ -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+ -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
+ -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+ -344, -328, -376, -360, -280, -264, -312, -296,
+ -472, -456, -504, -488, -408, -392, -440, -424,
+ -88, -72, -120, -104, -24, -8, -56, -40,
+ -216, -200, -248, -232, -152, -136, -184, -168,
+ -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+ -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624, -592,
+ -944, -912, -1008, -976, -816, -784, -880, -848,
+ 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
+ 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
+ 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
+ 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
+ 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+ 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
+ 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+ 344, 328, 376, 360, 280, 264, 312, 296,
+ 472, 456, 504, 488, 408, 392, 440, 424,
+ 88, 72, 120, 104, 24, 8, 56, 40,
+ 216, 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
+ 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
+ 688, 656, 752, 720, 560, 528, 624, 592,
+ 944, 912, 1008, 976, 816, 784, 880, 848
+ };
+ // clang-format on
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return ALawTable[mpt::byte_cast<uint8>(*inBuf)];
+ }
+};
+
+template <uint16 offset, std::size_t loByteIndex, std::size_t hiByteIndex>
+struct DecodeInt16
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (mpt::byte_cast<uint8>(inBuf[loByteIndex]) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 8)) - offset;
+ }
+};
+
+template <std::size_t loByteIndex, std::size_t hiByteIndex>
+struct DecodeInt16Delta
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ uint16 delta;
+ DecodeInt16Delta()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(inBuf[loByteIndex]) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 8);
+ return static_cast<int16>(delta);
+ }
+};
+
+struct DecodeInt16Delta8
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ uint16 delta;
+ DecodeInt16Delta8()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(inBuf[0]);
+ int16 result = delta & 0xFF;
+ delta += mpt::byte_cast<uint8>(inBuf[1]);
+ result |= (delta << 8);
+ return result;
+ }
+};
+
+template <uint32 offset, std::size_t loByteIndex, std::size_t midByteIndex, std::size_t hiByteIndex>
+struct DecodeInt24
+{
+ using input_t = std::byte;
+ using output_t = int32;
+ static constexpr std::size_t input_inc = 3;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return ((mpt::byte_cast<uint8>(inBuf[loByteIndex]) << 8) | (mpt::byte_cast<uint8>(inBuf[midByteIndex]) << 16) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 24)) - offset;
+ }
+};
+
+template <uint32 offset, std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeInt32
+{
+ using input_t = std::byte;
+ using output_t = int32;
+ static constexpr std::size_t input_inc = 4;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (mpt::byte_cast<uint8>(inBuf[loLoByteIndex]) | (mpt::byte_cast<uint8>(inBuf[loHiByteIndex]) << 8) | (mpt::byte_cast<uint8>(inBuf[hiLoByteIndex]) << 16) | (mpt::byte_cast<uint8>(inBuf[hiHiByteIndex]) << 24)) - offset;
+ }
+};
+
+template <uint64 offset, std::size_t b0, std::size_t b1, std::size_t b2, std::size_t b3, std::size_t b4, std::size_t b5, std::size_t b6, std::size_t b7>
+struct DecodeInt64
+{
+ using input_t = std::byte;
+ using output_t = int64;
+ static constexpr std::size_t input_inc = 8;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (uint64(0)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b0])) << 0)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b1])) << 8)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b2])) << 16)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b3])) << 24)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b4])) << 32)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b5])) << 40)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b6])) << 48)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b7])) << 56))
+ - offset;
+ }
+};
+
+template <std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeFloat32
+{
+ using input_t = std::byte;
+ using output_t = float32;
+ static constexpr std::size_t input_inc = 4;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float32 val = IEEE754binary32LE(inBuf[loLoByteIndex], inBuf[loHiByteIndex], inBuf[hiLoByteIndex], inBuf[hiHiByteIndex]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0f)
+ {
+ val = 1.0f;
+ } else
+ {
+ val = -1.0f;
+ }
+ }
+ return val;
+ }
+};
+
+template <std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeScaledFloat32
+{
+ using input_t = std::byte;
+ using output_t = float32;
+ static constexpr std::size_t input_inc = 4;
+ float factor;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float32 val = IEEE754binary32LE(inBuf[loLoByteIndex], inBuf[loHiByteIndex], inBuf[hiLoByteIndex], inBuf[hiHiByteIndex]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0f)
+ {
+ val = 1.0f;
+ } else
+ {
+ val = -1.0f;
+ }
+ }
+ return factor * val;
+ }
+ MPT_FORCEINLINE DecodeScaledFloat32(float scaleFactor)
+ : factor(scaleFactor)
+ {
+ return;
+ }
+};
+
+template <std::size_t b0, std::size_t b1, std::size_t b2, std::size_t b3, std::size_t b4, std::size_t b5, std::size_t b6, std::size_t b7>
+struct DecodeFloat64
+{
+ using input_t = std::byte;
+ using output_t = float64;
+ static constexpr std::size_t input_inc = 8;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float64 val = IEEE754binary64LE(inBuf[b0], inBuf[b1], inBuf[b2], inBuf[b3], inBuf[b4], inBuf[b5], inBuf[b6], inBuf[b7]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0)
+ {
+ val = 1.0;
+ } else
+ {
+ val = -1.0;
+ }
+ }
+ return val;
+ }
+};
+
+template <typename Tsample>
+struct DecodeIdentity
+{
+ using input_t = Tsample;
+ using output_t = Tsample;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return *inBuf;
+ }
+};
+
+
+// Reads sample data with Func and passes it directly to Func2.
+// Func1::output_t and Func2::input_t must be identical
+template <typename Func2, typename Func1>
+struct ConversionChain
+{
+ using input_t = typename Func1::input_t;
+ using output_t = typename Func2::output_t;
+ static constexpr std::size_t input_inc = Func1::input_inc;
+ Func1 func1;
+ Func2 func2;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return func2(func1(inBuf));
+ }
+ MPT_FORCEINLINE ConversionChain(Func2 f2 = Func2(), Func1 f1 = Func1())
+ : func1(f1)
+ , func2(f2)
+ {
+ return;
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp
new file mode 100644
index 00000000..83792428
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <algorithm>
+
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+struct EncodeuLaw
+{
+ using input_t = int16;
+ using output_t = std::byte;
+ static constexpr uint8 exp_table[17] = {0, 7 << 4, 6 << 4, 5 << 4, 4 << 4, 3 << 4, 2 << 4, 1 << 4, 0 << 4, 0, 0, 0, 0, 0, 0, 0, 0};
+ static constexpr uint8 mant_table[17] = {0, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3};
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ uint16 x = static_cast<uint16>(val);
+ uint8 out = (x >> 8) & 0x80;
+ uint32 abs = x & 0x7fff;
+ if(x & 0x8000)
+ {
+ abs ^= 0x7fff;
+ abs += 1;
+ }
+ x = static_cast<uint16>(std::clamp(static_cast<uint32>(abs + (33 << 2)), static_cast<uint32>(0), static_cast<uint32>(0x7fff)));
+ int index = mpt::countl_zero(x);
+ out |= exp_table[index];
+ out |= (x >> mant_table[index]) & 0x0f;
+ out ^= 0xff;
+ return mpt::byte_cast<std::byte>(out);
+ }
+};
+
+
+struct EncodeALaw
+{
+ using input_t = int16;
+ using output_t = std::byte;
+ static constexpr uint8 exp_table[17] = {0, 7 << 4, 6 << 4, 5 << 4, 4 << 4, 3 << 4, 2 << 4, 1 << 4, 0 << 4, 0, 0, 0, 0, 0, 0, 0, 0};
+ static constexpr uint8 mant_table[17] = {0, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ int16 sx = std::clamp(val, static_cast<int16>(-32767), static_cast<int16>(32767));
+ uint16 x = static_cast<uint16>(sx);
+ uint8 out = ((x & 0x8000) ^ 0x8000) >> 8;
+ x = static_cast<uint16>(std::abs(sx));
+ int index = mpt::countl_zero(x);
+ out |= exp_table[index];
+ out |= (x >> mant_table[index]) & 0x0f;
+ out ^= 0x55;
+ return mpt::byte_cast<std::byte>(out);
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp
new file mode 100644
index 00000000..2ba2b18b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp
@@ -0,0 +1,391 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/utility.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <type_traits>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+class SampleFormat
+{
+public:
+ enum class Enum : uint8
+ {
+ Unsigned8 = 9, // do not change value (for compatibility with old configuration settings)
+ Int8 = 8, // do not change value (for compatibility with old configuration settings)
+ Int16 = 16, // do not change value (for compatibility with old configuration settings)
+ Int24 = 24, // do not change value (for compatibility with old configuration settings)
+ Int32 = 32, // do not change value (for compatibility with old configuration settings)
+ Float32 = 32 + 128, // do not change value (for compatibility with old configuration settings)
+ Float64 = 64 + 128, // do not change value (for compatibility with old configuration settings)
+ Invalid = 0
+ };
+ static constexpr SampleFormat::Enum Unsigned8 = SampleFormat::Enum::Unsigned8;
+ static constexpr SampleFormat::Enum Int8 = SampleFormat::Enum::Int8;
+ static constexpr SampleFormat::Enum Int16 = SampleFormat::Enum::Int16;
+ static constexpr SampleFormat::Enum Int24 = SampleFormat::Enum::Int24;
+ static constexpr SampleFormat::Enum Int32 = SampleFormat::Enum::Int32;
+ static constexpr SampleFormat::Enum Float32 = SampleFormat::Enum::Float32;
+ static constexpr SampleFormat::Enum Float64 = SampleFormat::Enum::Float64;
+ static constexpr SampleFormat::Enum Invalid = SampleFormat::Enum::Invalid;
+
+private:
+ SampleFormat::Enum value;
+
+ template <typename T>
+ static MPT_CONSTEXPRINLINE SampleFormat::Enum Sanitize(T x) noexcept
+ {
+ using uT = typename std::make_unsigned<T>::type;
+ uT val = static_cast<uT>(x);
+ if(!val)
+ {
+ return SampleFormat::Enum::Invalid;
+ }
+ if(val == static_cast<uT>(-8))
+ {
+ val = 8 + 1;
+ }
+ // float|64|32|16|8|?|?|unsigned
+ val &= 0b1'1111'00'1;
+ auto is_float = [](uT val) -> bool
+ {
+ return (val & 0b1'0000'00'0) ? true : false;
+ };
+ auto is_unsigned = [](uT val) -> bool
+ {
+ return (val & 0b0'0000'00'1) ? true : false;
+ };
+ auto has_size = [](uT val) -> bool
+ {
+ return (val & 0b0'1111'00'0) ? true : false;
+ };
+ auto unique_size = [](uT val) -> bool
+ {
+ val &= 0b0'1111'00'0;
+ if(val == 0b0'0001'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0010'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0011'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0100'00'0)
+ {
+ return true;
+ } else if(val == 0b0'1000'00'0)
+ {
+ return true;
+ } else
+ {
+ return false;
+ }
+ };
+ auto get_size = [](uT val) -> std::size_t
+ {
+ val &= 0b0'1111'00'0;
+ if(val == 0b0'0001'00'0)
+ {
+ return 1;
+ } else if(val == 0b0'0010'00'0)
+ {
+ return 2;
+ } else if(val == 0b0'0011'00'0)
+ {
+ return 3;
+ } else if(val == 0b0'0100'00'0)
+ {
+ return 4;
+ } else if(val == 0b0'1000'00'0)
+ {
+ return 8;
+ } else
+ {
+ return 2; // default to 16bit
+ }
+ };
+ if(!has_size(val))
+ {
+ if(is_unsigned(val) && is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Invalid);
+ } else if(is_unsigned(val))
+ {
+ val = mpt::to_underlying(Enum::Unsigned8);
+ } else if(is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Float32);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Invalid);
+ }
+ } else if(!unique_size(val))
+ {
+ // order of size check matters
+ if((val & 0b0'0011'00'0) == 0b0'0011'00'0)
+ {
+ val = mpt::to_underlying(Enum::Int24);
+ } else if(val & 0b0'0010'00'0)
+ {
+ val = mpt::to_underlying(Enum::Int16);
+ } else if(val & 0b0'0100'00'0)
+ {
+ if(is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Float32);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Int32);
+ }
+ } else if(val & 0b0'1000'00'0)
+ {
+ val = mpt::to_underlying(Enum::Float64);
+ } else if(val & 0b0'0001'00'0)
+ {
+ if(is_unsigned(val))
+ {
+ val = mpt::to_underlying(Enum::Unsigned8);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Int8);
+ }
+ }
+ } else
+ {
+ if(is_unsigned(val) && (get_size(val) > 1))
+ {
+ val &= ~0b0'0000'00'1; // remove unsigned
+ }
+ if(is_float(val) && (get_size(val) < 4))
+ {
+ val &= ~0b1'0000'00'0; // remove float
+ }
+ if(!is_float(val) && (get_size(val) == 8))
+ {
+ val |= 0b1'0000'00'0; // add float
+ }
+ }
+ return static_cast<SampleFormat::Enum>(val);
+ }
+
+public:
+ MPT_CONSTEXPRINLINE SampleFormat() noexcept
+ : value(SampleFormat::Invalid)
+ {
+ }
+
+ MPT_CONSTEXPRINLINE SampleFormat(SampleFormat::Enum v) noexcept
+ : value(Sanitize(v))
+ {
+ }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat &a, const SampleFormat &b) noexcept
+ {
+ return a.value == b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat &a, const SampleFormat &b) noexcept
+ {
+ return a.value != b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat::Enum &a, const SampleFormat &b) noexcept
+ {
+ return a == b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat::Enum &a, const SampleFormat &b) noexcept
+ {
+ return a != b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat &a, const SampleFormat::Enum &b) noexcept
+ {
+ return a.value == b;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat &a, const SampleFormat::Enum &b) noexcept
+ {
+ return a.value != b;
+ }
+
+ MPT_CONSTEXPRINLINE bool IsValid() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8)
+ || (value == SampleFormat::Int8)
+ || (value == SampleFormat::Int16)
+ || (value == SampleFormat::Int24)
+ || (value == SampleFormat::Int32)
+ || (value == SampleFormat::Float32)
+ || (value == SampleFormat::Float64);
+ }
+
+ MPT_CONSTEXPRINLINE bool IsUnsigned() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8);
+ }
+ MPT_CONSTEXPRINLINE bool IsFloat() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Float32)
+ || (value == SampleFormat::Float64);
+ }
+ MPT_CONSTEXPRINLINE bool IsInt() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8)
+ || (value == SampleFormat::Int8)
+ || (value == SampleFormat::Int16)
+ || (value == SampleFormat::Int24)
+ || (value == SampleFormat::Int32);
+ }
+ MPT_CONSTEXPRINLINE uint8 GetSampleSize() const noexcept
+ {
+ return !IsValid() ? 0
+ : (value == SampleFormat::Unsigned8) ? 1
+ : (value == SampleFormat::Int8) ? 1
+ : (value == SampleFormat::Int16) ? 2
+ : (value == SampleFormat::Int24) ? 3
+ : (value == SampleFormat::Int32) ? 4
+ : (value == SampleFormat::Float32) ? 4
+ : (value == SampleFormat::Float64) ? 8
+ : 0;
+ }
+ MPT_CONSTEXPRINLINE uint8 GetBitsPerSample() const noexcept
+ {
+ return !IsValid() ? 0
+ : (value == SampleFormat::Unsigned8) ? 8
+ : (value == SampleFormat::Int8) ? 8
+ : (value == SampleFormat::Int16) ? 16
+ : (value == SampleFormat::Int24) ? 24
+ : (value == SampleFormat::Int32) ? 32
+ : (value == SampleFormat::Float32) ? 32
+ : (value == SampleFormat::Float64) ? 64
+ : 0;
+ }
+
+ MPT_CONSTEXPRINLINE operator SampleFormat::Enum() const noexcept
+ {
+ return value;
+ }
+ explicit MPT_CONSTEXPRINLINE operator std::underlying_type<SampleFormat::Enum>::type() const noexcept
+ {
+ return mpt::to_underlying(value);
+ }
+
+ // backward compatibility, conversion to/from integers
+ static MPT_CONSTEXPRINLINE SampleFormat FromInt(int x) noexcept
+ {
+ return SampleFormat(Sanitize(x));
+ }
+ static MPT_CONSTEXPRINLINE int ToInt(SampleFormat x) noexcept
+ {
+ return mpt::to_underlying(x.value);
+ }
+ MPT_CONSTEXPRINLINE int AsInt() const noexcept
+ {
+ return mpt::to_underlying(value);
+ }
+};
+
+
+template <typename Container>
+Container AllSampleFormats()
+{
+ return {SampleFormat::Float64, SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Int8, SampleFormat::Unsigned8};
+}
+
+template <typename Container>
+Container DefaultSampleFormats()
+{
+ return {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Int8};
+}
+
+template <typename Tsample>
+struct SampleFormatTraits;
+template <>
+struct SampleFormatTraits<uint8>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Unsigned8; }
+};
+template <>
+struct SampleFormatTraits<int8>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int8; }
+};
+template <>
+struct SampleFormatTraits<int16>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int16; }
+};
+template <>
+struct SampleFormatTraits<int24>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int24; }
+};
+template <>
+struct SampleFormatTraits<int32>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int32; }
+};
+template <>
+struct SampleFormatTraits<float>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Float32; }
+};
+template <>
+struct SampleFormatTraits<double>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Float64; }
+};
+
+template <SampleFormat::Enum sampleFormat>
+struct SampleFormatToType;
+template <>
+struct SampleFormatToType<SampleFormat::Unsigned8>
+{
+ typedef uint8 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int8>
+{
+ typedef int8 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int16>
+{
+ typedef int16 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int24>
+{
+ typedef int24 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int32>
+{
+ typedef int32 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Float32>
+{
+ typedef float type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Float64>
+{
+ typedef double type;
+};
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp
new file mode 100644
index 00000000..e0448b1c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/binary/hex.hpp"
+#include "mpt/format/join.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <map>
+#include <string>
+#include <vector>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+SoundDevice::Type ParseType(const SoundDevice::Identifier &identifier)
+{
+ std::vector<mpt::ustring> tmp = mpt::split(identifier, MPT_USTRING("_"));
+ if(tmp.size() == 0)
+ {
+ return SoundDevice::Type();
+ }
+ return tmp[0];
+}
+
+
+mpt::ustring Info::GetDisplayName() const
+{
+ mpt::ustring result = apiName + MPT_USTRING(" - ") + mpt::trim(name);
+ switch(flags.usability)
+ {
+ case SoundDevice::Info::Usability::Experimental:
+ result += MPT_USTRING(" [experimental]");
+ break;
+ case SoundDevice::Info::Usability::Deprecated:
+ result += MPT_USTRING(" [deprecated]");
+ break;
+ case SoundDevice::Info::Usability::Broken:
+ result += MPT_USTRING(" [broken]");
+ break;
+ case SoundDevice::Info::Usability::NotAvailable:
+ result += MPT_USTRING(" [alien]");
+ break;
+ default:
+ // nothing
+ break;
+ }
+ if(default_ == SoundDevice::Info::Default::Named)
+ {
+ result += MPT_USTRING(" [default]");
+ }
+ if(apiPath.size() > 0)
+ {
+ result += MPT_USTRING(" (") + mpt::join(apiPath, MPT_USTRING("/")) + MPT_USTRING(")");
+ }
+ return result;
+}
+
+
+SoundDevice::Identifier Info::GetIdentifier() const
+{
+ if(!IsValid())
+ {
+ return mpt::ustring();
+ }
+ mpt::ustring result = mpt::ustring();
+ result += type;
+ result += MPT_USTRING("_");
+ if(useNameAsIdentifier)
+ {
+ // UTF8-encode the name and convert the utf8 to hex.
+ // This ensures that no special characters are contained in the configuration key.
+ std::string utf8String = mpt::transcode<std::string>(mpt::common_encoding::utf8, name);
+ mpt::ustring hexString = mpt::encode_hex(mpt::as_span(utf8String));
+ result += hexString;
+ } else
+ {
+ result += internalID; // safe to not contain special characters
+ }
+ return result;
+}
+
+
+ChannelMapping::ChannelMapping(uint32 numHostChannels)
+{
+ ChannelToDeviceChannel.resize(numHostChannels);
+ for(uint32 channel = 0; channel < numHostChannels; ++channel)
+ {
+ ChannelToDeviceChannel[channel] = channel;
+ }
+}
+
+
+ChannelMapping::ChannelMapping(const std::vector<int32> &mapping)
+{
+ if(IsValid(mapping))
+ {
+ ChannelToDeviceChannel = mapping;
+ }
+}
+
+
+ChannelMapping ChannelMapping::BaseChannel(uint32 channels, int32 baseChannel)
+{
+ SoundDevice::ChannelMapping result;
+ result.ChannelToDeviceChannel.resize(channels);
+ for(uint32 channel = 0; channel < channels; ++channel)
+ {
+ result.ChannelToDeviceChannel[channel] = channel + baseChannel;
+ }
+ return result;
+}
+
+
+bool ChannelMapping::IsValid(const std::vector<int32> &mapping)
+{
+ if(mapping.empty())
+ {
+ return true;
+ }
+ std::map<int32, uint32> inverseMapping;
+ for(uint32 hostChannel = 0; hostChannel < mapping.size(); ++hostChannel)
+ {
+ int32 deviceChannel = mapping[hostChannel];
+ if(deviceChannel < 0)
+ {
+ return false;
+ }
+ if(deviceChannel > MaxDeviceChannel)
+ {
+ return false;
+ }
+ inverseMapping[deviceChannel] = hostChannel;
+ }
+ if(inverseMapping.size() != mapping.size())
+ {
+ return false;
+ }
+ return true;
+}
+
+
+mpt::ustring ChannelMapping::ToUString() const
+{
+ return mpt::join_format<mpt::ustring, int32>(ChannelToDeviceChannel, MPT_USTRING(","));
+}
+
+
+ChannelMapping ChannelMapping::FromString(const mpt::ustring &str)
+{
+ return SoundDevice::ChannelMapping(mpt::split_parse<int32>(str, MPT_USTRING(",")));
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp
new file mode 100644
index 00000000..0f08534f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp
@@ -0,0 +1,640 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/osinfo/class.hpp"
+#include "mpt/osinfo/windows_version.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/FlagSet.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if defined(MODPLUG_TRACKER)
+#include "Logging.h"
+#endif // MODPLUG_TRACKER
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifndef MPT_SOUNDDEV_TRACE
+#if defined(MODPLUG_TRACKER)
+#define MPT_SOUNDDEV_TRACE() MPT_TRACE()
+#else // !MODPLUG_TRACKER
+#define MPT_SOUNDDEV_TRACE() \
+ do \
+ { \
+ } while(0)
+#endif // MODPLUG_TRACKER
+#endif
+#ifndef MPT_SOUNDDEV_TRACE_SCOPE
+#if defined(MODPLUG_TRACKER)
+#define MPT_SOUNDDEV_TRACE_SCOPE() MPT_TRACE_SCOPE()
+#else // !MODPLUG_TRACKER
+#define MPT_SOUNDDEV_TRACE_SCOPE() \
+ do \
+ { \
+ } while(0)
+#endif // MODPLUG_TRACKER
+#endif
+
+
+class IMessageReceiver
+{
+public:
+ virtual void SoundDeviceMessage(LogLevel level, const mpt::ustring &str) = 0;
+};
+
+
+inline constexpr mpt::uchar TypeWAVEOUT[] = MPT_ULITERAL("WaveOut");
+inline constexpr mpt::uchar TypeDSOUND[] = MPT_ULITERAL("DirectSound");
+inline constexpr mpt::uchar TypeASIO[] = MPT_ULITERAL("ASIO");
+inline constexpr mpt::uchar TypePORTAUDIO_WASAPI[] = MPT_ULITERAL("WASAPI");
+inline constexpr mpt::uchar TypePORTAUDIO_WDMKS[] = MPT_ULITERAL("WDM-KS");
+inline constexpr mpt::uchar TypePORTAUDIO_WMME[] = MPT_ULITERAL("MME");
+inline constexpr mpt::uchar TypePORTAUDIO_DS[] = MPT_ULITERAL("DS");
+
+typedef mpt::ustring Type;
+
+
+typedef mpt::ustring Identifier;
+
+SoundDevice::Type ParseType(const SoundDevice::Identifier &identifier);
+
+
+struct Info
+{
+ SoundDevice::Type type;
+ mpt::ustring internalID;
+ mpt::ustring name; // user visible (and configuration key if useNameAsIdentifier)
+ mpt::ustring apiName; // user visible
+ std::vector<mpt::ustring> apiPath; // i.e. Wine-support, PortAudio
+ enum class Default
+ {
+ None = 0,
+ Named = 1,
+ Managed = 2,
+ };
+ Default default_;
+ bool useNameAsIdentifier;
+
+ enum class DefaultFor : int8
+ {
+ System = 3,
+ ProAudio = 2,
+ LowLevel = 1,
+ None = 0,
+ };
+ struct ManagerFlags
+ {
+ DefaultFor defaultFor = DefaultFor::None;
+ };
+ ManagerFlags managerFlags;
+
+ enum class Usability : int8
+ {
+ Usable = 3,
+ Experimental = 2,
+ Legacy = 1,
+ Unknown = 0,
+ Deprecated = -4,
+ Broken = -5,
+ NotAvailable = -6,
+ };
+ enum class Level : int8
+ {
+ Primary = 1,
+ Unknown = 0,
+ Secondary = -1,
+ };
+ enum class Compatible : int8
+ {
+ Yes = 1,
+ Unknown = 0,
+ No = -1,
+ };
+ enum class Api : int8
+ {
+ Native = 1,
+ Unknown = 0,
+ Emulated = -1,
+ };
+ enum class Io : int8
+ {
+ FullDuplex = 1,
+ Unknown = 0,
+ OutputOnly = -1,
+ };
+ enum class Mixing : int8
+ {
+ Server = 2,
+ Software = 1,
+ Unknown = 0,
+ Hardware = -1,
+ };
+ enum class Implementor : int8
+ {
+ OpenMPT = 1,
+ Unknown = 0,
+ External = -1,
+ };
+ struct Flags
+ {
+ Usability usability = Usability::Unknown;
+ Level level = Level::Unknown;
+ Compatible compatible = Compatible::Unknown;
+ Api api = Api::Unknown;
+ Io io = Io::Unknown;
+ Mixing mixing = Mixing::Unknown;
+ Implementor implementor = Implementor::Unknown;
+ };
+ Flags flags;
+
+ std::map<mpt::ustring, mpt::ustring> extraData; // user visible (hidden by default)
+
+ Info()
+ : default_(Default::None)
+ , useNameAsIdentifier(false)
+ {
+ }
+
+ bool IsValid() const
+ {
+ return !type.empty() && !internalID.empty();
+ }
+
+ bool IsDeprecated() const
+ {
+ return (static_cast<int8>(flags.usability) <= 0) || (static_cast<int8>(flags.level) <= 0);
+ }
+
+ SoundDevice::Identifier GetIdentifier() const;
+
+ mpt::ustring GetDisplayName() const;
+};
+
+
+struct ChannelMapping
+{
+
+private:
+ std::vector<int32> ChannelToDeviceChannel;
+
+public:
+ static constexpr int32 MaxDeviceChannel = 32000;
+
+public:
+ // Construct default identity mapping
+ ChannelMapping(uint32 numHostChannels = 2);
+
+ // Construct mapping from given vector.
+ // Silently fall back to identity mapping if mapping is invalid.
+ ChannelMapping(const std::vector<int32> &mapping);
+
+ // Construct mapping for #channels with a baseChannel offset.
+ static ChannelMapping BaseChannel(uint32 channels, int32 baseChannel);
+
+private:
+ // check that the channel mapping is actually a 1:1 mapping
+ static bool IsValid(const std::vector<int32> &mapping);
+
+public:
+ operator int() const
+ {
+ return GetNumHostChannels();
+ }
+
+ ChannelMapping &operator=(int channels)
+ {
+ return (*this = ChannelMapping(channels));
+ }
+
+ friend bool operator==(const SoundDevice::ChannelMapping &a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a.ChannelToDeviceChannel == b.ChannelToDeviceChannel);
+ }
+
+ friend bool operator!=(const SoundDevice::ChannelMapping &a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a.ChannelToDeviceChannel != b.ChannelToDeviceChannel);
+ }
+
+ friend bool operator==(int a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a == static_cast<int>(b));
+ }
+
+ friend bool operator==(const SoundDevice::ChannelMapping &a, int b)
+ {
+ return (static_cast<int>(a) == b);
+ }
+
+ friend bool operator!=(int a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a != static_cast<int>(b));
+ }
+
+ friend bool operator!=(const SoundDevice::ChannelMapping &a, int b)
+ {
+ return (static_cast<int>(a) != b);
+ }
+
+ uint32 GetNumHostChannels() const
+ {
+ return static_cast<uint32>(ChannelToDeviceChannel.size());
+ }
+
+ // Get the number of required device channels for this mapping. Derived from the maximum mapped-to channel number.
+ int32 GetRequiredDeviceChannels() const
+ {
+ if(ChannelToDeviceChannel.empty())
+ {
+ return 0;
+ }
+ int32 maxChannel = 0;
+ for(uint32 channel = 0; channel < ChannelToDeviceChannel.size(); ++channel)
+ {
+ if(ChannelToDeviceChannel[channel] > maxChannel)
+ {
+ maxChannel = ChannelToDeviceChannel[channel];
+ }
+ }
+ return maxChannel + 1;
+ }
+
+ // Convert OpenMPT channel number to the mapped device channel number.
+ int32 ToDevice(uint32 channel) const
+ {
+ if(channel >= ChannelToDeviceChannel.size())
+ {
+ return channel;
+ }
+ return ChannelToDeviceChannel[channel];
+ }
+
+ mpt::ustring ToUString() const;
+
+ static SoundDevice::ChannelMapping FromString(const mpt::ustring &str);
+};
+
+
+struct SysInfo
+{
+public:
+ mpt::osinfo::osclass SystemClass = mpt::osinfo::osclass::Unknown;
+ mpt::osinfo::windows::Version WindowsVersion = mpt::osinfo::windows::Version::NoWindows();
+ bool IsWine = false;
+ mpt::osinfo::osclass WineHostClass = mpt::osinfo::osclass::Unknown;
+ mpt::osinfo::windows::wine::version WineVersion;
+
+public:
+ bool IsOriginal() const { return !IsWine; }
+ bool IsWindowsOriginal() const { return !IsWine; }
+ bool IsWindowsWine() const { return IsWine; }
+
+public:
+ SysInfo() = delete;
+ SysInfo(mpt::osinfo::osclass systemClass)
+ : SystemClass(systemClass)
+ {
+ assert(SystemClass != mpt::osinfo::osclass::Windows);
+ return;
+ }
+ SysInfo(mpt::osinfo::osclass systemClass, mpt::osinfo::windows::Version windowsVersion)
+ : SystemClass(systemClass)
+ , WindowsVersion(windowsVersion)
+ {
+ return;
+ }
+ SysInfo(mpt::osinfo::osclass systemClass, mpt::osinfo::windows::Version windowsVersion, bool isWine, mpt::osinfo::osclass wineHostClass, mpt::osinfo::windows::wine::version wineVersion)
+ : SystemClass(systemClass)
+ , WindowsVersion(windowsVersion)
+ , IsWine(isWine)
+ , WineHostClass(wineHostClass)
+ , WineVersion(wineVersion)
+ {
+ return;
+ }
+};
+
+
+struct AppInfo
+{
+ mpt::ustring Name;
+ std::uintptr_t UIHandle; // HWND on Windows
+ int BoostedThreadPriorityXP;
+ mpt::ustring BoostedThreadMMCSSClassVista;
+ bool BoostedThreadRealtimePosix;
+ int BoostedThreadNicenessPosix;
+ int BoostedThreadRtprioPosix;
+#if defined(MODPLUG_TRACKER)
+ bool MaskDriverCrashes;
+#endif // MODPLUG_TRACKER
+ bool AllowDeferredProcessing;
+ AppInfo()
+ : UIHandle(0)
+ , BoostedThreadPriorityXP(2) // THREAD_PRIORITY_HIGHEST
+ , BoostedThreadMMCSSClassVista(MPT_USTRING("Pro Audio"))
+ , BoostedThreadRealtimePosix(false)
+ , BoostedThreadNicenessPosix(-5)
+ , BoostedThreadRtprioPosix(10)
+#if defined(MODPLUG_TRACKER)
+ , MaskDriverCrashes(false)
+#endif // MODPLUG_TRACKER
+ , AllowDeferredProcessing(true)
+ {
+ return;
+ }
+ AppInfo &SetName(const mpt::ustring &name)
+ {
+ Name = name;
+ return *this;
+ }
+ mpt::ustring GetName() const { return Name; }
+#if MPT_OS_WINDOWS
+ AppInfo &SetHWND(HWND hwnd)
+ {
+ UIHandle = reinterpret_cast<uintptr_t>(hwnd);
+ return *this;
+ }
+ HWND GetHWND() const { return reinterpret_cast<HWND>(UIHandle); }
+#endif // MPT_OS_WINDOWS
+};
+
+
+struct Settings
+{
+ double Latency; // seconds
+ double UpdateInterval; // seconds
+ uint32 Samplerate;
+ SoundDevice::ChannelMapping Channels;
+ uint8 InputChannels;
+ SampleFormat sampleFormat;
+ bool ExclusiveMode; // Use hardware buffers directly
+ bool BoostThreadPriority; // Boost thread priority for glitch-free audio rendering
+ bool KeepDeviceRunning;
+ bool UseHardwareTiming;
+ int32 DitherType;
+ uint32 InputSourceID;
+ Settings()
+ : Latency(0.1)
+ , UpdateInterval(0.005)
+ , Samplerate(48000)
+ , Channels(2)
+ , InputChannels(0)
+ , sampleFormat(SampleFormat::Float32)
+ , ExclusiveMode(false)
+ , BoostThreadPriority(true)
+ , KeepDeviceRunning(true)
+ , UseHardwareTiming(false)
+ , DitherType(1)
+ , InputSourceID(0)
+ {
+ return;
+ }
+ bool operator==(const SoundDevice::Settings &cmp) const
+ {
+ return true
+ && mpt::saturate_round<int64>(Latency * 1000000000.0) == mpt::saturate_round<int64>(cmp.Latency * 1000000000.0) // compare in nanoseconds
+ && mpt::saturate_round<int64>(UpdateInterval * 1000000000.0) == mpt::saturate_round<int64>(cmp.UpdateInterval * 1000000000.0) // compare in nanoseconds
+ && Samplerate == cmp.Samplerate
+ && Channels == cmp.Channels
+ && InputChannels == cmp.InputChannels
+ && sampleFormat == cmp.sampleFormat
+ && ExclusiveMode == cmp.ExclusiveMode
+ && BoostThreadPriority == cmp.BoostThreadPriority
+ && KeepDeviceRunning == cmp.KeepDeviceRunning
+ && UseHardwareTiming == cmp.UseHardwareTiming
+ && DitherType == cmp.DitherType
+ && InputSourceID == cmp.InputSourceID;
+ }
+ bool operator!=(const SoundDevice::Settings &cmp) const
+ {
+ return !(*this == cmp);
+ }
+ std::size_t GetBytesPerFrame() const
+ {
+ return sampleFormat.GetSampleSize() * Channels;
+ }
+ std::size_t GetBytesPerSecond() const
+ {
+ return Samplerate * GetBytesPerFrame();
+ }
+ uint32 GetTotalChannels() const
+ {
+ return InputChannels + Channels;
+ }
+};
+
+
+struct Flags
+{
+ // Windows since Vista has a limiter/compressor in the audio path that kicks
+ // in as soon as there are samples > 0dBFs (i.e. the absolute float value >
+ // 1.0). This happens for all APIs that get processed through the system-
+ // wide audio engine. It does not happen for exclusive mode WASAPI streams
+ // or direct WaveRT (labeled WDM-KS in PortAudio) streams. As there is no
+ // known way to disable this annoying behavior, avoid unclipped samples on
+ // affected windows versions and clip them ourselves before handing them to
+ // the APIs.
+ bool WantsClippedOutput;
+ Flags()
+ : WantsClippedOutput(false)
+ {
+ return;
+ }
+};
+
+
+struct Caps
+{
+ bool Available;
+ bool CanUpdateInterval;
+ bool CanSampleFormat;
+ bool CanExclusiveMode;
+ bool CanBoostThreadPriority;
+ bool CanKeepDeviceRunning;
+ bool CanUseHardwareTiming;
+ bool CanChannelMapping;
+ bool CanInput;
+ bool HasNamedInputSources;
+ bool CanDriverPanel;
+ bool HasInternalDither;
+ mpt::ustring ExclusiveModeDescription;
+ double LatencyMin;
+ double LatencyMax;
+ double UpdateIntervalMin;
+ double UpdateIntervalMax;
+ SoundDevice::Settings DefaultSettings;
+ Caps()
+ : Available(false)
+ , CanUpdateInterval(true)
+ , CanSampleFormat(true)
+ , CanExclusiveMode(false)
+ , CanBoostThreadPriority(true)
+ , CanKeepDeviceRunning(false)
+ , CanUseHardwareTiming(false)
+ , CanChannelMapping(false)
+ , CanInput(false)
+ , HasNamedInputSources(false)
+ , CanDriverPanel(false)
+ , HasInternalDither(false)
+ , ExclusiveModeDescription(MPT_USTRING("Use device exclusively"))
+ , LatencyMin(0.002) // 2ms
+ , LatencyMax(0.5) // 500ms
+ , UpdateIntervalMin(0.001) // 1ms
+ , UpdateIntervalMax(0.2) // 200ms
+ {
+ return;
+ }
+};
+
+
+struct DynamicCaps
+{
+ uint32 currentSampleRate = 0;
+ std::vector<uint32> supportedSampleRates;
+ std::vector<uint32> supportedExclusiveSampleRates;
+ std::vector<SampleFormat> supportedSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ std::vector<SampleFormat> supportedExclusiveModeSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ std::vector<mpt::ustring> channelNames;
+ std::vector<std::pair<uint32, mpt::ustring>> inputSourceNames;
+};
+
+
+struct BufferAttributes
+{
+ double Latency; // seconds
+ double UpdateInterval; // seconds
+ int NumBuffers;
+ BufferAttributes()
+ : Latency(0.0)
+ , UpdateInterval(0.0)
+ , NumBuffers(0)
+ {
+ return;
+ }
+};
+
+
+enum RequestFlags : uint32
+{
+ RequestFlagClose = 1 << 0,
+ RequestFlagReset = 1 << 1,
+ RequestFlagRestart = 1 << 2,
+};
+MPT_DECLARE_ENUM(RequestFlags)
+
+
+struct Statistics
+{
+ double InstantaneousLatency;
+ double LastUpdateInterval;
+ mpt::ustring text;
+ Statistics()
+ : InstantaneousLatency(0.0)
+ , LastUpdateInterval(0.0)
+ {
+ return;
+ }
+};
+
+
+class BackendInitializer
+{
+public:
+ BackendInitializer() = default;
+ virtual void Reload()
+ {
+ return;
+ }
+ virtual ~BackendInitializer() = default;
+};
+
+
+class IBase
+{
+
+protected:
+ IBase() = default;
+
+public:
+ virtual ~IBase() = default;
+
+public:
+ virtual void SetMessageReceiver(SoundDevice::IMessageReceiver *receiver) = 0;
+ virtual void SetCallback(SoundDevice::ICallback *callback) = 0;
+
+ virtual SoundDevice::Info GetDeviceInfo() const = 0;
+
+ virtual SoundDevice::Caps GetDeviceCaps() const = 0;
+ virtual SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates) = 0;
+
+ virtual bool Init(const SoundDevice::AppInfo &appInfo) = 0;
+ virtual bool Open(const SoundDevice::Settings &settings) = 0;
+ virtual bool Close() = 0;
+ virtual bool Start() = 0;
+ virtual void Stop() = 0;
+
+ virtual FlagSet<RequestFlags> GetRequestFlags() const = 0;
+
+ virtual bool IsInited() const = 0;
+ virtual bool IsOpen() const = 0;
+ virtual bool IsAvailable() const = 0;
+ virtual bool IsPlaying() const = 0;
+
+ virtual bool IsPlayingSilence() const = 0;
+ virtual void StopAndAvoidPlayingSilence() = 0;
+ virtual void EndPlayingSilence() = 0;
+
+ virtual bool OnIdle() = 0; // return true if any work has been done
+
+ virtual SoundDevice::Settings GetSettings() const = 0;
+ virtual SampleFormat GetActualSampleFormat() const = 0;
+ virtual SoundDevice::BufferAttributes GetEffectiveBufferAttributes() const = 0;
+
+ virtual SoundDevice::TimeInfo GetTimeInfo() const = 0;
+ virtual SoundDevice::StreamPosition GetStreamPosition() const = 0;
+
+ // Debugging aids in case of a crash
+ virtual bool DebugIsFragileDevice() const = 0;
+ virtual bool DebugInRealtimeCallback() const = 0;
+
+ // Informational only, do not use for timing.
+ // Use GetStreamPositionFrames() for timing
+ virtual SoundDevice::Statistics GetStatistics() const = 0;
+
+ virtual bool OpenDriverSettings() = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp
new file mode 100644
index 00000000..14b3bd67
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp
@@ -0,0 +1,1405 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifdef MPT_WITH_ASIO
+
+#include "SoundDeviceASIO.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/exception_text/exception_text.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#if defined(MODPLUG_TRACKER)
+#include "mptAssert.h"
+#if !defined(MPT_BUILD_WINESUPPORT)
+#include "../mptrack/ExceptionHandler.h"
+#endif // !MPT_BUILD_WINESUPPORT
+#endif // MODPLUG_TRACKER
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <exception>
+#include <memory>
+#include <new>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#include <ASIOModern/ASIO.hpp>
+#include <ASIOModern/ASIOSystemWindows.hpp>
+#if defined(MODPLUG_TRACKER)
+#include <ASIOModern/ASIOSystemWindowsSEH.hpp>
+#endif // MODPLUG_TRACKER
+#include <ASIOModern/ASIOSampleConvert.hpp>
+//#include <ASIOModern/ASIOVerifyABI.hpp>
+
+#endif // MPT_WITH_ASIO
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_ASIO
+
+
+static constexpr uint64 AppID1 = 0x4f70656e4d50542dull; // "OpenMPT-"
+static constexpr uint64 AppID2 = 0x4153494f00000000ull; // "ASIO"
+
+static constexpr double AsioSampleRateTolerance = 0.05;
+
+
+static constexpr inline auto value_cast(ASIO::Bool b) noexcept -> bool
+{
+ return static_cast<bool>(b);
+}
+
+
+// Helper class to temporarily open a driver for a query.
+class TemporaryASIODriverOpener
+{
+protected:
+ CASIODevice &device;
+ const bool wasOpen;
+
+public:
+ TemporaryASIODriverOpener(CASIODevice &d)
+ : device(d)
+ , wasOpen(d.IsDriverOpen())
+ {
+ if(!wasOpen)
+ {
+ device.OpenDriver();
+ }
+ }
+
+ ~TemporaryASIODriverOpener()
+ {
+ if(!wasOpen)
+ {
+ device.CloseDriver();
+ }
+ }
+};
+
+
+static mpt::winstring AsWinstring(const std::basic_string<TCHAR> &str)
+{
+ return mpt::winstring(str.data(), str.length());
+}
+
+
+std::vector<SoundDevice::Info> CASIODevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ std::vector<SoundDevice::Info> devices;
+ std::vector<ASIO::Windows::DriverInfo> drivers = ASIO::Windows::EnumerateDrivers();
+ for(const auto &driver : drivers)
+ {
+ SoundDevice::Info info;
+ info.type = TypeASIO;
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::CLSIDToString(driver.Clsid));
+ info.apiName = MPT_USTRING("ASIO");
+ info.name = mpt::transcode<mpt::ustring>(AsWinstring(driver.DisplayName()));
+ info.useNameAsIdentifier = false;
+ info.default_ = Info::Default::None;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ info.extraData[MPT_USTRING("Key")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Key));
+ ;
+ info.extraData[MPT_USTRING("Id")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Id));
+ info.extraData[MPT_USTRING("CLSID")] = mpt::transcode<mpt::ustring>(mpt::CLSIDToString(driver.Clsid));
+ info.extraData[MPT_USTRING("Name")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Name));
+ ;
+ info.extraData[MPT_USTRING("Description")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Description));
+ ;
+ info.extraData[MPT_USTRING("DisplayName")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.DisplayName()));
+ ;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Found driver:")());
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Key = '{}'")(info.extraData[MPT_USTRING("Key")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Id = '{}'")(info.extraData[MPT_USTRING("Id")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: CLSID = '{}'")(info.extraData[MPT_USTRING("CLSID")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Name = '{}'")(info.extraData[MPT_USTRING("Name")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Description = '{}'")(info.extraData[MPT_USTRING("Description")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: DisplayName = '{}'")(info.extraData[MPT_USTRING("DisplayName")]));
+ devices.push_back(info);
+ }
+ return devices;
+}
+
+
+CASIODevice::CASIODevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_RenderSilence(false)
+ , m_RenderingSilence(false)
+ , m_AsioRequest(0)
+ , m_UsedFeatures(0)
+ , m_DebugRealtimeThreadID(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_Ectx.SetDescription(MPT_UFORMAT_MESSAGE("ASIO Driver: {}")(GetDeviceInternalID()));
+ InitMembers();
+}
+
+
+void CASIODevice::InitMembers()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_DeferredBufferSwitchDispatcher = nullptr;
+ m_Driver = nullptr;
+
+ m_BufferLatency = 0.0;
+ m_nAsioBufferLen = 0;
+ m_BufferInfo.clear();
+ m_BuffersCreated = false;
+ m_ChannelInfo.clear();
+ m_SampleBufferDouble.clear();
+ m_SampleBufferFloat.clear();
+ m_SampleBufferInt16.clear();
+ m_SampleBufferInt24.clear();
+ m_SampleBufferInt32.clear();
+ m_SampleInputBufferDouble.clear();
+ m_SampleInputBufferFloat.clear();
+ m_SampleInputBufferInt16.clear();
+ m_SampleInputBufferInt24.clear();
+ m_SampleInputBufferInt32.clear();
+ m_CanOutputReady = false;
+
+ m_DeviceRunning = false;
+ m_TotalFramesWritten = 0;
+ m_DeferredProcessing = false;
+ m_BufferIndex = 0;
+ m_RenderSilence = false;
+ m_RenderingSilence = false;
+
+ m_AsioRequest.store(0);
+
+ m_DebugRealtimeThreadID.store(0);
+}
+
+
+bool CASIODevice::HandleRequests()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ uint32 flags = m_AsioRequest.exchange(0);
+ if(flags & AsioRequest::LatenciesChanged)
+ {
+ UpdateLatency();
+ result = true;
+ }
+ return result;
+}
+
+
+CASIODevice::~CASIODevice()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ Close();
+}
+
+
+bool CASIODevice::InternalOpen()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ assert(!IsDriverOpen());
+
+ InitMembers();
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Open('{}'): {}-bit, ({},{}) channels, {}Hz, hw-timing={}")(GetDeviceInternalID(), m_Settings.sampleFormat.GetBitsPerSample(), m_Settings.InputChannels, static_cast<int>(m_Settings.Channels), m_Settings.Samplerate, m_Settings.UseHardwareTiming));
+
+ SoundDevice::ChannelMapping inputChannelMapping = SoundDevice::ChannelMapping::BaseChannel(m_Settings.InputChannels, m_Settings.InputSourceID);
+
+ try
+ {
+
+ OpenDriver();
+
+ if(!IsDriverOpen())
+ {
+ throw ASIOException("Initializing driver failed.");
+ }
+
+ ASIO::Channels channels = AsioDriver()->getChannels();
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getChannels() => inputChannels={} outputChannel={}")(channels.Input, channels.Output));
+ if(channels.Input <= 0 && channels.Output <= 0)
+ {
+ m_DeviceUnavailableOnOpen = true;
+ throw ASIOException("Device unavailble.");
+ }
+ if(m_Settings.Channels > channels.Output)
+ {
+ throw ASIOException("Not enough output channels.");
+ }
+ if(m_Settings.Channels.GetRequiredDeviceChannels() > channels.Output)
+ {
+ throw ASIOException("Channel mapping requires more channels than available.");
+ }
+ if(m_Settings.InputChannels > channels.Input)
+ {
+ throw ASIOException("Not enough input channels.");
+ }
+ if(inputChannelMapping.GetRequiredDeviceChannels() > channels.Input)
+ {
+ throw ASIOException("Channel mapping requires more channels than available.");
+ }
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: setSampleRate(sampleRate={})")(m_Settings.Samplerate));
+ AsioDriver()->setSampleRate(m_Settings.Samplerate);
+
+ ASIO::BufferSizes bufferSizes = AsioDriver()->getBufferSizes();
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getBufferSize() => minSize={} maxSize={} preferredSize={} granularity={}")(bufferSizes.Min, bufferSizes.Max, bufferSizes.Preferred, bufferSizes.Granularity));
+ m_nAsioBufferLen = mpt::saturate_round<int32>(m_Settings.Latency * m_Settings.Samplerate / 2.0);
+ if(bufferSizes.Min <= 0 || bufferSizes.Max <= 0 || bufferSizes.Min > bufferSizes.Max)
+ { // limits make no sense
+ if(bufferSizes.Preferred > 0)
+ {
+ m_nAsioBufferLen = bufferSizes.Preferred;
+ } else
+ {
+ // just leave the user value, perhaps it works
+ }
+ } else if(bufferSizes.Granularity < -1)
+ { // bufferSizes.Granularity value not allowed, just clamp value
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ } else if(bufferSizes.Granularity == -1 && (mpt::popcount(static_cast<ASIO::ULong>(bufferSizes.Min)) != 1 || mpt::popcount(static_cast<ASIO::ULong>(bufferSizes.Max)) != 1))
+ { // bufferSizes.Granularity tells us we need power-of-2 sizes, but min or max sizes are no power-of-2
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // just start at 1 and find a matching power-of-2 in range
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = 1; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize *= 2)
+ {
+ if(bufSize >= bufferSizes.Min)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ }
+ // if no power-of-2 in range is found, just leave the clamped value alone, perhaps it works
+ } else if(bufferSizes.Granularity == -1)
+ { // sane values, power-of-2 size required between min and max
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // get the largest allowed buffer size that is smaller or equal to the target size
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = bufferSizes.Min; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize *= 2)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ } else if(bufferSizes.Granularity > 0)
+ { // buffer size in bufferSizes.Granularity steps from min to max allowed
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // get the largest allowed buffer size that is smaller or equal to the target size
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = bufferSizes.Min; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize += bufferSizes.Granularity)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ } else if(bufferSizes.Granularity == 0)
+ { // no bufferSizes.Granularity given, we should use bufferSizes.Preferred if possible
+ if(bufferSizes.Preferred > 0)
+ {
+ m_nAsioBufferLen = bufferSizes.Preferred;
+ } else if(m_nAsioBufferLen >= bufferSizes.Max)
+ { // a large latency was requested, use bufferSizes.Max
+ m_nAsioBufferLen = bufferSizes.Max;
+ } else
+ { // use bufferSizes.Min otherwise
+ m_nAsioBufferLen = bufferSizes.Min;
+ }
+ } else
+ { // should not happen
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+
+ m_BufferInfo.resize(m_Settings.GetTotalChannels());
+ for(uint32 channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ m_BufferInfo[channel] = ASIO::BufferInfo();
+ if(channel < m_Settings.InputChannels)
+ {
+ m_BufferInfo[channel].isInput = true;
+ m_BufferInfo[channel].channelNum = inputChannelMapping.ToDevice(channel);
+ } else
+ {
+ m_BufferInfo[channel].isInput = false;
+ m_BufferInfo[channel].channelNum = m_Settings.Channels.ToDevice(channel - m_Settings.InputChannels);
+ }
+ }
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: createBuffers(numChannels={}, bufferSize={})")(m_Settings.Channels.GetNumHostChannels(), m_nAsioBufferLen));
+ AsioDriver()->template createBuffers<AppID1, AppID2>(m_BufferInfo, m_nAsioBufferLen, *this);
+ m_BuffersCreated = true;
+ for(std::size_t i = 0; i < m_BufferInfo.size(); ++i)
+ {
+ if(!m_BufferInfo[i].buffers[0] || !m_BufferInfo[i].buffers[1])
+ {
+ throw ASIOException("createBuffes returned nullptr.");
+ }
+ }
+
+ m_ChannelInfo.resize(m_Settings.GetTotalChannels());
+ for(uint32 channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ if(channel < m_Settings.InputChannels)
+ {
+ m_ChannelInfo[channel] = AsioDriver()->getChannelInfo(inputChannelMapping.ToDevice(channel), true);
+ } else
+ {
+ m_ChannelInfo[channel] = AsioDriver()->getChannelInfo(m_Settings.Channels.ToDevice(channel - m_Settings.InputChannels), false);
+ }
+ assert(m_ChannelInfo[channel].isActive);
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getChannelInfo(isInput={} channel={}) => isActive={} channelGroup={} type={} name='{}'")((channel < m_Settings.InputChannels), m_Settings.Channels.ToDevice(channel), value_cast(m_ChannelInfo[channel].isActive), m_ChannelInfo[channel].channelGroup, mpt::to_underlying(m_ChannelInfo[channel].type), mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(m_ChannelInfo[channel].name))));
+ }
+
+ bool allChannelsAreInt = true;
+ bool allChannelsAreInt16ValidBits = true;
+ bool allChannelsAreNativeInt24 = true;
+ bool allChannelsAreFloat32 = true;
+ for(std::size_t channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ ASIO::Sample::Traits sampleTraits = ASIO::Sample::Traits(m_ChannelInfo[channel].type);
+ bool isFloat = sampleTraits.is_float;
+ bool isFloat32 = sampleTraits.is_float && sampleTraits.valid_bits == 32;
+ bool isInt16ValidBits = !sampleTraits.is_float && sampleTraits.valid_bits == 16;
+ bool isInt24 = !sampleTraits.is_float && sampleTraits.size_bytes == 3 && sampleTraits.valid_bits == 24;
+ bool isNative = (mpt::endian_is_little() && !sampleTraits.is_be) || (mpt::endian_is_big() && sampleTraits.is_be);
+ if(isFloat)
+ {
+ allChannelsAreInt = false;
+ }
+ if(!isInt16ValidBits)
+ {
+ allChannelsAreInt16ValidBits = false;
+ }
+ if(!(isInt24 && isNative))
+ {
+ allChannelsAreNativeInt24 = false;
+ }
+ if(!isFloat32)
+ {
+ allChannelsAreFloat32 = false;
+ }
+ }
+ if(allChannelsAreInt16ValidBits)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int16;
+ m_SampleBufferInt16.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt16.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreNativeInt24)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int24;
+ m_SampleBufferInt24.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt24.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreInt)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int32;
+ m_SampleBufferInt32.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt32.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreFloat32)
+ {
+ m_Settings.sampleFormat = SampleFormat::Float32;
+ m_SampleBufferFloat.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferFloat.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else
+ {
+ m_Settings.sampleFormat = SampleFormat::Float64;
+ m_SampleBufferDouble.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferDouble.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ }
+
+ for(std::size_t channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ ASIO::Sample::ClearBufferASIO(m_BufferInfo[channel].buffers[0], m_ChannelInfo[channel].type, m_nAsioBufferLen);
+ ASIO::Sample::ClearBufferASIO(m_BufferInfo[channel].buffers[1], m_ChannelInfo[channel].type, m_nAsioBufferLen);
+ }
+
+ m_CanOutputReady = AsioDriver()->canOutputReady();
+ ;
+
+ m_StreamPositionOffset = m_nAsioBufferLen;
+
+ UpdateLatency();
+
+ return true;
+
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ InternalClose();
+ return false;
+}
+
+
+void CASIODevice::UpdateLatency()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Latencies latencies;
+ try
+ {
+ latencies = AsioDriver()->getLatencies();
+ } catch(const ASIO::Error &)
+ {
+ // continue, failure is not fatal here
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ if(latencies.Output >= m_nAsioBufferLen)
+ {
+ m_BufferLatency = static_cast<double>(latencies.Output + m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate); // ASIO and OpenMPT semantics of 'latency' differ by one chunk/buffer
+ } else
+ {
+ // pointless value returned from asio driver, use a sane estimate
+ m_BufferLatency = 2.0 * static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ }
+}
+
+
+void CASIODevice::SetRenderSilence(bool silence, bool wait)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_RenderSilence = silence;
+ if(!wait)
+ {
+ return;
+ }
+ std::chrono::steady_clock::time_point pollingstart = std::chrono::steady_clock::now();
+ while(m_RenderingSilence != silence)
+ {
+ if((std::chrono::steady_clock::now() - pollingstart) > std::chrono::microseconds(250))
+ {
+#if defined(MODPLUG_TRACKER)
+ if(silence)
+ {
+ if(CallbackIsLockedByCurrentThread())
+ {
+ MPT_ASSERT_MSG(false, "AudioCriticalSection locked while stopping ASIO");
+ } else
+ {
+ MPT_ASSERT_MSG(false, "waiting for asio failed in Stop()");
+ }
+ } else
+ {
+ if(CallbackIsLockedByCurrentThread())
+ {
+ MPT_ASSERT_MSG(false, "AudioCriticalSection locked while starting ASIO");
+ } else
+ {
+ MPT_ASSERT_MSG(false, "waiting for asio failed in Start()");
+ }
+ }
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+
+bool CASIODevice::InternalStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_ALWAYS_MSG(!CallbackIsLockedByCurrentThread(), "AudioCriticalSection locked while starting ASIO");
+#else // !MODPLUG_TRACKER
+ assert(!CallbackIsLockedByCurrentThread());
+#endif // MODPLUG_TRACKER
+
+ if(m_Settings.KeepDeviceRunning)
+ {
+ if(m_DeviceRunning)
+ {
+ SetRenderSilence(false, true);
+ return true;
+ }
+ }
+
+ SetRenderSilence(false);
+ try
+ {
+ m_TotalFramesWritten = 0;
+ AsioDriver()->start();
+ m_DeviceRunning = true;
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return false;
+ }
+
+ return true;
+}
+
+
+bool CASIODevice::InternalIsPlayingSilence() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return m_Settings.KeepDeviceRunning && m_DeviceRunning && m_RenderSilence.load();
+}
+
+
+void CASIODevice::InternalEndPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InternalIsPlayingSilence())
+ {
+ return;
+ }
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ SetRenderSilence(false);
+}
+
+
+void CASIODevice::InternalStopAndAvoidPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ InternalStopImpl(true);
+}
+
+void CASIODevice::InternalStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ InternalStopImpl(false);
+}
+
+void CASIODevice::InternalStopImpl(bool force)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_ALWAYS_MSG(!CallbackIsLockedByCurrentThread(), "AudioCriticalSection locked while stopping ASIO");
+#else // !MODPLUG_TRACKER
+ assert(!CallbackIsLockedByCurrentThread());
+#endif // MODPLUG_TRACKER
+
+ if(m_Settings.KeepDeviceRunning && !force)
+ {
+ SetRenderSilence(true, true);
+ return;
+ }
+
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ SetRenderSilence(false);
+}
+
+
+bool CASIODevice::InternalClose()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeviceRunning)
+ {
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ }
+ SetRenderSilence(false);
+
+ m_CanOutputReady = false;
+ m_SampleBufferFloat.clear();
+ m_SampleBufferInt16.clear();
+ m_SampleBufferInt24.clear();
+ m_SampleBufferInt32.clear();
+ m_SampleInputBufferFloat.clear();
+ m_SampleInputBufferInt16.clear();
+ m_SampleInputBufferInt24.clear();
+ m_SampleInputBufferInt32.clear();
+ m_ChannelInfo.clear();
+ if(m_BuffersCreated)
+ {
+ try
+ {
+ AsioDriver()->disposeBuffers();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_BuffersCreated = false;
+ }
+ m_BufferInfo.clear();
+ m_nAsioBufferLen = 0;
+ m_BufferLatency = 0.0;
+
+ CloseDriver();
+
+ return true;
+}
+
+
+void CASIODevice::OpenDriver()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsDriverOpen())
+ {
+ return;
+ }
+ CLSID clsid = mpt::StringToCLSID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ try
+ {
+ if(GetAppInfo().AllowDeferredProcessing)
+ {
+ m_DeferredBufferSwitchDispatcher = ASIO::Windows::CreateBufferSwitchDispatcher([=](ASIO::BufferIndex bufferIndex)
+ { this->RealtimeBufferSwitchImpl(bufferIndex); });
+ }
+ {
+ CrashContextGuard guard{&m_Ectx};
+#if defined(MODPLUG_TRACKER)
+ if(GetAppInfo().MaskDriverCrashes)
+ {
+ m_Driver = std::make_unique<ASIO::Driver>(std::make_unique<ASIO::Windows::SEH::Driver>(clsid, GetAppInfo().GetHWND()));
+ } else
+#endif // MODPLUG_TRACKER
+ {
+ m_Driver = std::make_unique<ASIO::Driver>(std::make_unique<ASIO::Windows::Driver>(clsid, GetAppInfo().GetHWND()));
+ }
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return;
+ }
+ std::string driverName;
+ ASIO::Long driverVersion = 0;
+ std::string driverErrorMessage;
+ try
+ {
+ driverName = AsioDriver()->getDriverName();
+ driverVersion = AsioDriver()->getDriverVersion();
+ driverErrorMessage = AsioDriver()->getErrorMessage();
+ } catch(...)
+ {
+ CloseDriver();
+ ExceptionHandler(__func__);
+ return;
+ }
+ MPT_LOG(GetLogger(), LogInformation, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Opened driver {} Version 0x{}: {}")(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, driverName), mpt::format<mpt::ustring>::HEX0<8>(driverVersion), mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, driverErrorMessage)));
+}
+
+
+void CASIODevice::CloseDriver()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsDriverOpen())
+ {
+ return;
+ }
+ try
+ {
+ {
+ CrashContextGuard guard{&m_Ectx};
+ m_Driver = nullptr;
+ }
+ m_DeferredBufferSwitchDispatcher = nullptr;
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+}
+
+
+void CASIODevice::InternalFillAudioBuffer()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ FillAsioBuffer();
+}
+
+
+void CASIODevice::FillAsioBuffer(bool useSource)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ const bool rendersilence = !useSource;
+ const std::size_t countChunk = m_nAsioBufferLen;
+ const std::size_t inputChannels = m_Settings.InputChannels;
+ const std::size_t outputChannels = m_Settings.Channels;
+ for(std::size_t inputChannel = 0; inputChannel < inputChannels; ++inputChannel)
+ {
+ std::size_t channel = inputChannel;
+ const void *src = m_BufferInfo[channel].buffers[m_BufferIndex];
+ ASIO::SampleType sampleType = m_ChannelInfo[channel].type;
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ double *const dstDouble = m_SampleInputBufferDouble.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float64LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float64MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstDouble + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstDouble + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ float *const dstFloat = m_SampleInputBufferFloat.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float32MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstFloat + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstFloat + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ int16 *const dstInt16 = m_SampleInputBufferInt16.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int16LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int16MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstInt16 + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstInt16 + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ int24 *const dstInt24 = m_SampleInputBufferInt24.data();
+ assert((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int24LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int24MSB));
+ ASIO::Sample::CopyRawFromASIO(dstInt24 + inputChannel, inputChannels, src, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ int32 *const dstInt32 = m_SampleInputBufferInt32.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int32MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstInt32 + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstInt32 + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ if(rendersilence)
+ {
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ std::fill(m_SampleBufferDouble.data(), m_SampleBufferDouble.data() + countChunk * outputChannels, double(0.0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ std::fill(m_SampleBufferFloat.data(), m_SampleBufferFloat.data() + countChunk * outputChannels, float(0.0f));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ std::fill(m_SampleBufferInt16.data(), m_SampleBufferInt16.data() + countChunk * outputChannels, int16(0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ std::fill(m_SampleBufferInt24.data(), m_SampleBufferInt24.data() + countChunk * outputChannels, int24(0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ std::fill(m_SampleBufferInt32.data(), m_SampleBufferInt32.data() + countChunk * outputChannels, int32(0));
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ } else
+ {
+ CallbackLockedAudioReadPrepare(countChunk, m_nAsioBufferLen * 2);
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferDouble.data(), (m_SampleInputBufferDouble.size() > 0) ? m_SampleInputBufferDouble.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferFloat.data(), (m_SampleInputBufferFloat.size() > 0) ? m_SampleInputBufferFloat.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt16.data(), (m_SampleInputBufferInt16.size() > 0) ? m_SampleInputBufferInt16.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt24.data(), (m_SampleInputBufferInt24.size() > 0) ? m_SampleInputBufferInt24.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt32.data(), (m_SampleInputBufferInt32.size() > 0) ? m_SampleInputBufferInt32.data() : nullptr, countChunk);
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ for(std::size_t outputChannel = 0; outputChannel < outputChannels; ++outputChannel)
+ {
+ std::size_t channel = outputChannel + m_Settings.InputChannels;
+ void *dst = m_BufferInfo[channel].buffers[m_BufferIndex];
+ ASIO::SampleType sampleType = m_ChannelInfo[channel].type;
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ const double *const srcDouble = m_SampleBufferDouble.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float64LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float64MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcDouble + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcDouble + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ const float *const srcFloat = m_SampleBufferFloat.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float32MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcFloat + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcFloat + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ const int16 *const srcInt16 = m_SampleBufferInt16.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int16LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int16MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcInt16 + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcInt16 + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ const int24 *const srcInt24 = m_SampleBufferInt24.data();
+ assert((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int24LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int24MSB));
+ ASIO::Sample::CopyRawToASIO(dst, srcInt24 + outputChannel, outputChannels, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ const int32 *const srcInt32 = m_SampleBufferInt32.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int32MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcInt32 + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcInt32 + outputChannel, outputChannels, countChunk);
+ }
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ if(m_CanOutputReady)
+ {
+ try
+ {
+ AsioDriver()->outputReady(); // do not handle errors, there is nothing we could do about them
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ }
+ if(!rendersilence)
+ {
+ CallbackLockedAudioProcessDone();
+ }
+}
+
+
+bool CASIODevice::InternalHasTimeInfo() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return m_Settings.UseHardwareTiming;
+}
+
+
+SoundDevice::BufferAttributes CASIODevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_BufferLatency;
+ bufferAttributes.UpdateInterval = static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.NumBuffers = 2;
+ return bufferAttributes;
+}
+
+
+namespace
+{
+struct DebugRealtimeThreadIdGuard
+{
+ std::atomic<uint32> &ThreadID;
+ DebugRealtimeThreadIdGuard(std::atomic<uint32> &ThreadID)
+ : ThreadID(ThreadID)
+ {
+ ThreadID.store(GetCurrentThreadId());
+ }
+ ~DebugRealtimeThreadIdGuard()
+ {
+ ThreadID.store(0);
+ }
+};
+} // namespace
+
+
+void CASIODevice::RealtimeSampleRateDidChange(ASIO::SampleRate sRate) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(mpt::saturate_round<uint32>(sRate) == m_Settings.Samplerate)
+ {
+ // not different, ignore it
+ return;
+ }
+ m_UsedFeatures.fetch_or(AsioFeature::SampleRateChange);
+ if(static_cast<double>(m_Settings.Samplerate) * (1.0 - AsioSampleRateTolerance) <= sRate && sRate <= static_cast<double>(m_Settings.Samplerate) * (1.0 + AsioSampleRateTolerance))
+ {
+ // ignore slight differences which might be due to a unstable external ASIO clock source
+ return;
+ }
+ // play safe and close the device
+ RequestClose();
+}
+
+
+void CASIODevice::RealtimeRequestDeferredProcessing(bool deferred) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ if(deferred)
+ {
+ m_UsedFeatures.fetch_or(AsioFeature::DeferredProcess);
+ }
+ m_DeferredProcessing = deferred;
+}
+
+
+void CASIODevice::RealtimeTimeInfo(ASIO::Time asioTime) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ if(m_Settings.UseHardwareTiming)
+ {
+ SoundDevice::TimeInfo timeInfo;
+ if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSamplePositionValid) && (asioTime.timeInfo.flags & ASIO::TimeInfoFlagSystemTimeValid))
+ {
+ double speed = 1.0;
+ if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSpeedValid) && (asioTime.timeInfo.speed > 0.0))
+ {
+ speed = asioTime.timeInfo.speed;
+ } else if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSampleRateValid) && (asioTime.timeInfo.sampleRate > 0.0))
+ {
+ speed *= asioTime.timeInfo.sampleRate / m_Settings.Samplerate;
+ }
+ timeInfo.SyncPointStreamFrames = asioTime.timeInfo.samplePosition - m_StreamPositionOffset;
+ timeInfo.SyncPointSystemTimestamp = asioTime.timeInfo.systemTime;
+ timeInfo.Speed = speed;
+ } else
+ { // spec violation or nothing provided at all, better to estimate this stuff ourselves
+ const uint64 asioNow = CallbackLockedGetReferenceClockNowNanoseconds();
+ timeInfo.SyncPointStreamFrames = m_TotalFramesWritten + m_nAsioBufferLen - m_StreamPositionOffset;
+ timeInfo.SyncPointSystemTimestamp = asioNow + mpt::saturate_round<int64>(m_BufferLatency * 1000.0 * 1000.0 * 1000.0);
+ timeInfo.Speed = 1.0;
+ }
+ timeInfo.RenderStreamPositionBefore = StreamPositionFromFrames(m_TotalFramesWritten - m_StreamPositionOffset);
+ timeInfo.RenderStreamPositionAfter = StreamPositionFromFrames(m_TotalFramesWritten - m_StreamPositionOffset + m_nAsioBufferLen);
+ timeInfo.Latency = GetEffectiveBufferAttributes().Latency;
+ SetTimeInfo(timeInfo);
+ }
+}
+
+
+void CASIODevice::RealtimeBufferSwitch(ASIO::BufferIndex bufferIndex) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeferredBufferSwitchDispatcher && m_DeferredProcessing)
+ {
+ m_DeferredBufferSwitchDispatcher->Dispatch(bufferIndex);
+ } else
+ {
+ RealtimeBufferSwitchImpl(bufferIndex);
+ }
+}
+
+
+void CASIODevice::RealtimeBufferSwitchImpl(ASIO::BufferIndex bufferIndex) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ m_BufferIndex = bufferIndex;
+ bool rendersilence = m_RenderSilence;
+ m_RenderingSilence = rendersilence;
+ if(rendersilence)
+ {
+ m_StreamPositionOffset += m_nAsioBufferLen;
+ FillAsioBuffer(false);
+ } else
+ {
+ CallbackFillAudioBufferLocked();
+ }
+ m_TotalFramesWritten += m_nAsioBufferLen;
+}
+
+
+mpt::ustring CASIODevice::AsioFeaturesToString(AsioFeatures features)
+{
+ std::vector<mpt::ustring> results;
+ if(features & AsioFeature::ResetRequest)
+ {
+ results.push_back(MPT_USTRING("reset"));
+ }
+ if(features & AsioFeature::ResyncRequest)
+ {
+ results.push_back(MPT_USTRING("resync"));
+ }
+ if(features & AsioFeature::BufferSizeChange)
+ {
+ results.push_back(MPT_USTRING("buffer"));
+ }
+ if(features & AsioFeature::Overload)
+ {
+ results.push_back(MPT_USTRING("load"));
+ }
+ if(features & AsioFeature::SampleRateChange)
+ {
+ results.push_back(MPT_USTRING("srate"));
+ }
+ if(features & AsioFeature::DeferredProcess)
+ {
+ results.push_back(MPT_USTRING("deferred"));
+ }
+ return mpt::join(results, MPT_USTRING(","));
+}
+
+
+bool CASIODevice::DebugIsFragileDevice() const
+{
+ return true;
+}
+
+
+bool CASIODevice::DebugInRealtimeCallback() const
+{
+ return GetCurrentThreadId() == m_DebugRealtimeThreadID.load();
+}
+
+
+SoundDevice::Statistics CASIODevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_BufferLatency;
+ result.LastUpdateInterval = static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ result.text = mpt::ustring();
+ const AsioFeatures unsupported = AsioFeature::Overload | AsioFeature::BufferSizeChange | AsioFeature::SampleRateChange;
+ AsioFeatures usedFeatures = m_UsedFeatures.fetch_or(0);
+ AsioFeatures unsupportedFeatues = usedFeatures & unsupported;
+ if(unsupportedFeatues)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("WARNING: unsupported features: {}")(AsioFeaturesToString(unsupportedFeatues));
+ } else if(usedFeatures)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("OK, features used: {}")(AsioFeaturesToString(m_UsedFeatures));
+ } else
+ {
+ result.text = MPT_USTRING("OK.");
+ }
+ return result;
+}
+
+
+void CASIODevice::MessageResetRequest() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::ResetRequest);
+ RequestReset();
+}
+
+bool CASIODevice::MessageBufferSizeChange(ASIO::Long newSize) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ MPT_UNUSED(newSize);
+ m_UsedFeatures.fetch_or(AsioFeature::BufferSizeChange);
+ // We do not support ASIO::MessageSelector::BufferSizeChange.
+ // This should cause a driver to send a ASIO::MessageSelector::ResetRequest.
+ return false;
+}
+
+bool CASIODevice::MessageResyncRequest() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::ResyncRequest);
+ RequestRestart();
+ return true;
+}
+
+void CASIODevice::MessageLatenciesChanged() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AsioRequest.fetch_or(AsioRequest::LatenciesChanged);
+}
+
+ASIO::Long CASIODevice::MessageMMCCommand(ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Long result = 0;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: MMCCommand(value={}, message={}, opt={}) => result={}")(value, reinterpret_cast<std::uintptr_t>(message), opt ? mpt::format<mpt::ustring>::val(*opt) : MPT_USTRING("NULL"), result));
+ return result;
+}
+
+void CASIODevice::MessageOverload() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::Overload);
+}
+
+ASIO::Long CASIODevice::MessageUnknown(ASIO::MessageSelector selector, ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Long result = 0;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: AsioMessage(selector={}, value={}, message={}, opt={}) => result={}")(mpt::to_underlying(selector), value, reinterpret_cast<std::uintptr_t>(message), opt ? mpt::format<mpt::ustring>::val(*opt) : MPT_USTRING("NULL"), result));
+ return result;
+}
+
+
+void CASIODevice::ExceptionHandler(const char *func)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ try
+ {
+ throw; // rethrow
+#if defined(MODPLUG_TRACKER)
+ } catch(const ASIO::Windows::SEH::DriverCrash &e)
+ {
+#if !defined(MPT_BUILD_WINESUPPORT)
+ ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason::Driver);
+#endif // !MPT_BUILD_WINESUPPORT
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Crash: {}!")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::transcode<mpt::ustring>(mpt::source_encoding, std::string(e.func()))));
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("ASIO Driver Crash: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, std::string(e.func()))));
+#endif // MODPLUG_TRACKER
+ } catch(const std::bad_alloc &)
+ {
+ mpt::throw_out_of_memory();
+ } catch(const ASIO::Windows::DriverLoadFailed &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Load: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const ASIO::Windows::DriverInitFailed &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Init: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const ASIO::Error &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Error: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const std::exception &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Exception: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(...)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Unknown Exception")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func)));
+ }
+}
+
+
+SoundDevice::Caps CASIODevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+
+ caps.Available = true;
+ caps.CanUpdateInterval = false;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = false;
+ caps.CanKeepDeviceRunning = true;
+ caps.CanUseHardwareTiming = true;
+ caps.CanChannelMapping = true;
+ caps.CanInput = true;
+ caps.HasNamedInputSources = true;
+ caps.CanDriverPanel = true;
+
+ caps.LatencyMin = 0.000001; // 1 us
+ caps.LatencyMax = 0.5; // 500 ms
+ caps.UpdateIntervalMin = 0.0; // disabled
+ caps.UpdateIntervalMax = 0.0; // disabled
+
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CASIODevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ SoundDevice::DynamicCaps caps;
+
+ TemporaryASIODriverOpener opener(*this);
+ if(!IsDriverOpen())
+ {
+ m_DeviceUnavailableOnOpen = true;
+ return caps;
+ }
+
+ try
+ {
+ ASIO::SampleRate samplerate = AsioDriver()->getSampleRate();
+ if(samplerate > 0.0)
+ {
+ caps.currentSampleRate = mpt::saturate_round<uint32>(samplerate);
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+
+ for(size_t i = 0; i < baseSampleRates.size(); i++)
+ {
+ try
+ {
+ if(AsioDriver()->canSampleRate(static_cast<ASIO::SampleRate>(baseSampleRates[i])))
+ {
+ caps.supportedSampleRates.push_back(baseSampleRates[i]);
+ caps.supportedExclusiveSampleRates.push_back(baseSampleRates[i]);
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ }
+
+ try
+ {
+ ASIO::Channels channels = AsioDriver()->getChannels();
+ if(!((channels.Input > 0) || (channels.Output > 0)))
+ {
+ m_DeviceUnavailableOnOpen = true;
+ }
+ for(ASIO::Long i = 0; i < channels.Output; ++i)
+ {
+ mpt::ustring name = mpt::format<mpt::ustring>::dec(i);
+ try
+ {
+ ASIO::ChannelInfo channelInfo = AsioDriver()->getChannelInfo(i, false);
+ name = mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(channelInfo.name));
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ caps.channelNames.push_back(name);
+ }
+ for(ASIO::Long i = 0; i < channels.Input; ++i)
+ {
+ mpt::ustring name = mpt::format<mpt::ustring>::dec(i);
+ try
+ {
+ ASIO::ChannelInfo channelInfo = AsioDriver()->getChannelInfo(i, true);
+ name = mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(channelInfo.name));
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(i), name));
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ return caps;
+}
+
+
+bool CASIODevice::OpenDriverSettings()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ TemporaryASIODriverOpener opener(*this);
+ if(!IsDriverOpen())
+ {
+ return false;
+ }
+ try
+ {
+ result = AsioDriver()->controlPanel();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return false;
+ }
+ return result;
+}
+
+
+#endif // MPT_WITH_ASIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp
new file mode 100644
index 00000000..64485328
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifdef MPT_WITH_ASIO
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <cassert>
+
+#include <ASIOModern/ASIO.hpp>
+#include <ASIOModern/ASIOSystemWindows.hpp>
+
+#if defined(MODPLUG_TRACKER)
+#if !defined(MPT_BUILD_WINESUPPORT)
+#include "../mptrack/ExceptionHandler.h"
+#endif // !MPT_BUILD_WINESUPPORT
+#endif // MODPLUG_TRACKER
+
+#endif // MPT_WITH_ASIO
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#ifdef MPT_WITH_ASIO
+
+
+class ASIOException
+ : public std::runtime_error
+{
+public:
+ ASIOException(const std::string &msg)
+ : std::runtime_error(msg)
+ {
+ return;
+ }
+};
+
+class CASIODevice
+ : public SoundDevice::Base
+ , private ASIO::Driver::CallbackHandler
+{
+
+ friend class TemporaryASIODriverOpener;
+
+protected:
+ std::unique_ptr<ASIO::Windows::IBufferSwitchDispatcher> m_DeferredBufferSwitchDispatcher;
+ std::unique_ptr<ASIO::Driver> m_Driver;
+
+#if defined(MODPLUG_TRACKER) && !defined(MPT_BUILD_WINESUPPORT)
+ using CrashContext = ExceptionHandler::Context;
+ using CrashContextGuard = ExceptionHandler::ContextSetter;
+#else // !(MODPLUG_TRACKER && !MPT_BUILD_WINESUPPORT)
+ struct CrashContext
+ {
+ void SetDescription(mpt::ustring)
+ {
+ return;
+ }
+ };
+ struct CrashContextGuard
+ {
+ CrashContextGuard(CrashContext *)
+ {
+ return;
+ }
+ };
+#endif // MODPLUG_TRACKER && !MPT_BUILD_WINESUPPORT
+ CrashContext m_Ectx;
+
+ class ASIODriverWithContext
+ {
+ private:
+ ASIO::Driver *m_Driver;
+ CrashContextGuard m_Guard;
+
+ public:
+ ASIODriverWithContext(ASIO::Driver *driver, CrashContext *ectx)
+ : m_Driver(driver)
+ , m_Guard(ectx)
+ {
+ assert(driver);
+ assert(ectx);
+ }
+ ASIODriverWithContext(const ASIODriverWithContext &) = delete;
+ ASIODriverWithContext &operator=(const ASIODriverWithContext &) = delete;
+ ASIO::Driver *operator->()
+ {
+ return m_Driver;
+ }
+ };
+
+ ASIODriverWithContext AsioDriver()
+ {
+ assert(m_Driver);
+ return ASIODriverWithContext{m_Driver.get(), &m_Ectx};
+ }
+
+ double m_BufferLatency;
+ ASIO::Long m_nAsioBufferLen;
+ std::vector<ASIO::BufferInfo> m_BufferInfo;
+ bool m_BuffersCreated;
+ std::vector<ASIO::ChannelInfo> m_ChannelInfo;
+ std::vector<double> m_SampleBufferDouble;
+ std::vector<float> m_SampleBufferFloat;
+ std::vector<int16> m_SampleBufferInt16;
+ std::vector<int24> m_SampleBufferInt24;
+ std::vector<int32> m_SampleBufferInt32;
+ std::vector<double> m_SampleInputBufferDouble;
+ std::vector<float> m_SampleInputBufferFloat;
+ std::vector<int16> m_SampleInputBufferInt16;
+ std::vector<int24> m_SampleInputBufferInt24;
+ std::vector<int32> m_SampleInputBufferInt32;
+ bool m_CanOutputReady;
+
+ bool m_DeviceRunning;
+ uint64 m_TotalFramesWritten;
+ bool m_DeferredProcessing;
+ ASIO::BufferIndex m_BufferIndex;
+ std::atomic<bool> m_RenderSilence;
+ std::atomic<bool> m_RenderingSilence;
+
+ int64 m_StreamPositionOffset;
+
+ using AsioRequests = uint8;
+ struct AsioRequest
+ {
+ enum AsioRequestEnum : AsioRequests
+ {
+ LatenciesChanged = 1 << 0,
+ };
+ };
+ std::atomic<AsioRequests> m_AsioRequest;
+
+ using AsioFeatures = uint16;
+ struct AsioFeature
+ {
+ enum AsioFeatureEnum : AsioFeatures
+ {
+ ResetRequest = 1 << 0,
+ ResyncRequest = 1 << 1,
+ BufferSizeChange = 1 << 2,
+ Overload = 1 << 3,
+ SampleRateChange = 1 << 4,
+ DeferredProcess = 1 << 5,
+ };
+ };
+ mutable std::atomic<AsioFeatures> m_UsedFeatures;
+ static mpt::ustring AsioFeaturesToString(AsioFeatures features);
+
+ mutable std::atomic<uint32> m_DebugRealtimeThreadID;
+
+ void SetRenderSilence(bool silence, bool wait = false);
+
+public:
+ CASIODevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CASIODevice();
+
+private:
+ void InitMembers();
+ bool HandleRequests(); // return true if any work has been done
+ void UpdateLatency();
+
+ void InternalStopImpl(bool force);
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_BuffersCreated; }
+
+ bool InternalIsPlayingSilence() const;
+ void InternalStopAndAvoidPlayingSilence();
+ void InternalEndPlayingSilence();
+
+ bool OnIdle() { return HandleRequests(); }
+
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+ bool OpenDriverSettings();
+
+ bool DebugIsFragileDevice() const;
+ bool DebugInRealtimeCallback() const;
+
+ SoundDevice::Statistics GetStatistics() const;
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+protected:
+ void OpenDriver();
+ void CloseDriver();
+ bool IsDriverOpen() const { return (m_Driver != nullptr); }
+
+ bool InternalHasTimeInfo() const;
+
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+
+protected:
+ void FillAsioBuffer(bool useSource = true);
+
+private:
+ // CallbackHandler
+
+ void MessageResetRequest() noexcept override;
+ bool MessageBufferSizeChange(ASIO::Long newSize) noexcept override;
+ bool MessageResyncRequest() noexcept override;
+ void MessageLatenciesChanged() noexcept override;
+ ASIO::Long MessageMMCCommand(ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept override;
+ void MessageOverload() noexcept override;
+
+ ASIO::Long MessageUnknown(ASIO::MessageSelector selector, ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept override;
+
+ void RealtimeSampleRateDidChange(ASIO::SampleRate sRate) noexcept override;
+ void RealtimeRequestDeferredProcessing(bool value) noexcept override;
+ void RealtimeTimeInfo(ASIO::Time time) noexcept override;
+ void RealtimeBufferSwitch(ASIO::BufferIndex bufferIndex) noexcept override;
+
+ void RealtimeBufferSwitchImpl(ASIO::BufferIndex bufferIndex) noexcept;
+
+private:
+ void ExceptionHandler(const char *func);
+};
+
+#endif // MPT_WITH_ASIO
+
+} // namespace SoundDevice
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp
new file mode 100644
index 00000000..e72b2b69
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp
@@ -0,0 +1,457 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceBase.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+Base::Base(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : m_Logger(logger)
+ , m_Callback(nullptr)
+ , m_MessageReceiver(nullptr)
+ , m_Info(info)
+ , m_SysInfo(sysInfo)
+ , m_StreamPositionOutputFrames(0)
+ , m_RequestFlags(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ m_DeviceUnavailableOnOpen = false;
+
+ m_IsPlaying = false;
+ m_StreamPositionRenderFrames = 0;
+ m_StreamPositionOutputFrames = 0;
+
+ m_RequestFlags.store(0);
+}
+
+
+Base::~Base()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return;
+}
+
+
+SoundDevice::DynamicCaps Base::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps result;
+ result.supportedSampleRates = baseSampleRates;
+ return result;
+}
+
+
+bool Base::Init(const SoundDevice::AppInfo &appInfo)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsInited())
+ {
+ return true;
+ }
+ m_AppInfo = appInfo;
+ m_Caps = InternalGetDeviceCaps();
+ return m_Caps.Available;
+}
+
+
+bool Base::Open(const SoundDevice::Settings &settings)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsOpen())
+ {
+ Close();
+ }
+ m_Settings = settings;
+ if(m_Settings.Latency == 0.0) m_Settings.Latency = m_Caps.DefaultSettings.Latency;
+ if(m_Settings.UpdateInterval == 0.0) m_Settings.UpdateInterval = m_Caps.DefaultSettings.UpdateInterval;
+ m_Settings.Latency = std::clamp(m_Settings.Latency, m_Caps.LatencyMin, m_Caps.LatencyMax);
+ m_Settings.UpdateInterval = std::clamp(m_Settings.UpdateInterval, m_Caps.UpdateIntervalMin, m_Caps.UpdateIntervalMax);
+ m_Flags = SoundDevice::Flags();
+ m_DeviceUnavailableOnOpen = false;
+ m_RequestFlags.store(0);
+ return InternalOpen();
+}
+
+
+bool Base::Close()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return true;
+ }
+ Stop();
+ bool result = InternalClose();
+ m_RequestFlags.store(0);
+ return result;
+}
+
+
+uint64 Base::CallbackGetReferenceClockNowNanoseconds() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return 0;
+ }
+ uint64 result = m_Callback->SoundCallbackGetReferenceClockNowNanoseconds();
+ //MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("clock: {}")(result));
+ return result;
+}
+
+
+uint64 Base::CallbackLockedGetReferenceClockNowNanoseconds() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return 0;
+ }
+ uint64 result = m_Callback->SoundCallbackLockedGetReferenceClockNowNanoseconds();
+ //MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("clock-rt: {}")(result));
+ return result;
+}
+
+
+void Base::CallbackNotifyPreStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackPreStart();
+ }
+}
+
+
+void Base::CallbackNotifyPostStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackPostStop();
+ }
+}
+
+
+bool Base::CallbackIsLockedByCurrentThread() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return false;
+ }
+ return m_Callback->SoundCallbackIsLockedByCurrentThread();
+}
+
+
+void Base::CallbackFillAudioBufferLocked()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ CallbackLockedGuard lock(*m_Callback);
+ InternalFillAudioBuffer();
+ }
+}
+
+
+void Base::CallbackLockedAudioReadPrepare(std::size_t numFrames, std::size_t framesLatency)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InternalHasTimeInfo())
+ {
+ SoundDevice::TimeInfo timeInfo;
+ if(InternalHasGetStreamPosition())
+ {
+ timeInfo.SyncPointStreamFrames = InternalHasGetStreamPosition();
+ timeInfo.SyncPointSystemTimestamp = CallbackLockedGetReferenceClockNowNanoseconds();
+ timeInfo.Speed = 1.0;
+ } else
+ {
+ timeInfo.SyncPointStreamFrames = m_StreamPositionRenderFrames + numFrames;
+ timeInfo.SyncPointSystemTimestamp = CallbackLockedGetReferenceClockNowNanoseconds() + mpt::saturate_round<int64>(GetEffectiveBufferAttributes().Latency * 1000000000.0);
+ timeInfo.Speed = 1.0;
+ }
+ timeInfo.RenderStreamPositionBefore = StreamPositionFromFrames(m_StreamPositionRenderFrames);
+ timeInfo.RenderStreamPositionAfter = StreamPositionFromFrames(m_StreamPositionRenderFrames + numFrames);
+ timeInfo.Latency = GetEffectiveBufferAttributes().Latency;
+ SetTimeInfo(timeInfo);
+ }
+ m_StreamPositionRenderFrames += numFrames;
+ if(!InternalHasGetStreamPosition() && !InternalHasTimeInfo())
+ {
+ m_StreamPositionOutputFrames = m_StreamPositionRenderFrames - framesLatency;
+ } else
+ {
+ // unused, no locking
+ m_StreamPositionOutputFrames = 0;
+ }
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcessPrepare(m_TimeInfo);
+ }
+}
+
+
+template <typename Tsample>
+void Base::CallbackLockedAudioProcessImpl(Tsample *buffer, const Tsample *inputBuffer, std::size_t numFrames)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(numFrames <= 0)
+ {
+ return;
+ }
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcess(GetBufferFormat(), numFrames, buffer, inputBuffer);
+ }
+}
+
+void Base::CallbackLockedAudioProcess(uint8 *buffer, const uint8 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Unsigned8);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int8 *buffer, const int8 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int8);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int16 *buffer, const int16 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int16);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int24 *buffer, const int24 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int24);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int32 *buffer, const int32 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int32);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(float *buffer, const float *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Float32);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(double *buffer, const double *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Float64);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcessVoid(void *buffer, const void *inputBuffer, std::size_t numFrames)
+{
+ switch(GetBufferFormat().sampleFormat)
+ {
+ case SampleFormat::Unsigned8:
+ CallbackLockedAudioProcess(static_cast<uint8 *>(buffer), static_cast<const uint8 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int8:
+ CallbackLockedAudioProcess(static_cast<int8 *>(buffer), static_cast<const int8 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int16:
+ CallbackLockedAudioProcess(static_cast<int16 *>(buffer), static_cast<const int16 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int24:
+ CallbackLockedAudioProcess(static_cast<int24 *>(buffer), static_cast<const int24 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int32:
+ CallbackLockedAudioProcess(static_cast<int32 *>(buffer), static_cast<const int32 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Float32:
+ CallbackLockedAudioProcess(static_cast<float *>(buffer), static_cast<const float *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Float64:
+ CallbackLockedAudioProcess(static_cast<double *>(buffer), static_cast<const double *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Invalid:
+ // nothing
+ break;
+ }
+}
+
+
+void Base::CallbackLockedAudioProcessDone()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcessDone(m_TimeInfo);
+ }
+}
+
+
+void Base::SendDeviceMessage(LogLevel level, const mpt::ustring &str)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ MPT_LOG(GetLogger(), level, "sounddev", str);
+ if(m_MessageReceiver)
+ {
+ m_MessageReceiver->SoundDeviceMessage(level, str);
+ }
+}
+
+
+bool Base::Start()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return false;
+ }
+ if(!IsPlaying())
+ {
+ m_StreamPositionRenderFrames = 0;
+ {
+ m_StreamPositionOutputFrames = 0;
+ }
+ CallbackNotifyPreStart();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ if(!InternalStart())
+ {
+ CallbackNotifyPostStop();
+ return false;
+ }
+ m_IsPlaying = true;
+ }
+ return true;
+}
+
+
+void Base::Stop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(IsPlaying())
+ {
+ InternalStop();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ CallbackNotifyPostStop();
+ m_IsPlaying = false;
+ m_StreamPositionOutputFrames = 0;
+ m_StreamPositionRenderFrames = 0;
+ }
+}
+
+
+void Base::StopAndAvoidPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(!IsPlaying())
+ {
+ return;
+ }
+ InternalStopAndAvoidPlayingSilence();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ CallbackNotifyPostStop();
+ m_IsPlaying = false;
+ m_StreamPositionOutputFrames = 0;
+ m_StreamPositionRenderFrames = 0;
+}
+
+
+void Base::EndPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(IsPlaying())
+ {
+ return;
+ }
+ InternalEndPlayingSilence();
+}
+
+
+SoundDevice::StreamPosition Base::GetStreamPosition() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return StreamPosition();
+ }
+ int64 frames = 0;
+ if(InternalHasGetStreamPosition())
+ {
+ frames = InternalGetStreamPositionFrames();
+ } else if(InternalHasTimeInfo())
+ {
+ const uint64 now = CallbackGetReferenceClockNowNanoseconds();
+ const SoundDevice::TimeInfo timeInfo = GetTimeInfo();
+ frames = mpt::saturate_round<int64>(
+ timeInfo.SyncPointStreamFrames + (static_cast<double>(static_cast<int64>(now - timeInfo.SyncPointSystemTimestamp)) * timeInfo.Speed * m_Settings.Samplerate * (1.0 / (1000.0 * 1000.0))));
+ } else
+ {
+ frames = m_StreamPositionOutputFrames;
+ }
+ return StreamPositionFromFrames(frames);
+}
+
+
+SoundDevice::Statistics Base::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_Settings.Latency;
+ result.LastUpdateInterval = m_Settings.UpdateInterval;
+ result.text = mpt::ustring();
+ return result;
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp
new file mode 100644
index 00000000..72eee1df
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/mutex/mutex.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <atomic>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+class Base
+ : public IBase
+{
+
+private:
+ class CallbackLockedGuard
+ {
+ private:
+ ICallback &m_Callback;
+
+ public:
+ CallbackLockedGuard(ICallback &callback)
+ : m_Callback(callback)
+ {
+ m_Callback.SoundCallbackLock();
+ }
+ ~CallbackLockedGuard()
+ {
+ m_Callback.SoundCallbackUnlock();
+ }
+ };
+
+protected:
+ ILogger &m_Logger;
+
+private:
+ SoundDevice::ICallback *m_Callback;
+ SoundDevice::IMessageReceiver *m_MessageReceiver;
+
+ const SoundDevice::Info m_Info;
+
+private:
+ SoundDevice::Caps m_Caps;
+
+protected:
+ SoundDevice::SysInfo m_SysInfo;
+ SoundDevice::AppInfo m_AppInfo;
+ SoundDevice::Settings m_Settings;
+ SoundDevice::Flags m_Flags;
+ bool m_DeviceUnavailableOnOpen;
+
+private:
+ bool m_IsPlaying;
+
+ SoundDevice::TimeInfo m_TimeInfo;
+
+ int64 m_StreamPositionRenderFrames; // only updated or read in audio CALLBACK or when device is stopped. requires no further locking
+
+ std::atomic<int64> m_StreamPositionOutputFrames;
+
+ std::atomic<uint32> m_RequestFlags;
+
+public:
+ ILogger &GetLogger() const { return m_Logger; }
+ SoundDevice::SysInfo GetSysInfo() const { return m_SysInfo; }
+ SoundDevice::AppInfo GetAppInfo() const { return m_AppInfo; }
+
+protected:
+ SoundDevice::Type GetDeviceType() const { return m_Info.type; }
+ mpt::ustring GetDeviceInternalID() const { return m_Info.internalID; }
+ SoundDevice::Identifier GetDeviceIdentifier() const { return m_Info.GetIdentifier(); }
+
+ virtual void InternalFillAudioBuffer() = 0;
+
+ uint64 CallbackGetReferenceClockNowNanoseconds() const;
+ void CallbackNotifyPreStart();
+ void CallbackNotifyPostStop();
+ bool CallbackIsLockedByCurrentThread() const;
+ void CallbackFillAudioBufferLocked();
+ uint64 CallbackLockedGetReferenceClockNowNanoseconds() const;
+ void CallbackLockedAudioReadPrepare(std::size_t numFrames, std::size_t framesLatency);
+ template <typename Tsample>
+ void CallbackLockedAudioProcessImpl(Tsample *buffer, const Tsample *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(uint8 *buffer, const uint8 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int8 *buffer, const int8 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int16 *buffer, const int16 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int24 *buffer, const int24 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int32 *buffer, const int32 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(float *buffer, const float *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(double *buffer, const double *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcessVoid(void *buffer, const void *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcessDone();
+
+ void RequestClose() { m_RequestFlags.fetch_or(RequestFlagClose); }
+ void RequestReset() { m_RequestFlags.fetch_or(RequestFlagReset); }
+ void RequestRestart() { m_RequestFlags.fetch_or(RequestFlagRestart); }
+
+ void SendDeviceMessage(LogLevel level, const mpt::ustring &str);
+
+protected:
+ void SetTimeInfo(SoundDevice::TimeInfo timeInfo) { m_TimeInfo = timeInfo; }
+
+ SoundDevice::StreamPosition StreamPositionFromFrames(int64 frames) const { return SoundDevice::StreamPosition{frames, static_cast<double>(frames) / static_cast<double>(m_Settings.Samplerate)}; }
+
+ virtual bool InternalHasTimeInfo() const { return false; }
+
+ virtual bool InternalHasGetStreamPosition() const { return false; }
+ virtual int64 InternalGetStreamPositionFrames() const { return 0; }
+
+ virtual bool InternalIsOpen() const = 0;
+
+ virtual bool InternalOpen() = 0;
+ virtual bool InternalStart() = 0;
+ virtual void InternalStop() = 0;
+ virtual bool InternalClose() = 0;
+
+ virtual bool InternalIsPlayingSilence() const { return false; }
+ virtual void InternalStopAndAvoidPlayingSilence() { InternalStop(); }
+ virtual void InternalEndPlayingSilence() { return; }
+
+ virtual SoundDevice::Caps InternalGetDeviceCaps() = 0;
+
+ virtual SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const = 0;
+
+protected:
+ Base(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+
+public:
+ virtual ~Base();
+
+ void SetCallback(SoundDevice::ICallback *callback) { m_Callback = callback; }
+ void SetMessageReceiver(SoundDevice::IMessageReceiver *receiver) { m_MessageReceiver = receiver; }
+
+ SoundDevice::Info GetDeviceInfo() const { return m_Info; }
+
+ SoundDevice::Caps GetDeviceCaps() const { return m_Caps; }
+ virtual SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+ bool Init(const SoundDevice::AppInfo &appInfo);
+ bool Open(const SoundDevice::Settings &settings);
+ bool Close();
+ bool Start();
+ void Stop();
+
+ FlagSet<RequestFlags> GetRequestFlags() const { return FlagSet<RequestFlags>(m_RequestFlags.load()); }
+
+ bool IsInited() const { return m_Caps.Available; }
+ bool IsOpen() const { return IsInited() && InternalIsOpen(); }
+ bool IsAvailable() const { return m_Caps.Available && !m_DeviceUnavailableOnOpen; }
+ bool IsPlaying() const { return m_IsPlaying; }
+
+ virtual bool IsPlayingSilence() const { return IsOpen() && !IsPlaying() && InternalIsPlayingSilence(); }
+ virtual void StopAndAvoidPlayingSilence();
+ virtual void EndPlayingSilence();
+
+ virtual bool OnIdle() { return false; }
+
+ SoundDevice::Settings GetSettings() const { return m_Settings; }
+ SampleFormat GetActualSampleFormat() const { return IsOpen() ? m_Settings.sampleFormat : SampleFormat(SampleFormat::Invalid); }
+ SoundDevice::BufferFormat GetBufferFormat() const
+ {
+ BufferFormat bufferFormat;
+ bufferFormat.Samplerate = m_Settings.Samplerate;
+ bufferFormat.Channels = m_Settings.Channels;
+ bufferFormat.InputChannels = m_Settings.InputChannels;
+ bufferFormat.sampleFormat = m_Settings.sampleFormat;
+ bufferFormat.WantsClippedOutput = m_Flags.WantsClippedOutput;
+ bufferFormat.DitherType = m_Settings.DitherType;
+ return bufferFormat;
+ }
+ SoundDevice::BufferAttributes GetEffectiveBufferAttributes() const { return (IsOpen() && IsPlaying()) ? InternalGetEffectiveBufferAttributes() : SoundDevice::BufferAttributes(); }
+
+ SoundDevice::TimeInfo GetTimeInfo() const { return m_TimeInfo; }
+ SoundDevice::StreamPosition GetStreamPosition() const;
+
+ virtual bool DebugIsFragileDevice() const { return false; }
+ virtual bool DebugInRealtimeCallback() const { return false; }
+
+ virtual SoundDevice::Statistics GetStatistics() const;
+
+ virtual bool OpenDriverSettings() { return false; };
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp
new file mode 100644
index 00000000..81ce7258
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/audio/span.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/Dither.hpp"
+#include "openmpt/soundbase/CopyMix.hpp"
+
+#include <variant>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+template <typename Tsample>
+class BufferIO
+{
+
+private:
+ mpt::audio_span_interleaved<const Tsample> const m_src;
+ mpt::audio_span_interleaved<Tsample> const m_dst;
+ std::size_t m_countFramesReadProcessed;
+ std::size_t m_countFramesWriteProcessed;
+ const BufferFormat m_bufferFormat;
+
+public:
+ inline BufferIO(Tsample *dst, const Tsample *src, std::size_t numFrames, BufferFormat bufferFormat)
+ : m_src(src, bufferFormat.InputChannels, numFrames)
+ , m_dst(dst, bufferFormat.Channels, numFrames)
+ , m_countFramesReadProcessed(0)
+ , m_countFramesWriteProcessed(0)
+ , m_bufferFormat(bufferFormat)
+ {
+ return;
+ }
+
+ template <typename audio_span_dst>
+ inline void Read(audio_span_dst dst)
+ {
+ assert(m_countFramesReadProcessed + dst.size_frames() <= m_src.size_frames());
+ ConvertBufferToBufferMixInternal(dst, mpt::make_audio_span_with_offset(m_src, m_countFramesReadProcessed), m_bufferFormat.InputChannels, dst.size_frames());
+ m_countFramesReadProcessed += dst.size_frames();
+ }
+
+ template <int fractionalBits, typename audio_span_dst>
+ inline void ReadFixedPoint(audio_span_dst dst)
+ {
+ assert(m_countFramesReadProcessed + dst.size_frames() <= m_src.size_frames());
+ ConvertBufferToBufferMixInternalFixed<fractionalBits>(dst, mpt::make_audio_span_with_offset(m_src, m_countFramesReadProcessed), m_bufferFormat.InputChannels, dst.size_frames());
+ m_countFramesReadProcessed += dst.size_frames();
+ }
+
+ template <typename audio_span_src, typename TDither>
+ inline void Write(audio_span_src src, TDither &dither)
+ {
+ assert(m_countFramesWriteProcessed + src.size_frames() <= m_dst.size_frames());
+ if(m_bufferFormat.WantsClippedOutput)
+ {
+ ConvertBufferMixInternalToBuffer<true>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ } else
+ {
+ ConvertBufferMixInternalToBuffer<false>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ }
+ m_countFramesWriteProcessed += src.size_frames();
+ }
+
+ template <int fractionalBits, typename audio_span_src, typename TDither>
+ inline void WriteFixedPoint(audio_span_src src, TDither &dither)
+ {
+ assert(m_countFramesWriteProcessed + src.size_frames() <= m_dst.size_frames());
+ if(m_bufferFormat.WantsClippedOutput)
+ {
+ ConvertBufferMixInternalFixedToBuffer<fractionalBits, true>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ } else
+ {
+ ConvertBufferMixInternalFixedToBuffer<fractionalBits, false>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ }
+ m_countFramesWriteProcessed += src.size_frames();
+ }
+
+ inline ~BufferIO()
+ {
+ // fill remaining buffer with silence
+ while(m_countFramesWriteProcessed < m_dst.size_frames())
+ {
+ for(std::size_t channel = 0; channel < m_dst.size_channels(); ++channel)
+ {
+ m_dst(channel, m_countFramesWriteProcessed) = SC::sample_cast<Tsample>(static_cast<int16>(0));
+ }
+ m_countFramesWriteProcessed += 1;
+ }
+ }
+};
+
+
+template <typename TDithers>
+class CallbackBuffer
+{
+private:
+ std::variant<
+ BufferIO<uint8>,
+ BufferIO<int8>,
+ BufferIO<int16>,
+ BufferIO<int24>,
+ BufferIO<int32>,
+ BufferIO<float>,
+ BufferIO<double>>
+ m_BufferIO;
+ TDithers &m_Dithers;
+ std::size_t m_NumFrames;
+
+public:
+ template <typename Tsample>
+ explicit inline CallbackBuffer(Tsample *dst, const Tsample *src, std::size_t numFrames, TDithers &dithers, BufferFormat bufferFormat)
+ : m_BufferIO(BufferIO<Tsample>{dst, src, numFrames, bufferFormat})
+ , m_Dithers(dithers)
+ , m_NumFrames(numFrames)
+ {
+ return;
+ }
+
+ inline std::size_t GetNumFrames() const
+ {
+ return m_NumFrames;
+ }
+
+ template <typename audio_span_dst>
+ inline void Read(audio_span_dst dst)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ bufferIO.Read(dst);
+ },
+ m_BufferIO);
+ }
+
+ template <int fractionalBits, typename audio_span_dst>
+ inline void ReadFixedPoint(audio_span_dst dst)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ bufferIO.template ReadFixedPoint<fractionalBits>(dst);
+ },
+ m_BufferIO);
+ }
+
+ template <typename audio_span_src>
+ inline void Write(audio_span_src src)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ std::visit(
+ [&](auto &ditherInstance)
+ {
+ bufferIO.Write(src, ditherInstance);
+ },
+ m_Dithers.Variant());
+ },
+ m_BufferIO);
+ }
+
+ template <int fractionalBits, typename audio_span_src>
+ inline void WriteFixedPoint(audio_span_src src)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ std::visit(
+ [&](auto &ditherInstance)
+ {
+ bufferIO.template WriteFixedPoint<fractionalBits>(src, ditherInstance);
+ },
+ m_Dithers.Variant());
+ },
+ m_BufferIO);
+ }
+};
+
+
+template <typename TDithers>
+class CallbackBufferHandler
+ : public ICallback
+{
+private:
+ TDithers m_Dithers;
+
+protected:
+ template <typename Trd>
+ explicit CallbackBufferHandler(Trd &rd)
+ : m_Dithers(rd)
+ {
+ return;
+ }
+
+protected:
+ inline TDithers &Dithers()
+ {
+ return m_Dithers;
+ }
+
+private:
+ template <typename Tsample>
+ inline void SoundCallbackLockedProcessImpl(BufferFormat bufferFormat, std::size_t numFrames, Tsample *buffer, const Tsample *inputBuffer)
+ {
+ CallbackBuffer<TDithers> callbackBuffer{buffer, inputBuffer, numFrames, m_Dithers, bufferFormat};
+ SoundCallbackLockedCallback(callbackBuffer);
+ }
+
+public:
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, uint8 *buffer, const uint8 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int8 *buffer, const int8 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int16 *buffer, const int16 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int24 *buffer, const int24 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int32 *buffer, const int32 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, float *buffer, const float *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, double *buffer, const double *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ virtual void SoundCallbackLockedCallback(CallbackBuffer<TDithers> &buffer) = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp
new file mode 100644
index 00000000..edefe115
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+struct StreamPosition
+{
+ int64 Frames = 0; // relative to Start()
+ double Seconds = 0.0; // relative to Start()
+};
+
+
+struct TimeInfo
+{
+
+ int64 SyncPointStreamFrames = 0;
+ uint64 SyncPointSystemTimestamp = 0;
+ double Speed = 1.0;
+
+ SoundDevice::StreamPosition RenderStreamPositionBefore;
+ SoundDevice::StreamPosition RenderStreamPositionAfter;
+ // int64 chunkSize = After - Before
+
+ double Latency = 0.0; // seconds
+};
+
+
+struct BufferFormat
+{
+ uint32 Samplerate;
+ uint32 Channels;
+ uint8 InputChannels;
+ SampleFormat sampleFormat;
+ bool WantsClippedOutput;
+ int32 DitherType;
+};
+
+
+class ICallback
+{
+public:
+ // main thread
+ virtual uint64 SoundCallbackGetReferenceClockNowNanoseconds() const = 0; // timeGetTime()*1000000 on Windows
+ virtual void SoundCallbackPreStart() = 0;
+ virtual void SoundCallbackPostStop() = 0;
+ virtual bool SoundCallbackIsLockedByCurrentThread() const = 0;
+ // audio thread
+ virtual void SoundCallbackLock() = 0;
+ virtual uint64 SoundCallbackLockedGetReferenceClockNowNanoseconds() const = 0; // timeGetTime()*1000000 on Windows
+ virtual void SoundCallbackLockedProcessPrepare(SoundDevice::TimeInfo timeInfo) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, uint8 *buffer, const uint8 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int8 *buffer, const int8 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int16 *buffer, const int16 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int24 *buffer, const int24 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int32 *buffer, const int32 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, float *buffer, const float *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, double *buffer, const double *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcessDone(SoundDevice::TimeInfo timeInfo) = 0;
+ virtual void SoundCallbackUnlock() = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp
new file mode 100644
index 00000000..5785d4bb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp
@@ -0,0 +1,572 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceDirectSound.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_DIRECTSOUND)
+
+
+
+namespace
+{
+struct DevicesAndLoggerAndSysInfo
+{
+ std::vector<SoundDevice::Info> devices;
+ ILogger *logger;
+ SoundDevice::SysInfo sysInfo;
+};
+} // namespace
+
+
+static BOOL WINAPI DSEnumCallback(GUID *lpGuid, LPCTSTR lpstrDescription, LPCTSTR lpstrDriver, LPVOID lpContext)
+{
+ DevicesAndLoggerAndSysInfo &devicesAndLoggerAndSysInfo = *(DevicesAndLoggerAndSysInfo *)lpContext;
+ std::vector<SoundDevice::Info> &devices = devicesAndLoggerAndSysInfo.devices;
+ ILogger &logger = *devicesAndLoggerAndSysInfo.logger;
+ SoundDevice::SysInfo &sysInfo = devicesAndLoggerAndSysInfo.sysInfo;
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ if(!lpstrDescription)
+ {
+ return TRUE;
+ }
+ GUID guid = (lpGuid ? *lpGuid : GUID());
+ SoundDevice::Info info;
+ info.type = TypeDSOUND;
+ info.default_ = (!lpGuid ? Info::Default::Managed : Info::Default::None);
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::GUIDToString(guid));
+ info.name = mpt::transcode<mpt::ustring>(mpt::winstring(lpstrDescription));
+ if(lpstrDriver)
+ {
+ info.extraData[MPT_USTRING("DriverName")] = mpt::transcode<mpt::ustring>(mpt::winstring(lpstrDriver));
+ }
+ if(lpGuid)
+ {
+ info.extraData[MPT_USTRING("UUID")] = mpt::format<mpt::ustring>::val(mpt::UUID(guid));
+ }
+ info.apiName = MPT_USTRING("DirectSound");
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsWine() ? Info::Compatible::Yes : Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::OutputOnly,
+ Info::Mixing::Software,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ devices.push_back(info);
+ return TRUE;
+}
+
+
+std::vector<SoundDevice::Info> CDSoundDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ DevicesAndLoggerAndSysInfo devicesAndLoggerAndSysInfo = {std::vector<SoundDevice::Info>(), &logger, sysInfo};
+ DirectSoundEnumerate(DSEnumCallback, &devicesAndLoggerAndSysInfo);
+ return devicesAndLoggerAndSysInfo.devices;
+}
+
+
+CDSoundDevice::CDSoundDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : CSoundDeviceWithThread(logger, info, sysInfo)
+ , m_piDS(NULL)
+ , m_pPrimary(NULL)
+ , m_pMixBuffer(NULL)
+ , m_nDSoundBufferSize(0)
+ , m_bMixRunning(FALSE)
+ , m_dwWritePos(0)
+ , m_StatisticLatencyFrames(0)
+ , m_StatisticPeriodFrames(0)
+{
+ return;
+}
+
+
+CDSoundDevice::~CDSoundDevice()
+{
+ Close();
+}
+
+
+SoundDevice::Caps CDSoundDevice::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = true;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use primary buffer");
+ caps.DefaultSettings.sampleFormat = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista)) ? SampleFormat::Float32 : SampleFormat::Int16;
+ IDirectSound *dummy = nullptr;
+ IDirectSound *ds = nullptr;
+ if(m_piDS)
+ {
+ ds = m_piDS;
+ } else
+ {
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &dummy, NULL) != DS_OK)
+ {
+ return caps;
+ }
+ if(!dummy)
+ {
+ return caps;
+ }
+ ds = dummy;
+ }
+ DSCAPS dscaps = {};
+ dscaps.dwSize = sizeof(dscaps);
+ if(DS_OK == ds->GetCaps(&dscaps))
+ {
+ if(!(dscaps.dwFlags & DSCAPS_EMULDRIVER))
+ {
+ caps.CanExclusiveMode = true;
+ }
+ }
+ if(dummy)
+ {
+ dummy->Release();
+ dummy = nullptr;
+ }
+ ds = nullptr;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CDSoundDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ IDirectSound *dummy = nullptr;
+ IDirectSound *ds = nullptr;
+ if(m_piDS)
+ {
+ ds = m_piDS;
+ } else
+ {
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &dummy, NULL) != DS_OK)
+ {
+ return caps;
+ }
+ if(!dummy)
+ {
+ return caps;
+ }
+ ds = dummy;
+ }
+ DSCAPS dscaps = {};
+ dscaps.dwSize = sizeof(dscaps);
+ if(DS_OK == ds->GetCaps(&dscaps))
+ {
+ if(dscaps.dwMaxSecondarySampleRate == 0)
+ {
+ // nothing known about supported sample rates
+ } else
+ {
+ for(const auto &rate : baseSampleRates)
+ {
+ if(dscaps.dwMinSecondarySampleRate <= rate && rate <= dscaps.dwMaxSecondarySampleRate)
+ {
+ caps.supportedSampleRates.push_back(rate);
+ caps.supportedExclusiveSampleRates.push_back(rate);
+ }
+ }
+ }
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ // Vista
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ } else if(!(dscaps.dwFlags & DSCAPS_EMULDRIVER))
+ {
+ // XP wdm
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(dscaps.dwFlags & DSCAPS_PRIMARY8BIT)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Unsigned8);
+ }
+ if(dscaps.dwFlags & DSCAPS_PRIMARY16BIT)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ if(caps.supportedExclusiveModeSampleFormats.empty())
+ {
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ } else
+ {
+ // XP vdx
+ // nothing, announce all, fail later
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ }
+ if(dummy)
+ {
+ dummy->Release();
+ dummy = nullptr;
+ }
+ ds = nullptr;
+ return caps;
+}
+
+
+bool CDSoundDevice::InternalOpen()
+{
+ if(m_Settings.InputChannels > 0) return false;
+
+ WAVEFORMATEXTENSIBLE wfext;
+ if(!FillWaveFormatExtensible(wfext, m_Settings)) return false;
+ WAVEFORMATEX *pwfx = &wfext.Format;
+
+ const uint32 bytesPerFrame = static_cast<uint32>(m_Settings.GetBytesPerFrame());
+
+ DSBUFFERDESC dsbd;
+ DSBCAPS dsc;
+
+ if(m_piDS) return true;
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &m_piDS, NULL) != DS_OK) return false;
+ if(!m_piDS) return false;
+ if(m_piDS->SetCooperativeLevel(m_AppInfo.GetHWND(), m_Settings.ExclusiveMode ? DSSCL_WRITEPRIMARY : DSSCL_PRIORITY) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ m_bMixRunning = FALSE;
+ m_nDSoundBufferSize = mpt::saturate_round<int32>(m_Settings.Latency * pwfx->nAvgBytesPerSec);
+ m_nDSoundBufferSize = mpt::align_up<uint32>(m_nDSoundBufferSize, bytesPerFrame);
+ m_nDSoundBufferSize = std::clamp(m_nDSoundBufferSize, mpt::align_up<uint32>(DSBSIZE_MIN, bytesPerFrame), mpt::align_down<uint32>(DSBSIZE_MAX, bytesPerFrame));
+ if(!m_Settings.ExclusiveMode)
+ {
+ // Set the format of the primary buffer
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
+ dsbd.dwBufferBytes = 0;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = NULL;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pPrimary, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ if(m_pPrimary->SetFormat(pwfx) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ // Create the secondary buffer
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ dsbd.dwBufferBytes = m_nDSoundBufferSize;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = pwfx;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pMixBuffer, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ } else
+ {
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ dsbd.dwBufferBytes = 0;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = NULL;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pPrimary, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ if(m_pPrimary->SetFormat(pwfx) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ dsc.dwSize = sizeof(dsc);
+ if(m_pPrimary->GetCaps(&dsc) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ m_nDSoundBufferSize = dsc.dwBufferBytes;
+ m_pMixBuffer = m_pPrimary;
+ m_pMixBuffer->AddRef();
+ }
+ if(m_Settings.sampleFormat == SampleFormat::Int8)
+ {
+ m_Settings.sampleFormat = SampleFormat::Unsigned8;
+ }
+ LPVOID lpBuf1, lpBuf2;
+ DWORD dwSize1, dwSize2;
+ if(m_pMixBuffer->Lock(0, m_nDSoundBufferSize, &lpBuf1, &dwSize1, &lpBuf2, &dwSize2, 0) == DS_OK)
+ {
+ UINT zero = (pwfx->wBitsPerSample == 8) ? 0x80 : 0x00;
+ if((lpBuf1) && (dwSize1)) memset(lpBuf1, zero, dwSize1);
+ if((lpBuf2) && (dwSize2)) memset(lpBuf2, zero, dwSize2);
+ m_pMixBuffer->Unlock(lpBuf1, dwSize1, lpBuf2, dwSize2);
+ } else
+ {
+ DWORD dwStat = 0;
+ m_pMixBuffer->GetStatus(&dwStat);
+ if(dwStat & DSBSTATUS_BUFFERLOST) m_pMixBuffer->Restore();
+ }
+ m_dwWritePos = 0xFFFFFFFF;
+ SetWakeupInterval(std::min(m_Settings.UpdateInterval, m_nDSoundBufferSize / (2.0 * m_Settings.GetBytesPerSecond())));
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ return true;
+}
+
+
+bool CDSoundDevice::InternalClose()
+{
+ if(m_pMixBuffer)
+ {
+ m_pMixBuffer->Release();
+ m_pMixBuffer = NULL;
+ }
+ if(m_pPrimary)
+ {
+ m_pPrimary->Release();
+ m_pPrimary = NULL;
+ }
+ if(m_piDS)
+ {
+ m_piDS->Release();
+ m_piDS = NULL;
+ }
+ m_bMixRunning = FALSE;
+ return true;
+}
+
+
+void CDSoundDevice::StartFromSoundThread()
+{
+ // done in InternalFillAudioBuffer
+}
+
+
+void CDSoundDevice::StopFromSoundThread()
+{
+ if(m_pMixBuffer)
+ {
+ m_pMixBuffer->Stop();
+ }
+ m_bMixRunning = FALSE;
+}
+
+
+void CDSoundDevice::InternalFillAudioBuffer()
+{
+ if(!m_pMixBuffer)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_nDSoundBufferSize == 0)
+ {
+ RequestClose();
+ return;
+ }
+
+ DWORD dwLatency = 0;
+
+ for(int refillCount = 0; refillCount < 2; ++refillCount)
+ {
+ // Refill the buffer at most twice so we actually sleep some time when CPU is overloaded.
+
+ const uint32 bytesPerFrame = static_cast<uint32>(m_Settings.GetBytesPerFrame());
+
+ DWORD dwPlay = 0;
+ DWORD dwWrite = 0;
+ if(m_pMixBuffer->GetCurrentPosition(&dwPlay, &dwWrite) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+
+ uint32 dwBytes = m_nDSoundBufferSize / 2;
+ if(!m_bMixRunning)
+ {
+ // startup
+ m_dwWritePos = dwWrite;
+ dwLatency = 0;
+ } else
+ {
+ // running
+ dwLatency = (m_dwWritePos - dwPlay + m_nDSoundBufferSize) % m_nDSoundBufferSize;
+ dwLatency = (dwLatency + m_nDSoundBufferSize - 1) % m_nDSoundBufferSize + 1;
+ dwBytes = (dwPlay - m_dwWritePos + m_nDSoundBufferSize) % m_nDSoundBufferSize;
+ dwBytes = std::clamp(dwBytes, uint32(0), m_nDSoundBufferSize / 2); // limit refill amount to half the buffer size
+ }
+ dwBytes = dwBytes / bytesPerFrame * bytesPerFrame; // truncate to full frame
+ if(dwBytes < bytesPerFrame)
+ {
+ // ok, nothing to do
+ return;
+ }
+
+ void *buf1 = nullptr;
+ void *buf2 = nullptr;
+ DWORD dwSize1 = 0;
+ DWORD dwSize2 = 0;
+ HRESULT hr = m_pMixBuffer->Lock(m_dwWritePos, dwBytes, &buf1, &dwSize1, &buf2, &dwSize2, 0);
+ if(hr == DSERR_BUFFERLOST)
+ {
+ // buffer lost, restore buffer and try again, fail if it fails again
+ if(m_pMixBuffer->Restore() != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_pMixBuffer->Lock(m_dwWritePos, dwBytes, &buf1, &dwSize1, &buf2, &dwSize2, 0) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ } else if(hr != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+
+ CallbackLockedAudioReadPrepare(dwSize1 / bytesPerFrame + dwSize2 / bytesPerFrame, dwLatency / bytesPerFrame);
+
+ CallbackLockedAudioProcessVoid(buf1, nullptr, dwSize1 / bytesPerFrame);
+ CallbackLockedAudioProcessVoid(buf2, nullptr, dwSize2 / bytesPerFrame);
+
+ m_StatisticLatencyFrames.store(dwLatency / bytesPerFrame);
+ m_StatisticPeriodFrames.store(dwSize1 / bytesPerFrame + dwSize2 / bytesPerFrame);
+
+ if(m_pMixBuffer->Unlock(buf1, dwSize1, buf2, dwSize2) != DS_OK)
+ {
+ CallbackLockedAudioProcessDone();
+ RequestClose();
+ return;
+ }
+ m_dwWritePos += dwSize1 + dwSize2;
+ m_dwWritePos %= m_nDSoundBufferSize;
+
+ CallbackLockedAudioProcessDone();
+
+ DWORD dwStatus = 0;
+ m_pMixBuffer->GetStatus(&dwStatus);
+ if(!m_bMixRunning || !(dwStatus & DSBSTATUS_PLAYING))
+ {
+ if(!(dwStatus & DSBSTATUS_BUFFERLOST))
+ {
+ // start playing
+ hr = m_pMixBuffer->Play(0, 0, DSBPLAY_LOOPING);
+ } else
+ {
+ // buffer lost flag is set, do not try start playing, we know it will fail with DSERR_BUFFERLOST.
+ hr = DSERR_BUFFERLOST;
+ }
+ if(hr == DSERR_BUFFERLOST)
+ {
+ // buffer lost, restore buffer and try again, fail if it fails again
+ if(m_pMixBuffer->Restore() != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_pMixBuffer->Play(0, 0, DSBPLAY_LOOPING) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ } else if(hr != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ m_bMixRunning = TRUE;
+ }
+
+ if(dwBytes < m_nDSoundBufferSize / 2)
+ {
+ // Sleep again if we did fill less than half the buffer size.
+ // Otherwise it's a better idea to refill again right away.
+ break;
+ }
+ }
+}
+
+
+SoundDevice::BufferAttributes CDSoundDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_nDSoundBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.UpdateInterval = std::min(m_Settings.UpdateInterval, m_nDSoundBufferSize / (2.0 * m_Settings.GetBytesPerSecond()));
+ bufferAttributes.NumBuffers = 1;
+ return bufferAttributes;
+}
+
+
+SoundDevice::Statistics CDSoundDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = 1.0 * m_StatisticLatencyFrames.load() / m_Settings.Samplerate;
+ result.LastUpdateInterval = 1.0 * m_StatisticPeriodFrames.load() / m_Settings.Samplerate;
+ result.text = mpt::ustring();
+ return result;
+}
+
+
+#endif // MPT_WITH_DIRECTSOUND
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp
new file mode 100644
index 00000000..9c6ef67d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#if defined(MPT_WITH_DIRECTSOUND)
+#include <dsound.h>
+#endif // MPT_WITH_DIRECTSOUND
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#if defined(MPT_WITH_DIRECTSOUND)
+
+
+class CDSoundDevice : public CSoundDeviceWithThread
+{
+protected:
+ IDirectSound *m_piDS;
+ IDirectSoundBuffer *m_pPrimary;
+ IDirectSoundBuffer *m_pMixBuffer;
+ uint32 m_nDSoundBufferSize;
+ BOOL m_bMixRunning;
+ DWORD m_dwWritePos;
+
+ std::atomic<uint32> m_StatisticLatencyFrames;
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CDSoundDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CDSoundDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ void StartFromSoundThread();
+ void StopFromSoundThread();
+ bool InternalIsOpen() const { return (m_pMixBuffer != NULL); }
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+#endif // MPT_WITH_DIRECTSOUND
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp
new file mode 100644
index 00000000..2c165273
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp
@@ -0,0 +1,504 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceManager.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceASIO.hpp"
+#include "SoundDeviceDirectSound.hpp"
+#include "SoundDevicePortAudio.hpp"
+#include "SoundDeviceRtAudio.hpp"
+#include "SoundDeviceWaveout.hpp"
+#include "SoundDevicePulseaudio.hpp"
+#include "SoundDevicePulseSimple.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+
+struct CompareInfo
+{
+ static int64 score(const SoundDevice::Info &x)
+ {
+ int64 score = 0;
+ score *= 256;
+ score += static_cast<int8>(x.managerFlags.defaultFor);
+ score *= 256;
+ score += static_cast<int8>(x.flags.usability);
+ score *= 256;
+ score += static_cast<int8>(x.flags.level);
+ score *= 256;
+ score += static_cast<int8>(x.flags.compatible);
+ score *= 256;
+ score += static_cast<int8>(x.flags.api);
+ score *= 256;
+ score += static_cast<int8>(x.flags.io);
+ score *= 256;
+ score += static_cast<int8>(x.flags.mixing);
+ score *= 256;
+ score += static_cast<int8>(x.flags.implementor);
+ return score;
+ }
+ bool operator()(const SoundDevice::Info &x, const SoundDevice::Info &y)
+ {
+ const auto scorex = score(x);
+ const auto scorey = score(y);
+ return (scorex > scorey)
+ || ((scorex == scorey) && (x.type > y.type))
+ || ((scorex == scorey) && (x.type == y.type) && (x.default_ > y.default_))
+ || ((scorex == scorey) && (x.type == y.type) && (x.default_ == y.default_) && (x.name < y.name));
+ }
+};
+
+
+std::vector<std::shared_ptr<IDevicesEnumerator>> Manager::GetDefaultEnumerators()
+{
+ return GetEnabledEnumerators(EnabledBackends());
+}
+
+
+std::vector<std::shared_ptr<IDevicesEnumerator>> Manager::GetEnabledEnumerators(EnabledBackends enabledBackends)
+{
+ std::vector<std::shared_ptr<IDevicesEnumerator>> result;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+#if defined(MPT_WITH_PULSEAUDIO)
+ if(enabledBackends.Pulseaudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<Pulseaudio>>());
+ }
+#endif // MPT_WITH_PULSEAUDIO
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ if(enabledBackends.PulseaudioSimple)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<PulseaudioSimple>>());
+ }
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+#if MPT_OS_WINDOWS
+ if(enabledBackends.WaveOut)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CWaveDevice>>());
+ }
+#endif // MPT_OS_WINDOWS
+
+#if defined(MPT_WITH_DIRECTSOUND)
+ // kind of deprecated by now
+ if(enabledBackends.DirectSound)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CDSoundDevice>>());
+ }
+#endif // MPT_WITH_DIRECTSOUND
+
+#ifdef MPT_WITH_ASIO
+ if(enabledBackends.ASIO)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CASIODevice>>());
+ }
+#endif // MPT_WITH_ASIO
+
+#ifdef MPT_WITH_PORTAUDIO
+ if(enabledBackends.PortAudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CPortaudioDevice>>());
+ }
+#endif // MPT_WITH_PORTAUDIO
+
+#ifdef MPT_WITH_RTAUDIO
+ if(enabledBackends.RtAudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CRtAudioDevice>>());
+ }
+#endif // MPT_WITH_RTAUDIO
+ return result;
+}
+
+
+void Manager::ReEnumerate(bool firstRun)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_SoundDevices.clear();
+ m_DeviceUnavailable.clear();
+ m_DeviceFactoryMethods.clear();
+ m_DeviceCaps.clear();
+ m_DeviceDynamicCaps.clear();
+
+ if(firstRun)
+ {
+ for(auto &deviceEnumerator : m_DeviceEnumerators)
+ {
+ if(!deviceEnumerator)
+ {
+ continue;
+ }
+ m_BackendInitializers.push_back(deviceEnumerator->BackendInitializer());
+ }
+ } else
+ {
+ for(auto &initializer : m_BackendInitializers)
+ {
+ initializer->Reload();
+ }
+ }
+
+ for(auto &enumerator : m_DeviceEnumerators)
+ {
+ if(!enumerator)
+ {
+ continue;
+ }
+ const auto infos = enumerator->EnumerateDevices(GetLogger(), GetSysInfo());
+ mpt::append(m_SoundDevices, infos);
+ for(const auto &info : infos)
+ {
+ SoundDevice::Identifier identifier = info.GetIdentifier();
+ if(!identifier.empty())
+ {
+ m_DeviceFactoryMethods[identifier] = enumerator->GetCreateFunc();
+ }
+ }
+ }
+
+ struct Default
+ {
+ SoundDevice::Info::DefaultFor value = SoundDevice::Info::DefaultFor::None;
+ };
+
+ std::map<SoundDevice::Type, Default> typeDefault;
+ if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Linux)
+ {
+#if defined(MPT_WITH_PULSEAUDIO)
+ typeDefault[MPT_USTRING("PulseAudio")].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ typeDefault[MPT_USTRING("PulseAudio-Simple")].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("pulse"))].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("alsa"))].value = Info::DefaultFor::LowLevel;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("jack"))].value = Info::DefaultFor::ProAudio;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paALSA)].value = Info::DefaultFor::LowLevel;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paJACK)].value = Info::DefaultFor::ProAudio;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Darwin)
+ {
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("core"))].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paCoreAudio)].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("jack"))].value = Info::DefaultFor::ProAudio;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paJACK)].value = Info::DefaultFor::ProAudio;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::BSD)
+ {
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paOSS)].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("oss"))].value = Info::DefaultFor::System;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Haiku)
+ {
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paBeOS)].value = Info::DefaultFor::System;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine() && GetSysInfo().WineHostClass == mpt::osinfo::osclass::Linux)
+ { // Wine on Linux
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine() && GetSysInfo().WineHostClass == mpt::osinfo::osclass::Darwin)
+ { // Wine on macOS
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine())
+ { // Wine
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeDSOUND].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista))
+ { // WinXP
+ typeDefault[SoundDevice::TypeWAVEOUT].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7))
+ { // Vista
+ typeDefault[SoundDevice::TypeWAVEOUT].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows)
+ { // >=Win7
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else
+ { // unknown
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ }
+ for(auto &deviceInfo : m_SoundDevices)
+ {
+ if(typeDefault[deviceInfo.type].value != Info::DefaultFor::None)
+ {
+ deviceInfo.managerFlags.defaultFor = typeDefault[deviceInfo.type].value;
+ }
+ }
+ std::stable_sort(m_SoundDevices.begin(), m_SoundDevices.end(), CompareInfo());
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("Sound Devices enumerated:")());
+ for(const auto &device : m_SoundDevices)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Identifier : {}")(device.GetIdentifier()));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Type : {}")(device.type));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" InternalID: {}")(device.internalID));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" API Name : {}")(device.apiName));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Name : {}")(device.name));
+ for(const auto &extra : device.extraData)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Extra Data: {} = {}")(extra.first, extra.second));
+ }
+ }
+}
+
+
+SoundDevice::Manager::GlobalID Manager::GetGlobalID(SoundDevice::Identifier identifier) const
+{
+ for(std::size_t i = 0; i < m_SoundDevices.size(); ++i)
+ {
+ if(m_SoundDevices[i].GetIdentifier() == identifier)
+ {
+ return i;
+ }
+ }
+ return ~SoundDevice::Manager::GlobalID();
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfo(SoundDevice::Manager::GlobalID id) const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(id > m_SoundDevices.size())
+ {
+ return SoundDevice::Info();
+ }
+ return m_SoundDevices[id];
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfo(SoundDevice::Identifier identifier) const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_SoundDevices.empty())
+ {
+ return SoundDevice::Info();
+ }
+ if(identifier.empty())
+ {
+ return SoundDevice::Info();
+ }
+ for(const auto &info : *this)
+ {
+ if(info.GetIdentifier() == identifier)
+ {
+ return info;
+ }
+ }
+ return SoundDevice::Info();
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfoBestMatch(SoundDevice::Identifier identifier)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_SoundDevices.empty())
+ {
+ return SoundDevice::Info();
+ }
+ if(!identifier.empty())
+ { // valid identifier
+ for(const auto &info : *this)
+ {
+ if((info.GetIdentifier() == identifier) && !IsDeviceUnavailable(info.GetIdentifier()))
+ { // exact match
+ return info;
+ }
+ }
+ }
+ for(const auto &info : *this)
+ { // find first available device
+ if(!IsDeviceUnavailable(info.GetIdentifier()))
+ {
+ return info;
+ }
+ }
+ // default to first device
+ return *begin();
+}
+
+
+bool Manager::OpenDriverSettings(SoundDevice::Identifier identifier, SoundDevice::IMessageReceiver *messageReceiver, SoundDevice::IBase *currentSoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ result = currentSoundDevice->OpenDriverSettings();
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ dummy->SetMessageReceiver(messageReceiver);
+ result = dummy->OpenDriverSettings();
+ }
+ delete dummy;
+ }
+ return result;
+}
+
+
+SoundDevice::Caps Manager::GetDeviceCaps(SoundDevice::Identifier identifier, SoundDevice::IBase *currentSoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeviceCaps.find(identifier) == m_DeviceCaps.end())
+ {
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ m_DeviceCaps[identifier] = currentSoundDevice->GetDeviceCaps();
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ m_DeviceCaps[identifier] = dummy->GetDeviceCaps();
+ } else
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ delete dummy;
+ }
+ }
+ return m_DeviceCaps[identifier];
+}
+
+
+SoundDevice::DynamicCaps Manager::GetDeviceDynamicCaps(SoundDevice::Identifier identifier, const std::vector<uint32> &baseSampleRates, SoundDevice::IMessageReceiver *messageReceiver, SoundDevice::IBase *currentSoundDevice, bool update)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if((m_DeviceDynamicCaps.find(identifier) == m_DeviceDynamicCaps.end()) || update)
+ {
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ m_DeviceDynamicCaps[identifier] = currentSoundDevice->GetDeviceDynamicCaps(baseSampleRates);
+ if(!currentSoundDevice->IsAvailable())
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ dummy->SetMessageReceiver(messageReceiver);
+ m_DeviceDynamicCaps[identifier] = dummy->GetDeviceDynamicCaps(baseSampleRates);
+ if(!dummy->IsAvailable())
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ } else
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ delete dummy;
+ }
+ }
+ return m_DeviceDynamicCaps[identifier];
+}
+
+
+SoundDevice::IBase *Manager::CreateSoundDevice(SoundDevice::Identifier identifier)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ const SoundDevice::Info info = FindDeviceInfo(identifier);
+ if(!info.IsValid())
+ {
+ return nullptr;
+ }
+ if(m_DeviceFactoryMethods.find(identifier) == m_DeviceFactoryMethods.end())
+ {
+ return nullptr;
+ }
+ if(!m_DeviceFactoryMethods[identifier])
+ {
+ return nullptr;
+ }
+ SoundDevice::IBase *result = m_DeviceFactoryMethods[identifier](GetLogger(), info, GetSysInfo());
+ if(!result)
+ {
+ return nullptr;
+ }
+ if(!result->Init(m_AppInfo))
+ {
+ delete result;
+ result = nullptr;
+ return nullptr;
+ }
+ m_DeviceCaps[identifier] = result->GetDeviceCaps(); // update cached caps
+ return result;
+}
+
+
+
+Manager::Manager(ILogger &logger, SoundDevice::SysInfo sysInfo, SoundDevice::AppInfo appInfo, std::vector<std::shared_ptr<IDevicesEnumerator>> deviceEnumerators)
+ : m_Logger(logger)
+ , m_SysInfo(sysInfo)
+ , m_AppInfo(appInfo)
+ , m_DeviceEnumerators(std::move(deviceEnumerators))
+{
+ ReEnumerate(true);
+}
+
+
+Manager::~Manager()
+{
+ return;
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp
new file mode 100644
index 00000000..3807978b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+struct EnabledBackends
+{
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ bool Pulseaudio = true;
+#endif // MPT_WITH_PULSEAUDIO && MPT_ENABLE_PULSEAUDIO_FULL
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ bool PulseaudioSimple = true;
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+#if MPT_OS_WINDOWS
+ bool WaveOut = true;
+#endif // MPT_OS_WINDOWS
+#if defined(MPT_WITH_DIRECTSOUND)
+ bool DirectSound = true;
+#endif // MPT_WITH_DIRECTSOUND
+#ifdef MPT_WITH_ASIO
+ bool ASIO = true;
+#endif // MPT_WITH_ASIO
+#ifdef MPT_WITH_PORTAUDIO
+ bool PortAudio = true;
+#endif // MPT_WITH_PORTAUDIO
+#ifdef MPT_WITH_RTAUDIO
+ bool RtAudio = true;
+#endif // MPT_WITH_RTAUDIO
+};
+
+
+class IDevicesEnumerator
+{
+protected:
+ typedef SoundDevice::IBase *(*CreateSoundDeviceFunc)(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo);
+
+protected:
+ IDevicesEnumerator() = default;
+
+public:
+ virtual ~IDevicesEnumerator() = default;
+
+public:
+ virtual std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() = 0;
+ virtual std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo) = 0;
+ virtual CreateSoundDeviceFunc GetCreateFunc() = 0;
+};
+
+
+template <typename TSoundDevice>
+class DevicesEnumerator
+ : public IDevicesEnumerator
+{
+public:
+ DevicesEnumerator() = default;
+ ~DevicesEnumerator() override = default;
+
+public:
+ std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() override
+ {
+ return TSoundDevice::BackendInitializer();
+ }
+ std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo) override
+ {
+ return TSoundDevice::EnumerateDevices(logger, sysInfo);
+ }
+ virtual CreateSoundDeviceFunc GetCreateFunc() override
+ {
+ return &ConstructSoundDevice;
+ }
+
+public:
+ static SoundDevice::IBase *ConstructSoundDevice(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo)
+ {
+ return new TSoundDevice(logger, info, sysInfo);
+ }
+};
+
+
+class Manager
+{
+
+public:
+ typedef std::size_t GlobalID;
+
+protected:
+ typedef SoundDevice::IBase *(*CreateSoundDeviceFunc)(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo);
+
+protected:
+ ILogger &m_Logger;
+ const SoundDevice::SysInfo m_SysInfo;
+ const SoundDevice::AppInfo m_AppInfo;
+
+ std::vector<std::shared_ptr<IDevicesEnumerator>> m_DeviceEnumerators;
+
+ std::vector<std::unique_ptr<BackendInitializer>> m_BackendInitializers;
+
+ std::vector<SoundDevice::Info> m_SoundDevices;
+ std::map<SoundDevice::Identifier, bool> m_DeviceUnavailable;
+ std::map<SoundDevice::Identifier, CreateSoundDeviceFunc> m_DeviceFactoryMethods;
+ std::map<SoundDevice::Identifier, SoundDevice::Caps> m_DeviceCaps;
+ std::map<SoundDevice::Identifier, SoundDevice::DynamicCaps> m_DeviceDynamicCaps;
+
+public:
+ Manager(ILogger &logger, SoundDevice::SysInfo sysInfo, SoundDevice::AppInfo appInfo, std::vector<std::shared_ptr<IDevicesEnumerator>> deviceEnumerators = GetDefaultEnumerators());
+ ~Manager();
+
+public:
+ ILogger &GetLogger() const { return m_Logger; }
+ SoundDevice::SysInfo GetSysInfo() const { return m_SysInfo; }
+ SoundDevice::AppInfo GetAppInfo() const { return m_AppInfo; }
+
+ static std::vector<std::shared_ptr<IDevicesEnumerator>> GetDefaultEnumerators();
+ static std::vector<std::shared_ptr<IDevicesEnumerator>> GetEnabledEnumerators(EnabledBackends enabledBackends);
+
+ void ReEnumerate(bool firstRun = false);
+
+ std::vector<SoundDevice::Info>::const_iterator begin() const { return m_SoundDevices.begin(); }
+ std::vector<SoundDevice::Info>::const_iterator end() const { return m_SoundDevices.end(); }
+ const std::vector<SoundDevice::Info> &GetDeviceInfos() const { return m_SoundDevices; }
+
+ SoundDevice::Manager::GlobalID GetGlobalID(SoundDevice::Identifier identifier) const;
+
+ SoundDevice::Info FindDeviceInfo(SoundDevice::Manager::GlobalID id) const;
+ SoundDevice::Info FindDeviceInfo(SoundDevice::Identifier identifier) const;
+ SoundDevice::Info FindDeviceInfoBestMatch(SoundDevice::Identifier identifier);
+
+ bool OpenDriverSettings(SoundDevice::Identifier identifier, SoundDevice::IMessageReceiver *messageReceiver = nullptr, SoundDevice::IBase *currentSoundDevice = nullptr);
+
+ void SetDeviceUnavailable(SoundDevice::Identifier identifier) { m_DeviceUnavailable[identifier] = true; }
+ bool IsDeviceUnavailable(SoundDevice::Identifier identifier) { return m_DeviceUnavailable[identifier]; }
+
+ SoundDevice::Caps GetDeviceCaps(SoundDevice::Identifier identifier, SoundDevice::IBase *currentSoundDevice = nullptr);
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(SoundDevice::Identifier identifier, const std::vector<uint32> &baseSampleRates, SoundDevice::IMessageReceiver *messageReceiver = nullptr, SoundDevice::IBase *currentSoundDevice = nullptr, bool update = false);
+
+ SoundDevice::IBase *CreateSoundDevice(SoundDevice::Identifier identifier);
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp
new file mode 100644
index 00000000..e8824552
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp
@@ -0,0 +1,1081 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePortAudio.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/buffer.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <array>
+#include <vector>
+
+#include <cstddef>
+
+#ifdef MPT_WITH_PORTAUDIO
+#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
+#include "../include/portaudio/src/common/pa_debugprint.h"
+#endif
+#if MPT_OS_WINDOWS
+#include <shellapi.h>
+#include <windows.h>
+#endif
+#endif
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_PORTAUDIO
+
+#ifdef MPT_ALL_LOGGING
+#define PALOG(x) MPT_LOG(GetLogger(), LogDebug, "PortAudio", x)
+#define PA_LOG_ENABLED 1
+#else
+#define PALOG(x) \
+ do \
+ { \
+ } while(0)
+#define PA_LOG_ENABLED 0
+#endif
+
+
+
+CPortaudioDevice::CPortaudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_StatisticPeriodFrames(0)
+{
+ mpt::ustring internalID = GetDeviceInternalID();
+ if(internalID == MPT_USTRING("WASAPI-Default"))
+ {
+ m_DeviceIsDefault = true;
+ m_DeviceIndex = Pa_GetHostApiInfo(Pa_HostApiTypeIdToHostApiIndex(paWASAPI))->defaultOutputDevice;
+ } else
+ {
+ m_DeviceIsDefault = false;
+ m_DeviceIndex = mpt::ConvertStringTo<PaDeviceIndex>(internalID);
+ }
+ m_HostApiType = Pa_GetHostApiInfo(Pa_GetDeviceInfo(m_DeviceIndex)->hostApi)->type;
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo = {};
+#endif // MPT_OS_WINDOWS
+ m_Stream = 0;
+ m_StreamInfo = 0;
+ m_CurrentFrameBuffer = nullptr;
+ m_CurrentFrameBufferInput = nullptr;
+ m_CurrentFrameCount = 0;
+ m_CurrentRealLatency = 0.0;
+}
+
+
+CPortaudioDevice::~CPortaudioDevice()
+{
+ Close();
+}
+
+
+bool CPortaudioDevice::InternalOpen()
+{
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+ m_Stream = 0;
+ m_StreamInfo = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ m_CurrentFrameCount = 0;
+ m_StreamParameters.device = m_DeviceIndex;
+ if(m_StreamParameters.device == -1)
+ {
+ return false;
+ }
+ m_StreamParameters.channelCount = m_Settings.Channels;
+ if(m_Settings.sampleFormat.IsFloat())
+ {
+ if(m_Settings.sampleFormat.GetBitsPerSample() != 32)
+ {
+ return false;
+ }
+ m_StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(m_Settings.sampleFormat.GetBitsPerSample())
+ {
+ case 8: m_StreamParameters.sampleFormat = paInt8; break;
+ case 16: m_StreamParameters.sampleFormat = paInt16; break;
+ case 24: m_StreamParameters.sampleFormat = paInt24; break;
+ case 32: m_StreamParameters.sampleFormat = paInt32; break;
+ default: return false; break;
+ }
+ }
+ m_StreamParameters.suggestedLatency = m_Settings.Latency;
+ m_StreamParameters.hostApiSpecificStreamInfo = NULL;
+ unsigned long framesPerBuffer = static_cast<long>(m_Settings.UpdateInterval * m_Settings.Samplerate);
+ if(m_HostApiType == paWASAPI)
+ {
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo = {};
+ m_WasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
+ m_WasapiStreamInfo.hostApiType = paWASAPI;
+ m_WasapiStreamInfo.version = 1;
+ if(m_Settings.BoostThreadPriority)
+ {
+ m_WasapiStreamInfo.flags |= paWinWasapiThreadPriority;
+ if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING(""))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Audio"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityAudio;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Capture"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityCapture;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Distribution"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityDistribution;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Games"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityGames;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Playback"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityPlayback;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Pro Audio"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityProAudio;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Window Manager"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityWindowManager;
+ } else
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
+ }
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+ }
+#endif // MPT_OS_WINDOWS
+ if(m_Settings.ExclusiveMode)
+ {
+ m_Flags.WantsClippedOutput = false;
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags |= paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ } else
+ {
+ m_Flags.WantsClippedOutput = GetSysInfo().IsOriginal();
+ }
+ } else if(m_HostApiType == paWDMKS)
+ {
+ m_Flags.WantsClippedOutput = false;
+ framesPerBuffer = paFramesPerBufferUnspecified; // let portaudio choose
+ } else if(m_HostApiType == paMME)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ } else if(m_HostApiType == paDirectSound)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ } else
+ {
+ m_Flags.WantsClippedOutput = false;
+ }
+ m_InputStreamParameters = m_StreamParameters;
+ if(!HasInputChannelsOnSameDevice())
+ {
+ m_InputStreamParameters.device = static_cast<PaDeviceIndex>(m_Settings.InputSourceID);
+ }
+ m_InputStreamParameters.channelCount = m_Settings.InputChannels;
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ if(m_HostApiType == paWASAPI)
+ {
+ if(m_Settings.ExclusiveMode)
+ {
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags &= ~paWinWasapiExplicitSampleFormat;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ return false;
+ }
+ } else
+ {
+ if(!GetSysInfo().IsWine && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
+ { // retry with automatic stream format conversion (i.e. resampling)
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags |= paWinWasapiAutoConvert;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ return false;
+ }
+ } else
+ {
+ return false;
+ }
+ }
+ } else
+ {
+ return false;
+ }
+ }
+ PaStreamFlags flags = paNoFlag;
+ if(m_Settings.DitherType == 0)
+ {
+ flags |= paDitherOff;
+ }
+ if(Pa_OpenStream(&m_Stream, (m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate, framesPerBuffer, flags, StreamCallbackWrapper, reinterpret_cast<void *>(this)) != paNoError)
+ {
+ return false;
+ }
+ m_StreamInfo = Pa_GetStreamInfo(m_Stream);
+ if(!m_StreamInfo)
+ {
+ Pa_CloseStream(m_Stream);
+ m_Stream = 0;
+ return false;
+ }
+ return true;
+}
+
+
+bool CPortaudioDevice::InternalClose()
+{
+ if(m_Stream)
+ {
+ const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
+ Pa_AbortStream(m_Stream);
+ Pa_CloseStream(m_Stream);
+ if(Pa_GetDeviceInfo(m_StreamParameters.device)->hostApi == Pa_HostApiTypeIdToHostApiIndex(paWDMKS))
+ {
+ Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5)); // wait for broken wdm drivers not closing the stream immediatly
+ }
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+ m_StreamInfo = 0;
+ m_Stream = 0;
+ m_CurrentFrameCount = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ }
+ return true;
+}
+
+
+bool CPortaudioDevice::InternalStart()
+{
+ return Pa_StartStream(m_Stream) == paNoError;
+}
+
+
+void CPortaudioDevice::InternalStop()
+{
+ Pa_StopStream(m_Stream);
+}
+
+
+void CPortaudioDevice::InternalFillAudioBuffer()
+{
+ if(m_CurrentFrameCount == 0)
+ {
+ return;
+ }
+ CallbackLockedAudioReadPrepare(m_CurrentFrameCount, mpt::saturate_cast<std::size_t>(mpt::saturate_round<int64>(m_CurrentRealLatency * m_StreamInfo->sampleRate)));
+ CallbackLockedAudioProcessVoid(m_CurrentFrameBuffer, m_CurrentFrameBufferInput, m_CurrentFrameCount);
+ m_StatisticPeriodFrames.store(m_CurrentFrameCount);
+ CallbackLockedAudioProcessDone();
+}
+
+
+int64 CPortaudioDevice::InternalGetStreamPositionFrames() const
+{
+ if(Pa_IsStreamActive(m_Stream) != 1)
+ {
+ return 0;
+ }
+ return static_cast<int64>(Pa_GetStreamTime(m_Stream) * m_StreamInfo->sampleRate);
+}
+
+
+SoundDevice::BufferAttributes CPortaudioDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_StreamInfo->outputLatency;
+ bufferAttributes.UpdateInterval = m_Settings.UpdateInterval;
+ bufferAttributes.NumBuffers = 1;
+ if(m_HostApiType == paWASAPI && m_Settings.ExclusiveMode)
+ {
+ // WASAPI exclusive mode streams only account for a single period of latency in PortAudio
+ // (i.e. the same way as Steinerg ASIO defines latency).
+ // That does not match our definition of latency, repair it.
+ bufferAttributes.Latency *= 2.0;
+ }
+ return bufferAttributes;
+}
+
+
+bool CPortaudioDevice::OnIdle()
+{
+ if(!IsPlaying())
+ {
+ return false;
+ }
+ if(m_Stream)
+ {
+ if(m_HostApiType == paWDMKS)
+ {
+ // Catch timeouts in PortAudio threading code that cause the thread to exit.
+ // Restore our desired playback state by resetting the whole sound device.
+ if(Pa_IsStreamActive(m_Stream) <= 0)
+ {
+ // Hung state tends to be caused by an overloaded system.
+ // Sleeping too long would freeze the UI,
+ // but at least sleep a tiny bit of time to let things settle down.
+ const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
+ Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5));
+ RequestReset();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+SoundDevice::Statistics CPortaudioDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_CurrentRealLatency;
+ result.LastUpdateInterval = 1.0 * m_StatisticPeriodFrames / m_Settings.Samplerate;
+ result.text = mpt::ustring();
+#if MPT_OS_WINDOWS
+ if(m_HostApiType == paWASAPI)
+ {
+ if(m_Settings.ExclusiveMode)
+ {
+ if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiExplicitSampleFormat))
+ {
+ result.text += MPT_USTRING("Exclusive stream.");
+ } else
+ {
+ result.text += MPT_USTRING("Exclusive stream with sample format conversion.");
+ }
+ } else
+ {
+ if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiAutoConvert))
+ {
+ result.text += MPT_USTRING("WASAPI stream resampling.");
+ } else
+ {
+ result.text += MPT_USTRING("No resampling.");
+ }
+ }
+ }
+#endif // MPT_OS_WINDOWS
+ return result;
+}
+
+
+SoundDevice::Caps CPortaudioDevice::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = true;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(m_DeviceIndex);
+ if(deviceInfo)
+ {
+ caps.DefaultSettings.Latency = deviceInfo->defaultLowOutputLatency;
+ }
+ if(HasInputChannelsOnSameDevice())
+ {
+ caps.CanInput = true;
+ caps.HasNamedInputSources = false;
+ } else
+ {
+ caps.CanInput = (EnumerateInputOnlyDevices(m_HostApiType).size() > 0);
+ caps.HasNamedInputSources = caps.CanInput;
+ }
+ if(m_HostApiType == paWASAPI)
+ {
+ caps.CanBoostThreadPriority = true;
+ caps.CanDriverPanel = true;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ if(m_DeviceIsDefault)
+ {
+ caps.CanExclusiveMode = false;
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.010;
+ } else
+ {
+ caps.CanExclusiveMode = true;
+ if(deviceInfo)
+ {
+ // PortAudio WASAPI returns the device period as latency
+ caps.DefaultSettings.Latency = deviceInfo->defaultHighOutputLatency * 2.0;
+ caps.DefaultSettings.UpdateInterval = deviceInfo->defaultHighOutputLatency;
+ }
+ }
+ } else if(m_HostApiType == paWDMKS)
+ {
+ caps.CanUpdateInterval = false;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
+ } else if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsWine)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ } else if(GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ } else if(m_HostApiType == paASIO)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
+ }
+ if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.HasInternalDither = false;
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.HasInternalDither = false;
+ }
+ } else if(m_HostApiType == paJACK)
+ {
+ caps.HasInternalDither = false;
+ } else if(m_HostApiType == paWASAPI)
+ {
+ caps.HasInternalDither = false;
+ }
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CPortaudioDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ PaDeviceIndex device = m_DeviceIndex;
+ if(device == paNoDevice)
+ {
+ return caps;
+ }
+ for(std::size_t n = 0; n < baseSampleRates.size(); n++)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ StreamParameters.sampleFormat = paInt16;
+ if(m_HostApiType == paWASAPI)
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, baseSampleRates[n]) == paFormatIsSupported)
+ {
+ caps.supportedSampleRates.push_back(baseSampleRates[n]);
+ if(!((m_HostApiType == paWASAPI) && m_DeviceIsDefault))
+ {
+ caps.supportedExclusiveSampleRates.push_back(baseSampleRates[n]);
+ }
+ }
+ }
+ if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+ } else if(m_HostApiType == paJACK)
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ } else if(m_HostApiType == paWASAPI)
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+#if MPT_OS_WINDOWS
+ if(m_HostApiType == paWASAPI && !m_DeviceIsDefault)
+ {
+ caps.supportedExclusiveModeSampleFormats.clear();
+ const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
+ for(const SampleFormat sampleFormat : sampleFormats)
+ {
+ for(const auto sampleRate : caps.supportedExclusiveSampleRates)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ if(sampleFormat.IsFloat())
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: StreamParameters.sampleFormat = paInt8; break;
+ case 16: StreamParameters.sampleFormat = paInt16; break;
+ case 24: StreamParameters.sampleFormat = paInt24; break;
+ case 32: StreamParameters.sampleFormat = paInt32; break;
+ }
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ PaWasapiStreamInfo wasapiStreamInfo = {};
+ wasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
+ wasapiStreamInfo.hostApiType = paWASAPI;
+ wasapiStreamInfo.version = 1;
+ wasapiStreamInfo.flags = paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
+ StreamParameters.hostApiSpecificStreamInfo = &wasapiStreamInfo;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ break;
+ }
+ }
+ }
+ }
+ if(m_HostApiType == paWDMKS)
+ {
+ caps.supportedSampleFormats.clear();
+ caps.supportedExclusiveModeSampleFormats.clear();
+ const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
+ for(const SampleFormat sampleFormat : sampleFormats)
+ {
+ for(const auto sampleRate : caps.supportedSampleRates)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ if(sampleFormat.IsFloat())
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: StreamParameters.sampleFormat = paInt8; break;
+ case 16: StreamParameters.sampleFormat = paInt16; break;
+ case 24: StreamParameters.sampleFormat = paInt24; break;
+ case 32: StreamParameters.sampleFormat = paInt32; break;
+ }
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
+ {
+ caps.supportedSampleFormats.push_back(sampleFormat);
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ break;
+ }
+ }
+ }
+ if(caps.supportedSampleFormats.empty())
+ {
+ caps.supportedSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ }
+ if(caps.supportedExclusiveModeSampleFormats.empty())
+ {
+ caps.supportedExclusiveModeSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ }
+ }
+#endif // MPT_OS_WINDOWS
+#if MPT_OS_WINDOWS
+ if((m_HostApiType == paWASAPI) && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
+ {
+ caps.supportedSampleRates = baseSampleRates;
+ }
+#endif // MPT_OS_WINDOWS
+
+ if(!HasInputChannelsOnSameDevice())
+ {
+ caps.inputSourceNames.clear();
+ auto inputDevices = EnumerateInputOnlyDevices(m_HostApiType);
+ for(const auto &dev : inputDevices)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(dev.first), dev.second));
+ }
+ }
+
+ return caps;
+}
+
+
+bool CPortaudioDevice::OpenDriverSettings()
+{
+#if MPT_OS_WINDOWS
+ if(m_HostApiType != paWASAPI)
+ {
+ return false;
+ }
+ bool hasVista = GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista);
+ mpt::winstring controlEXE;
+ TCHAR systemDir[MAX_PATH] = {};
+ if(GetSystemDirectory(systemDir, mpt::saturate_cast<UINT>(std::size(systemDir))) > 0)
+ {
+ controlEXE += mpt::ReadWinBuf(systemDir);
+ controlEXE += TEXT("\\");
+ }
+ controlEXE += TEXT("control.exe");
+ return (reinterpret_cast<INT_PTR>(ShellExecute(NULL, TEXT("open"), controlEXE.c_str(), (hasVista ? TEXT("/name Microsoft.Sound") : TEXT("mmsys.cpl")), NULL, SW_SHOW)) >= 32);
+#else // !MPT_OS_WINDOWS
+ return false;
+#endif // MPT_OS_WINDOWS
+}
+
+
+int CPortaudioDevice::StreamCallback(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
+{
+ if(!input && !output)
+ {
+ return paAbort;
+ }
+ if(m_HostApiType == paWDMKS)
+ {
+ // For WDM-KS, timeInfo->outputBufferDacTime seems to contain bogus values.
+ // Work-around it by using the slightly less accurate per-stream latency estimation.
+ m_CurrentRealLatency = m_StreamInfo->outputLatency;
+ } else if(m_HostApiType == paWASAPI)
+ {
+ // PortAudio latency calculation appears to miss the current period or chunk for WASAPI. Compensate it.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else if(m_HostApiType == paDirectSound)
+ {
+ // PortAudio latency calculation appears to miss the buffering latency.
+ // The current chunk, however, appears to be compensated for.
+ // Repair the confusion.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else if(m_HostApiType == paALSA)
+ {
+ // PortAudio latency calculation appears to miss the buffering latency.
+ // The current chunk, however, appears to be compensated for.
+ // Repair the confusion.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else
+ {
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime;
+ }
+ m_CurrentFrameBuffer = output;
+ m_CurrentFrameBufferInput = input;
+ m_CurrentFrameCount = frameCount;
+ CallbackFillAudioBufferLocked();
+ m_CurrentFrameCount = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ if((m_HostApiType == paALSA) && (statusFlags & paOutputUnderflow))
+ {
+ // PortAudio ALSA does not recover well from buffer underruns
+ RequestRestart();
+ }
+ return paContinue;
+}
+
+
+int CPortaudioDevice::StreamCallbackWrapper(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
+{
+ return reinterpret_cast<CPortaudioDevice *>(userData)->StreamCallback(input, output, frameCount, timeInfo, statusFlags);
+}
+
+
+std::vector<SoundDevice::Info> CPortaudioDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if PA_LOG_ENABLED
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
+ {
+ if(!Pa_GetDeviceInfo(dev))
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->hostApi < 0)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
+ {
+ continue;
+ }
+ if(!Pa_GetDeviceInfo(dev)->name)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxOutputChannels <= 0)
+ {
+ continue;
+ }
+ SoundDevice::Info result = SoundDevice::Info();
+ switch((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type))
+ {
+ case paWASAPI:
+ result.type = TypePORTAUDIO_WASAPI;
+ break;
+ case paWDMKS:
+ result.type = TypePORTAUDIO_WDMKS;
+ break;
+ case paMME:
+ result.type = TypePORTAUDIO_WMME;
+ break;
+ case paDirectSound:
+ result.type = TypePORTAUDIO_DS;
+ break;
+ default:
+ result.type = MPT_USTRING("PortAudio") + MPT_USTRING("-") + mpt::format<mpt::ustring>::dec(static_cast<int>(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type));
+ break;
+ }
+ result.internalID = mpt::format<mpt::ustring>::dec(dev);
+ result.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name);
+ result.apiName = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
+ result.extraData[MPT_USTRING("PortAudio-HostAPI-name")] = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
+ result.apiPath.push_back(MPT_USTRING("PortAudio"));
+ result.useNameAsIdentifier = true;
+ result.flags = {
+ Info::Usability::Unknown,
+ Info::Level::Unknown,
+ Info::Compatible::Unknown,
+ Info::Api::Unknown,
+ Info::Io::Unknown,
+ Info::Mixing::Unknown,
+ Info::Implementor::External};
+ // clang-format off
+ switch(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type)
+ {
+ case paDirectSound:
+ result.apiName = MPT_USTRING("DirectSound");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paMME:
+ result.apiName = MPT_USTRING("MME");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Legacy : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paASIO:
+ result.apiName = MPT_USTRING("ASIO");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case paCoreAudio:
+ result.apiName = MPT_USTRING("CoreAudio");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case paOSS:
+ result.apiName = MPT_USTRING("OSS");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Api::Native : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Mixing::Hardware : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Software : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paALSA:
+ result.apiName = MPT_USTRING("ALSA");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paAL:
+ result.apiName = MPT_USTRING("OpenAL");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ Info::Usability::Usable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paWDMKS:
+ result.apiName = sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? MPT_USTRING("WaveRT") : MPT_USTRING("WDM-KS");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Usability::Usable : Info::Usability::Usable : Info::Usability::Broken : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Native : Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case paJACK:
+ result.apiName = MPT_USTRING("JACK");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case paWASAPI:
+ result.apiName = MPT_USTRING("WASAPI");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ?
+ sysInfo.IsWindowsOriginal() ?
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7) ?
+ Info::Usability::Usable
+ :
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ?
+ Info::Usability::Experimental
+ :
+ Info::Usability::NotAvailable
+ :
+ Info::Usability::Usable
+ :
+ Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ default:
+ // nothing
+ break;
+ }
+ // clang-format on
+ PALOG(MPT_UFORMAT_MESSAGE("PortAudio: {}, {}, {}, {}")(result.internalID, result.name, result.apiName, static_cast<int>(result.default_)));
+ PALOG(MPT_UFORMAT_MESSAGE(" low : {}")(Pa_GetDeviceInfo(dev)->defaultLowOutputLatency));
+ PALOG(MPT_UFORMAT_MESSAGE(" high : {}")(Pa_GetDeviceInfo(dev)->defaultHighOutputLatency));
+ if((result.default_ != Info::Default::None) && (Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type == paWASAPI))
+ {
+ auto defaultResult = result;
+ defaultResult.default_ = Info::Default::Managed;
+ defaultResult.name = MPT_USTRING("Default Device");
+ defaultResult.internalID = MPT_USTRING("WASAPI-Default");
+ defaultResult.useNameAsIdentifier = false;
+ devices.push_back(defaultResult);
+ result.default_ = Info::Default::Named;
+ }
+ devices.push_back(result);
+ }
+ return devices;
+}
+
+
+std::vector<std::pair<PaDeviceIndex, mpt::ustring>> CPortaudioDevice::EnumerateInputOnlyDevices(PaHostApiTypeId hostApiType)
+{
+ std::vector<std::pair<PaDeviceIndex, mpt::ustring>> result;
+ for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
+ {
+ if(!Pa_GetDeviceInfo(dev))
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->hostApi < 0)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
+ {
+ continue;
+ }
+ if(!Pa_GetDeviceInfo(dev)->name)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxInputChannels <= 0)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxOutputChannels > 0)
+ { // only find devices with only input channels
+ continue;
+ }
+ if(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type != hostApiType)
+ {
+ continue;
+ }
+ result.push_back(std::make_pair(dev, mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name)));
+ }
+ return result;
+}
+
+
+bool CPortaudioDevice::HasInputChannelsOnSameDevice() const
+{
+ if(m_DeviceIndex == paNoDevice)
+ {
+ return false;
+ }
+ const PaDeviceInfo *deviceinfo = Pa_GetDeviceInfo(m_DeviceIndex);
+ if(!deviceinfo)
+ {
+ return false;
+ }
+ return (deviceinfo->maxInputChannels > 0);
+}
+
+
+#if MPT_COMPILER_MSVC
+static void PortaudioLog(const char *text)
+{
+ if(!text)
+ {
+ return;
+ }
+#if PA_LOG_ENABLED
+ MPT_LOG(mpt::log::GlobalLogger(), LogDebug, "PortAudio", MPT_UFORMAT_MESSAGE("PortAudio: {}")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, text)));
+#endif
+}
+#endif // MPT_COMPILER_MSVC
+
+
+PortAudioInitializer::PortAudioInitializer()
+{
+#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
+ PaUtil_SetDebugPrintFunction(PortaudioLog);
+#endif
+ m_initialized = (Pa_Initialize() == paNoError);
+}
+
+
+void PortAudioInitializer::Reload()
+{
+ if(m_initialized)
+ {
+ Pa_Terminate();
+ m_initialized = false;
+ }
+ m_initialized = (Pa_Initialize() == paNoError);
+}
+
+
+PortAudioInitializer::~PortAudioInitializer()
+{
+ if(!m_initialized)
+ {
+ return;
+ }
+ Pa_Terminate();
+}
+
+
+#endif // MPT_WITH_PORTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp
new file mode 100644
index 00000000..892f819d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#ifdef MPT_WITH_PORTAUDIO
+#include <portaudio.h>
+#if MPT_OS_WINDOWS
+#include <pa_win_wasapi.h>
+#endif // MPT_OS_WINDOWS
+#endif
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#ifdef MPT_WITH_PORTAUDIO
+
+class PortAudioInitializer
+ : public BackendInitializer
+{
+private:
+ bool m_initialized = false;
+
+public:
+ PortAudioInitializer();
+ PortAudioInitializer(const PortAudioInitializer &) = delete;
+ PortAudioInitializer &operator=(const PortAudioInitializer &) = delete;
+ void Reload();
+ ~PortAudioInitializer() override;
+};
+
+class CPortaudioDevice : public SoundDevice::Base
+{
+
+private:
+ PortAudioInitializer m_PortAudio;
+
+protected:
+ PaDeviceIndex m_DeviceIsDefault;
+ PaDeviceIndex m_DeviceIndex;
+ PaHostApiTypeId m_HostApiType;
+ PaStreamParameters m_StreamParameters;
+ PaStreamParameters m_InputStreamParameters;
+#if MPT_OS_WINDOWS
+ PaWasapiStreamInfo m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ PaStream *m_Stream;
+ const PaStreamInfo *m_StreamInfo;
+ void *m_CurrentFrameBuffer;
+ const void *m_CurrentFrameBufferInput;
+ unsigned long m_CurrentFrameCount;
+
+ double m_CurrentRealLatency; // seconds
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CPortaudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CPortaudioDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_Stream ? true : false; }
+ bool InternalHasGetStreamPosition() const { return false; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool OpenDriverSettings();
+ bool OnIdle();
+
+ int StreamCallback(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags);
+
+public:
+ static int StreamCallbackWrapper(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData);
+
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer()
+ {
+ return std::make_unique<PortAudioInitializer>();
+ }
+
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+private:
+ bool HasInputChannelsOnSameDevice() const;
+
+ static std::vector<std::pair<PaDeviceIndex, mpt::ustring>> EnumerateInputOnlyDevices(PaHostApiTypeId hostApiType);
+};
+
+
+#endif // MPT_WITH_PORTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp
new file mode 100644
index 00000000..5a134921
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp
@@ -0,0 +1,503 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePulseSimple.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <vector>
+
+#include <cstddef>
+#include <cstring>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+//#define MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+
+
+mpt::ustring PulseaudioSimple::PulseErrorString(int error)
+{
+ if(error == 0)
+ {
+ return mpt::ustring();
+ }
+ const char *str = pa_strerror(error);
+ if(!str)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ if(std::strlen(str) == 0)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ return MPT_UFORMAT_MESSAGE("{} (error={})")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, str), error);
+}
+
+
+#ifdef MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+static void PulseAudioSinkInfoListCallback(pa_context * /* c */, const pa_sink_info *i, int /* eol */, void *userdata)
+{
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_USTRING("PulseAudioSinkInfoListCallback"));
+ std::vector<SoundDevice::Info> *devices_ = reinterpret_cast<std::vector<SoundDevice::Info> *>(userdata);
+ if(!devices_)
+ {
+ return;
+ }
+ std::vector<SoundDevice::Info> &devices = *devices_;
+ if(!i)
+ {
+ return;
+ }
+ if(!i->name)
+ {
+ return;
+ }
+ if(!i->description)
+ {
+ return;
+ }
+ if(i->n_ports <= 0)
+ {
+ return;
+ }
+ for(uint32 port = 0; port < i->n_ports; ++port)
+ {
+ // we skip all sinks without ports or with all ports known to be currently unavailable
+ if(!i->ports)
+ {
+ break;
+ }
+ if(!i->ports[port])
+ {
+ continue;
+ }
+ if(i->ports[port]->available == PA_PORT_AVAILABLE_NO)
+ {
+ continue;
+ }
+ SoundDevice::Info info;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.type = MPT_USTRING("PulseAudio-Simple");
+#else // !MPT_ENABLE_PULSEAUDIO_FULL
+ info.type = MPT_USTRING("PulseAudio");
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->name);
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->description);
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.apiName = MPT_USTRING("PulseAudio Simple API");
+#else
+ info.apiName = MPT_USTRING("PulseAudio");
+#endif
+ info.default_ = Info::Default::None;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+ break;
+ }
+}
+
+#endif // MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+
+std::vector<SoundDevice::Info> PulseaudioSimple::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if 0
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ SoundDevice::Info info;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.type = MPT_USTRING("PulseAudio-Simple");
+#else // !MPT_ENABLE_PULSEAUDIO_FULL
+ info.type = MPT_USTRING("PulseAudio");
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+ info.internalID = MPT_USTRING("0");
+ info.name = MPT_USTRING("Default Device");
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.apiName = MPT_USTRING("PulseAudio Simple API");
+#else
+ info.apiName = MPT_USTRING("PulseAudio");
+#endif
+ info.default_ = Info::Default::Managed;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+
+#ifdef MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+ int result = 0;
+ pa_mainloop *m = nullptr;
+ pa_context *c = nullptr;
+ bool doneConnect = false;
+ pa_context_state_t cs = PA_CONTEXT_UNCONNECTED;
+ pa_operation *o = nullptr;
+ pa_operation_state_t s = PA_OPERATION_RUNNING;
+
+ m = pa_mainloop_new();
+ if(!m)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_new"));
+ goto cleanup;
+ }
+ c = pa_context_new(pa_mainloop_get_api(m), mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::ustring()).c_str()); // TODO: get AppInfo
+ if(!c)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_new"));
+ goto cleanup;
+ }
+ if(pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ doneConnect = false;
+ while(!doneConnect)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ cs = pa_context_get_state(c);
+ switch(cs)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ doneConnect = true;
+ break;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ default:
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ break;
+ }
+ }
+ o = pa_context_get_sink_info_list(c, &PulseAudioSinkInfoListCallback, &devices);
+ if(!o)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_get_sink_info_list: ") + PulseErrorString(pa_context_errno(c)));
+ goto cleanup;
+ }
+ s = PA_OPERATION_RUNNING;
+ while((s = pa_operation_get_state(o)) == PA_OPERATION_RUNNING)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ }
+ if(s == PA_OPERATION_CANCELLED)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_operation_get_state"));
+ goto cleanup;
+ }
+ goto cleanup;
+
+cleanup:
+
+ if(o)
+ {
+ pa_operation_unref(o);
+ o = nullptr;
+ }
+ if(c)
+ {
+ pa_context_disconnect(c);
+ pa_context_unref(c);
+ c = nullptr;
+ }
+ if(m)
+ {
+ pa_mainloop_quit(m, 0);
+ pa_mainloop_run(m, &result);
+ pa_mainloop_free(m);
+ m = nullptr;
+ }
+
+#endif // MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+ return devices;
+}
+
+
+PulseaudioSimple::PulseaudioSimple(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : ThreadBase(logger, info, sysInfo)
+ , m_PA_SimpleOutput(nullptr)
+ , m_StatisticLastLatencyFrames(0)
+{
+ return;
+}
+
+
+SoundDevice::Caps PulseaudioSimple::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true; // TODO: poll PulseAudio
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Adjust latency");
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.005;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ caps.DefaultSettings.ExclusiveMode = true;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps PulseaudioSimple::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ caps.supportedSampleRates = baseSampleRates;
+ caps.supportedExclusiveSampleRates = baseSampleRates;
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ return caps;
+}
+
+
+bool PulseaudioSimple::InternalIsOpen() const
+{
+ return m_PA_SimpleOutput;
+}
+
+
+bool PulseaudioSimple::InternalOpen()
+{
+ if(m_Settings.sampleFormat != SampleFormat::Float32)
+ {
+ InternalClose();
+ return false;
+ }
+ int error = 0;
+ pa_sample_spec ss = {};
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = m_Settings.Samplerate;
+ ss.channels = m_Settings.Channels;
+ pa_buffer_attr ba = {};
+ ba.minreq = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.UpdateInterval), m_Settings.GetBytesPerFrame());
+ ba.maxlength = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.Latency), m_Settings.GetBytesPerFrame());
+ ba.tlength = ba.maxlength - ba.minreq;
+ ba.prebuf = ba.tlength;
+ ba.fragsize = 0;
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ m_EffectiveBufferAttributes.Latency = static_cast<double>(ba.maxlength) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.UpdateInterval = static_cast<double>(ba.minreq) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.NumBuffers = 1;
+ m_OutputBuffer.resize(ba.minreq / m_Settings.sampleFormat.GetSampleSize());
+ m_PA_SimpleOutput = pa_simple_new(
+ NULL,
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ PA_STREAM_PLAYBACK,
+ ((GetDeviceInternalID() == MPT_USTRING("0")) ? NULL : mpt::transcode<std::string>(mpt::common_encoding::utf8, GetDeviceInternalID()).c_str()),
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ &ss,
+ NULL,
+ (m_Settings.ExclusiveMode ? &ba : NULL),
+ &error);
+ if(!m_PA_SimpleOutput)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_new failed: {}")(PulseErrorString(error)));
+ InternalClose();
+ return false;
+ }
+ return true;
+}
+
+
+void PulseaudioSimple::InternalStartFromSoundThread()
+{
+ return;
+}
+
+
+void PulseaudioSimple::InternalFillAudioBuffer()
+{
+ bool needsClose = false;
+ int error = 0;
+ error = 0;
+ pa_usec_t latency_usec = pa_simple_get_latency(m_PA_SimpleOutput, &error);
+ if(error != 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_get_latency failed: {}")(PulseErrorString(error)));
+ RequestClose();
+ return;
+ }
+ error = 0;
+ // We add the update period to the latency because:
+ // 1. PulseAudio latency calculation is done before we are actually
+ // refilling.
+ // 2. We have 1 additional period latency becasue the writing is blocking and
+ // audio has will be calculated almost one period in advance in the worst
+ // case.
+ // I think, in total we only need to add the period once.
+ std::size_t latencyFrames = 0;
+ latencyFrames += (latency_usec * m_Settings.Samplerate) / 1000000;
+ latencyFrames += 1 * (m_OutputBuffer.size() / m_Settings.Channels);
+ CallbackLockedAudioReadPrepare(m_OutputBuffer.size() / m_Settings.Channels, latencyFrames);
+ CallbackLockedAudioProcess(m_OutputBuffer.data(), nullptr, m_OutputBuffer.size() / m_Settings.Channels);
+ error = 0;
+ if(pa_simple_write(m_PA_SimpleOutput, &(m_OutputBuffer[0]), m_OutputBuffer.size() * sizeof(float32), &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_write failed: {}")(PulseErrorString(error)));
+ needsClose = true;
+ }
+ m_StatisticLastLatencyFrames.store(latencyFrames);
+ CallbackLockedAudioProcessDone();
+ if(needsClose)
+ {
+ RequestClose();
+ return;
+ }
+}
+
+
+void PulseaudioSimple::InternalWaitFromSoundThread()
+{
+ // We block in InternalFillAudioBuffer and thus have no need to wait further
+ return;
+}
+
+
+SoundDevice::BufferAttributes PulseaudioSimple::InternalGetEffectiveBufferAttributes() const
+{
+ return m_EffectiveBufferAttributes;
+}
+
+
+SoundDevice::Statistics PulseaudioSimple::GetStatistics() const
+{
+ SoundDevice::Statistics stats;
+ stats.InstantaneousLatency = static_cast<double>(m_StatisticLastLatencyFrames.load()) / static_cast<double>(m_Settings.Samplerate);
+ stats.LastUpdateInterval = m_EffectiveBufferAttributes.UpdateInterval;
+ stats.text = mpt::ustring();
+ return stats;
+}
+
+
+void PulseaudioSimple::InternalStopFromSoundThread()
+{
+ int error = 0;
+ bool oldVersion = false;
+ std::vector<uint64> version = mpt::split_parse<uint64>(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, pa_get_library_version() ? pa_get_library_version() : ""));
+ if(!version.empty())
+ {
+ if(version[0] < 4)
+ {
+ oldVersion = true;
+ }
+ }
+ if(oldVersion)
+ {
+ // draining is awfully slow with pulseaudio version < 4.0.0,
+ // just flush there
+ error = 0;
+ if(pa_simple_flush(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_flush failed: {}")(PulseErrorString(error)));
+ }
+ } else
+ {
+ error = 0;
+ if(pa_simple_drain(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_drain failed: {}")(PulseErrorString(error)));
+ }
+ }
+ return;
+}
+
+
+bool PulseaudioSimple::InternalClose()
+{
+ if(m_PA_SimpleOutput)
+ {
+ pa_simple_free(m_PA_SimpleOutput);
+ m_PA_SimpleOutput = nullptr;
+ }
+ m_OutputBuffer.resize(0);
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ return true;
+}
+
+
+PulseaudioSimple::~PulseaudioSimple()
+{
+ return;
+}
+
+
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp
new file mode 100644
index 00000000..0fb790bf
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <memory>
+#include <vector>
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+
+
+class PulseaudioSimple
+ : public ThreadBase
+{
+private:
+ static mpt::ustring PulseErrorString(int error);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+public:
+ PulseaudioSimple(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool InternalIsOpen() const;
+ bool InternalOpen();
+ void InternalStartFromSoundThread();
+ void InternalFillAudioBuffer();
+ void InternalWaitFromSoundThread();
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ void InternalStopFromSoundThread();
+ bool InternalClose();
+ ~PulseaudioSimple();
+
+private:
+ pa_simple *m_PA_SimpleOutput;
+ SoundDevice::BufferAttributes m_EffectiveBufferAttributes;
+ std::vector<float32> m_OutputBuffer;
+ std::atomic<uint32> m_StatisticLastLatencyFrames;
+};
+
+
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp
new file mode 100644
index 00000000..237e2228
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp
@@ -0,0 +1,475 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePulseaudio.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <vector>
+
+#include <cstddef>
+#include <cstring>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+
+#if defined(MPT_WITH_PULSEAUDIO)
+
+
+mpt::ustring Pulseaudio::PulseErrorString(int error)
+{
+ if(error == 0)
+ {
+ return mpt::ustring();
+ }
+ const char *str = pa_strerror(error);
+ if(!str)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ if(std::strlen(str) == 0)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ return MPT_UFORMAT_MESSAGE("{} (error={})")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, str), error);
+}
+
+
+static void PulseAudioSinkInfoListCallback(pa_context * /* c */, const pa_sink_info *i, int /* eol */, void *userdata)
+{
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_USTRING("PulseAudioSinkInfoListCallback"));
+ std::vector<SoundDevice::Info> *devices_ = reinterpret_cast<std::vector<SoundDevice::Info> *>(userdata);
+ if(!devices_)
+ {
+ return;
+ }
+ std::vector<SoundDevice::Info> &devices = *devices_;
+ if(!i)
+ {
+ return;
+ }
+ if(!i->name)
+ {
+ return;
+ }
+ if(!i->description)
+ {
+ return;
+ }
+ if(i->n_ports <= 0)
+ {
+ return;
+ }
+ for(uint32 port = 0; port < i->n_ports; ++port)
+ {
+ // we skip all sinks without ports or with all ports known to be currently unavailable
+ if(!i->ports)
+ {
+ break;
+ }
+ if(!i->ports[port])
+ {
+ continue;
+ }
+ if(i->ports[port]->available == PA_PORT_AVAILABLE_NO)
+ {
+ continue;
+ }
+ SoundDevice::Info info;
+ info.type = MPT_USTRING("PulseAudio");
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->name);
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->description);
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = Info::Default::None;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+ break;
+ }
+}
+
+
+std::vector<SoundDevice::Info> Pulseaudio::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ std::vector<SoundDevice::Info> devices;
+ SoundDevice::Info info;
+ info.type = MPT_USTRING("PulseAudio");
+ info.internalID = MPT_USTRING("0");
+ info.name = MPT_USTRING("Default Device");
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = Info::Default::Managed;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+
+ int result = 0;
+ pa_mainloop *m = nullptr;
+ pa_context *c = nullptr;
+ bool doneConnect = false;
+ pa_context_state_t cs = PA_CONTEXT_UNCONNECTED;
+ pa_operation *o = nullptr;
+ pa_operation_state_t s = PA_OPERATION_RUNNING;
+
+ m = pa_mainloop_new();
+ if(!m)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_new"));
+ goto cleanup;
+ }
+ c = pa_context_new(pa_mainloop_get_api(m), mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::ustring()).c_str()); // TODO: get AppInfo
+ if(!c)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_new"));
+ goto cleanup;
+ }
+ if(pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ doneConnect = false;
+ while(!doneConnect)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ cs = pa_context_get_state(c);
+ switch(cs)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ doneConnect = true;
+ break;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ default:
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ break;
+ }
+ }
+ o = pa_context_get_sink_info_list(c, &PulseAudioSinkInfoListCallback, &devices);
+ if(!o)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_get_sink_info_list: ") + PulseErrorString(pa_context_errno(c)));
+ goto cleanup;
+ }
+ s = PA_OPERATION_RUNNING;
+ while((s = pa_operation_get_state(o)) == PA_OPERATION_RUNNING)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ }
+ if(s == PA_OPERATION_CANCELLED)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_operation_get_state"));
+ goto cleanup;
+ }
+ goto cleanup;
+
+cleanup:
+
+ if(o)
+ {
+ pa_operation_unref(o);
+ o = nullptr;
+ }
+ if(c)
+ {
+ pa_context_disconnect(c);
+ pa_context_unref(c);
+ c = nullptr;
+ }
+ if(m)
+ {
+ pa_mainloop_quit(m, 0);
+ pa_mainloop_run(m, &result);
+ pa_mainloop_free(m);
+ m = nullptr;
+ }
+
+ return devices;
+}
+
+
+Pulseaudio::Pulseaudio(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : ThreadBase(logger, info, sysInfo)
+ , m_PA_SimpleOutput(nullptr)
+ , m_StatisticLastLatencyFrames(0)
+{
+ return;
+}
+
+
+SoundDevice::Caps Pulseaudio::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true; // TODO: poll PulseAudio
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = true;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use early requests");
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.005;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ caps.DefaultSettings.ExclusiveMode = true;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps Pulseaudio::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ caps.supportedSampleRates = baseSampleRates;
+ caps.supportedExclusiveSampleRates = baseSampleRates;
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ return caps;
+}
+
+
+bool Pulseaudio::InternalIsOpen() const
+{
+ return m_PA_SimpleOutput;
+}
+
+
+bool Pulseaudio::InternalOpen()
+{
+ if(m_Settings.sampleFormat != SampleFormat::Float32)
+ {
+ InternalClose();
+ return false;
+ }
+ int error = 0;
+ pa_sample_spec ss = {};
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = m_Settings.Samplerate;
+ ss.channels = m_Settings.Channels;
+ pa_buffer_attr ba = {};
+ ba.minreq = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.UpdateInterval), m_Settings.GetBytesPerFrame());
+ ba.maxlength = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.Latency), m_Settings.GetBytesPerFrame());
+ ba.tlength = ba.maxlength - ba.minreq;
+ ba.prebuf = ba.tlength;
+ ba.fragsize = 0;
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ m_EffectiveBufferAttributes.Latency = static_cast<double>(ba.maxlength) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.UpdateInterval = static_cast<double>(ba.minreq) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.NumBuffers = 1;
+ m_OutputBuffer.resize(ba.minreq / m_Settings.sampleFormat.GetSampleSize());
+ m_PA_SimpleOutput = pa_simple_new(
+ NULL,
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ PA_STREAM_PLAYBACK,
+ ((GetDeviceInternalID() == MPT_USTRING("0")) ? NULL : mpt::transcode<std::string>(mpt::common_encoding::utf8, GetDeviceInternalID()).c_str()),
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ &ss,
+ NULL,
+ (m_Settings.ExclusiveMode ? &ba : NULL),
+ &error);
+ if(!m_PA_SimpleOutput)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_new failed: {}")(PulseErrorString(error)));
+ InternalClose();
+ return false;
+ }
+ return true;
+}
+
+
+void Pulseaudio::InternalStartFromSoundThread()
+{
+ return;
+}
+
+
+void Pulseaudio::InternalFillAudioBuffer()
+{
+ bool needsClose = false;
+ int error = 0;
+ error = 0;
+ pa_usec_t latency_usec = pa_simple_get_latency(m_PA_SimpleOutput, &error);
+ if(error != 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_get_latency failed: {}")(PulseErrorString(error)));
+ RequestClose();
+ return;
+ }
+ error = 0;
+ // We add the update period to the latency because:
+ // 1. PulseAudio latency calculation is done before we are actually
+ // refilling.
+ // 2. We have 1 additional period latency becasue the writing is blocking and
+ // audio has will be calculated almost one period in advance in the worst
+ // case.
+ // I think, in total we only need to add the period once.
+ std::size_t latencyFrames = 0;
+ latencyFrames += (latency_usec * m_Settings.Samplerate) / 1000000;
+ latencyFrames += 1 * (m_OutputBuffer.size() / m_Settings.Channels);
+ CallbackLockedAudioReadPrepare(m_OutputBuffer.size() / m_Settings.Channels, latencyFrames);
+ CallbackLockedAudioProcess(m_OutputBuffer.data(), nullptr, m_OutputBuffer.size() / m_Settings.Channels);
+ error = 0;
+ if(pa_simple_write(m_PA_SimpleOutput, &(m_OutputBuffer[0]), m_OutputBuffer.size() * sizeof(float32), &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_write failed: {}")(PulseErrorString(error)));
+ needsClose = true;
+ }
+ m_StatisticLastLatencyFrames.store(latencyFrames);
+ CallbackLockedAudioProcessDone();
+ if(needsClose)
+ {
+ RequestClose();
+ return;
+ }
+}
+
+
+void Pulseaudio::InternalWaitFromSoundThread()
+{
+ // We block in InternalFillAudioBuffer and thus have no need to wait further
+ return;
+}
+
+
+SoundDevice::BufferAttributes Pulseaudio::InternalGetEffectiveBufferAttributes() const
+{
+ return m_EffectiveBufferAttributes;
+}
+
+
+SoundDevice::Statistics Pulseaudio::GetStatistics() const
+{
+ SoundDevice::Statistics stats;
+ stats.InstantaneousLatency = static_cast<double>(m_StatisticLastLatencyFrames.load()) / static_cast<double>(m_Settings.Samplerate);
+ stats.LastUpdateInterval = m_EffectiveBufferAttributes.UpdateInterval;
+ stats.text = mpt::ustring();
+ return stats;
+}
+
+
+void Pulseaudio::InternalStopFromSoundThread()
+{
+ int error = 0;
+ bool oldVersion = false;
+ std::vector<uint64> version = mpt::split_parse<uint64>(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, pa_get_library_version() ? pa_get_library_version() : ""));
+ if(!version.empty())
+ {
+ if(version[0] < 4)
+ {
+ oldVersion = true;
+ }
+ }
+ if(oldVersion)
+ {
+ // draining is awfully slow with pulseaudio version < 4.0.0,
+ // just flush there
+ error = 0;
+ if(pa_simple_flush(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_flush failed: {}")(PulseErrorString(error)));
+ }
+ } else
+ {
+ error = 0;
+ if(pa_simple_drain(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_drain failed: {}")(PulseErrorString(error)));
+ }
+ }
+ return;
+}
+
+
+bool Pulseaudio::InternalClose()
+{
+ if(m_PA_SimpleOutput)
+ {
+ pa_simple_free(m_PA_SimpleOutput);
+ m_PA_SimpleOutput = nullptr;
+ }
+ m_OutputBuffer.resize(0);
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ return true;
+}
+
+
+Pulseaudio::~Pulseaudio()
+{
+ return;
+}
+
+
+#endif // MPT_WITH_PULSEAUDIO
+
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp
new file mode 100644
index 00000000..a7168c9e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <memory>
+#include <vector>
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+#if defined(MPT_WITH_PULSEAUDIO)
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#endif // MPT_WITH_PULSEAUDIO
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+
+#if defined(MPT_WITH_PULSEAUDIO)
+
+
+class Pulseaudio
+ : public ThreadBase
+{
+private:
+ static mpt::ustring PulseErrorString(int error);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+public:
+ Pulseaudio(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool InternalIsOpen() const;
+ bool InternalOpen();
+ void InternalStartFromSoundThread();
+ void InternalFillAudioBuffer();
+ void InternalWaitFromSoundThread();
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ void InternalStopFromSoundThread();
+ bool InternalClose();
+ ~Pulseaudio();
+
+private:
+ pa_simple *m_PA_SimpleOutput;
+ SoundDevice::BufferAttributes m_EffectiveBufferAttributes;
+ std::vector<float32> m_OutputBuffer;
+ std::atomic<uint32> m_StatisticLastLatencyFrames;
+};
+
+
+#endif // MPT_WITH_PULSEAUDIO
+
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp
new file mode 100644
index 00000000..6f1e1d6f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp
@@ -0,0 +1,779 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceRtAudio.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_RTAUDIO
+
+
+
+static constexpr uint8 ParseDigit(char c)
+{
+ return c - '0';
+}
+
+using RtAudioVersion = std::array<unsigned int, 3>;
+
+static constexpr RtAudioVersion ParseVersion(const char *str)
+{
+ RtAudioVersion version = {0, 0, 0};
+ std::size_t version_pos = 0;
+ while(*str)
+ {
+ const char c = *str;
+ if(c == '.')
+ {
+ version_pos += 1;
+ } else
+ {
+ version[version_pos] = (version[version_pos] * 10) + ParseDigit(c);
+ }
+ str++;
+ }
+ return version;
+}
+
+static constexpr bool RtAudioCheckVersion(const char *wanted_)
+{
+ RtAudioVersion actual = ParseVersion(RTAUDIO_VERSION);
+ RtAudioVersion wanted = ParseVersion(wanted_);
+ if(actual[0] > wanted[0])
+ {
+ return true;
+ } else if(actual[0] == wanted[0])
+ {
+ if(actual[1] > wanted[1])
+ {
+ return true;
+ } else if(actual[1] == wanted[1])
+ {
+ return (actual[2] >= wanted[2]);
+ } else
+ {
+ return false;
+ }
+ } else
+ {
+ return false;
+ }
+}
+
+template <typename RtAudio = ::RtAudio, bool is_v5_1_0 = RtAudioCheckVersion("5.1.0")>
+struct RtAudio_v5_1_0_Shim
+{
+};
+
+template <typename RtAudio>
+struct RtAudio_v5_1_0_Shim<RtAudio, true>
+{
+
+ static inline std::string getApiName(typename RtAudio::Api api)
+ {
+ return RtAudio::getApiName(api);
+ }
+
+ static inline std::string getApiDisplayName(typename RtAudio::Api api)
+ {
+ return RtAudio::getApiDisplayName(api);
+ }
+
+ static inline typename RtAudio::Api getCompiledApiByName(const std::string &name)
+ {
+ return RtAudio::getCompiledApiByName(name);
+ }
+};
+
+template <typename RtAudio>
+struct RtAudio_v5_1_0_Shim<RtAudio, false>
+{
+
+ static constexpr const char *rtaudio_api_names[][2] = {
+ {"unspecified", "Unknown" },
+ {"alsa", "ALSA" },
+ {"pulse", "Pulse" },
+ {"oss", "OpenSoundSystem"},
+ {"jack", "Jack" },
+ {"core", "CoreAudio" },
+ {"wasapi", "WASAPI" },
+ {"asio", "ASIO" },
+ {"ds", "DirectSound" },
+ {"dummy", "Dummy" },
+ };
+
+ static constexpr typename RtAudio::Api rtaudio_all_apis[] = {
+ RtAudio::UNIX_JACK,
+ RtAudio::LINUX_PULSE,
+ RtAudio::LINUX_ALSA,
+ RtAudio::LINUX_OSS,
+ RtAudio::WINDOWS_ASIO,
+ RtAudio::WINDOWS_WASAPI,
+ RtAudio::WINDOWS_DS,
+ RtAudio::MACOSX_CORE,
+ RtAudio::RTAUDIO_DUMMY,
+ RtAudio::UNSPECIFIED,
+ };
+
+ static inline std::string getApiName(typename RtAudio::Api api)
+ {
+ if(api < 0)
+ {
+ return std::string();
+ }
+ if(api >= mpt::saturate_cast<int>(std::size(rtaudio_api_names)))
+ {
+ return std::string();
+ }
+ return rtaudio_api_names[api][0];
+ }
+
+ static inline std::string getApiDisplayName(typename RtAudio::Api api)
+ {
+ if(api < 0)
+ {
+ return std::string();
+ }
+ if(api >= mpt::saturate_cast<int>(std::size(rtaudio_api_names)))
+ {
+ return std::string();
+ }
+ return rtaudio_api_names[api][1];
+ }
+
+ static inline typename RtAudio::Api getCompiledApiByName(const std::string &name)
+ {
+ for(std::size_t i = 0; i < std::size(rtaudio_api_names); ++i)
+ {
+ if(name == rtaudio_api_names[rtaudio_all_apis[i]][0])
+ {
+ return rtaudio_all_apis[i];
+ }
+ }
+ return RtAudio::UNSPECIFIED;
+ }
+};
+
+struct RtAudioShim
+ : RtAudio_v5_1_0_Shim<>
+{
+};
+
+
+static RtAudioFormat SampleFormatToRtAudioFormat(SampleFormat sampleFormat)
+{
+ RtAudioFormat result = RtAudioFormat();
+ if(sampleFormat.IsFloat())
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 32: result = RTAUDIO_FLOAT32; break;
+ case 64: result = RTAUDIO_FLOAT64; break;
+ }
+ } else if(sampleFormat.IsInt())
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: result = RTAUDIO_SINT8; break;
+ case 16: result = RTAUDIO_SINT16; break;
+ case 24: result = RTAUDIO_SINT24; break;
+ case 32: result = RTAUDIO_SINT32; break;
+ }
+ }
+ return result;
+}
+
+
+CRtAudioDevice::CRtAudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_RtAudio(std::unique_ptr<RtAudio>())
+ , m_FramesPerChunk(0)
+{
+ m_CurrentFrameBufferOutput = nullptr;
+ m_CurrentFrameBufferInput = nullptr;
+ m_CurrentFrameBufferCount = 0;
+ m_CurrentStreamTime = 0.0;
+ m_StatisticLatencyFrames.store(0);
+ m_StatisticPeriodFrames.store(0);
+ try
+ {
+ m_RtAudio = std::make_unique<RtAudio>(GetApi(info));
+ } catch(const RtAudioError &)
+ {
+ // nothing
+ }
+}
+
+
+CRtAudioDevice::~CRtAudioDevice()
+{
+ Close();
+}
+
+
+bool CRtAudioDevice::InternalOpen()
+{
+ try
+ {
+ if(SampleFormatToRtAudioFormat(m_Settings.sampleFormat) == RtAudioFormat())
+ {
+ return false;
+ }
+ if(ChannelMapping::BaseChannel(m_Settings.Channels, m_Settings.Channels.ToDevice(0)) != m_Settings.Channels)
+ { // only simple base channel mappings are supported
+ return false;
+ }
+ m_OutputStreamParameters.deviceId = GetDevice(GetDeviceInfo());
+ m_OutputStreamParameters.nChannels = m_Settings.Channels;
+ m_OutputStreamParameters.firstChannel = m_Settings.Channels.ToDevice(0);
+ m_InputStreamParameters.deviceId = GetDevice(GetDeviceInfo());
+ m_InputStreamParameters.nChannels = m_Settings.InputChannels;
+ m_InputStreamParameters.firstChannel = m_Settings.InputSourceID;
+ m_FramesPerChunk = mpt::saturate_round<int>(m_Settings.UpdateInterval * m_Settings.Samplerate);
+ m_StreamOptions.flags = RtAudioStreamFlags();
+ m_StreamOptions.numberOfBuffers = mpt::saturate_round<int>(m_Settings.Latency * m_Settings.Samplerate / m_FramesPerChunk);
+ m_StreamOptions.priority = 0;
+ m_StreamOptions.streamName = mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName());
+ if(m_Settings.BoostThreadPriority)
+ {
+ m_StreamOptions.flags |= RTAUDIO_SCHEDULE_REALTIME;
+ m_StreamOptions.priority = m_AppInfo.BoostedThreadPriorityXP;
+ }
+ if(m_Settings.ExclusiveMode)
+ {
+ //m_FramesPerChunk = 0; // auto
+ m_StreamOptions.flags |= RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_HOG_DEVICE;
+ m_StreamOptions.numberOfBuffers = 2;
+ }
+ if(m_RtAudio->getCurrentApi() == RtAudio::Api::WINDOWS_WASAPI)
+ {
+ m_Flags.WantsClippedOutput = true;
+ } else if(m_RtAudio->getCurrentApi() == RtAudio::Api::WINDOWS_DS)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ }
+ m_RtAudio->openStream((m_OutputStreamParameters.nChannels > 0) ? &m_OutputStreamParameters : nullptr, (m_InputStreamParameters.nChannels > 0) ? &m_InputStreamParameters : nullptr, SampleFormatToRtAudioFormat(m_Settings.sampleFormat), m_Settings.Samplerate, &m_FramesPerChunk, &RtAudioCallback, this, &m_StreamOptions, nullptr);
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+bool CRtAudioDevice::InternalClose()
+{
+ try
+ {
+ m_RtAudio->closeStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+bool CRtAudioDevice::InternalStart()
+{
+ try
+ {
+ m_RtAudio->startStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+void CRtAudioDevice::InternalStop()
+{
+ try
+ {
+ m_RtAudio->stopStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return;
+ }
+ return;
+}
+
+
+void CRtAudioDevice::InternalFillAudioBuffer()
+{
+ if(m_CurrentFrameBufferCount == 0)
+ {
+ return;
+ }
+ CallbackLockedAudioReadPrepare(m_CurrentFrameBufferCount, m_FramesPerChunk * m_StreamOptions.numberOfBuffers);
+ CallbackLockedAudioProcessVoid(m_CurrentFrameBufferOutput, m_CurrentFrameBufferInput, m_CurrentFrameBufferCount);
+ m_StatisticLatencyFrames.store(m_CurrentFrameBufferCount * m_StreamOptions.numberOfBuffers);
+ m_StatisticPeriodFrames.store(m_CurrentFrameBufferCount);
+ CallbackLockedAudioProcessDone();
+}
+
+
+int64 CRtAudioDevice::InternalGetStreamPositionFrames() const
+{
+ return mpt::saturate_round<int64>(m_RtAudio->getStreamTime() * m_RtAudio->getStreamSampleRate());
+}
+
+
+SoundDevice::BufferAttributes CRtAudioDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_FramesPerChunk * m_StreamOptions.numberOfBuffers / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.UpdateInterval = m_FramesPerChunk / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.NumBuffers = m_StreamOptions.numberOfBuffers;
+ return bufferAttributes;
+}
+
+
+int CRtAudioDevice::RtAudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData)
+{
+ reinterpret_cast<CRtAudioDevice *>(userData)->AudioCallback(outputBuffer, inputBuffer, nFrames, streamTime, status);
+ return 0; // continue
+}
+
+
+void CRtAudioDevice::AudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
+{
+ m_CurrentFrameBufferOutput = outputBuffer;
+ m_CurrentFrameBufferInput = inputBuffer;
+ m_CurrentFrameBufferCount = nFrames;
+ m_CurrentStreamTime = streamTime;
+ CallbackFillAudioBufferLocked();
+ m_CurrentFrameBufferCount = 0;
+ m_CurrentFrameBufferOutput = 0;
+ m_CurrentFrameBufferInput = 0;
+ if(status != RtAudioStreamStatus())
+ {
+ // maybe
+ // RequestRestart();
+ }
+}
+
+
+SoundDevice::Statistics CRtAudioDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ long latency = 0;
+ try
+ {
+ if(m_RtAudio->isStreamOpen())
+ {
+ latency = m_RtAudio->getStreamLatency();
+ if(m_Settings.InputChannels > 0 && m_Settings.Channels > 0)
+ {
+ latency /= 2;
+ }
+ }
+ } catch(const RtAudioError &)
+ {
+ latency = 0;
+ }
+ if(latency > 0)
+ {
+ result.InstantaneousLatency = latency / static_cast<double>(m_Settings.Samplerate);
+ result.LastUpdateInterval = m_StatisticPeriodFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ } else
+ {
+ result.InstantaneousLatency = m_StatisticLatencyFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ result.LastUpdateInterval = m_StatisticPeriodFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ }
+ return result;
+}
+
+
+SoundDevice::Caps CRtAudioDevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+ if(!m_RtAudio)
+ {
+ return caps;
+ }
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = m_RtAudio->getDeviceInfo(GetDevice(GetDeviceInfo()));
+ } catch(const RtAudioError &)
+ {
+ return caps;
+ }
+ caps.Available = rtinfo.probed;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false; // only base channel is supported, and that does not make too much sense for non-ASIO backends
+ caps.CanInput = (rtinfo.inputChannels > 0);
+ caps.HasNamedInputSources = true;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Exclusive Mode");
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CRtAudioDevice::GetDeviceDynamicCaps(const std::vector<uint32> & /* baseSampleRates */)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps caps;
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = m_RtAudio->getDeviceInfo(GetDevice(GetDeviceInfo()));
+ } catch(const RtAudioError &)
+ {
+ return caps;
+ }
+ if(!rtinfo.probed)
+ {
+ return caps;
+ }
+ caps.inputSourceNames.clear();
+ for(unsigned int channel = 0; channel < rtinfo.inputChannels; ++channel)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(channel, MPT_USTRING("Channel ") + mpt::format<mpt::ustring>::dec(channel + 1)));
+ }
+ mpt::append(caps.supportedSampleRates, rtinfo.sampleRates);
+ std::reverse(caps.supportedSampleRates.begin(), caps.supportedSampleRates.end());
+ mpt::append(caps.supportedExclusiveSampleRates, rtinfo.sampleRates);
+ std::reverse(caps.supportedExclusiveSampleRates.begin(), caps.supportedExclusiveSampleRates.end());
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(rtinfo.nativeFormats & RTAUDIO_SINT8)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int8);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT16)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT24)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int24);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT32)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int32);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_FLOAT32)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Float32);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_FLOAT64)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Float64);
+ }
+ for(unsigned int channel = 0; channel < rtinfo.outputChannels; ++channel)
+ {
+ caps.channelNames.push_back(MPT_UFORMAT_MESSAGE("Output Channel {}")(channel));
+ }
+ for(unsigned int channel = 0; channel < rtinfo.inputChannels; ++channel)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(channel), MPT_UFORMAT_MESSAGE("Input Channel {}")(channel)));
+ }
+ return caps;
+}
+
+
+void CRtAudioDevice::SendError(const RtAudioError &e)
+{
+ LogLevel level = LogError;
+ switch(e.getType())
+ {
+ case RtAudioError::WARNING:
+ level = LogWarning;
+ break;
+ case RtAudioError::DEBUG_WARNING:
+ level = LogDebug;
+ break;
+ case RtAudioError::UNSPECIFIED:
+ level = LogError;
+ break;
+ case RtAudioError::NO_DEVICES_FOUND:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_DEVICE:
+ level = LogError;
+ break;
+ case RtAudioError::MEMORY_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_PARAMETER:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_USE:
+ level = LogError;
+ break;
+ case RtAudioError::DRIVER_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::SYSTEM_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::THREAD_ERROR:
+ level = LogError;
+ break;
+ default:
+ level = LogError;
+ break;
+ }
+ SendDeviceMessage(level, mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, e.getMessage()));
+}
+
+
+RtAudio::Api CRtAudioDevice::GetApi(SoundDevice::Info info)
+{
+ std::vector<mpt::ustring> apidev = mpt::split(info.internalID, MPT_USTRING(","));
+ if(apidev.size() != 2)
+ {
+ return RtAudio::UNSPECIFIED;
+ }
+ return RtAudioShim::getCompiledApiByName(mpt::transcode<std::string>(mpt::common_encoding::utf8, apidev[0]));
+}
+
+
+unsigned int CRtAudioDevice::GetDevice(SoundDevice::Info info)
+{
+ std::vector<mpt::ustring> apidev = mpt::split(info.internalID, MPT_USTRING(","));
+ if(apidev.size() != 2)
+ {
+ return 0;
+ }
+ return mpt::ConvertStringTo<unsigned int>(apidev[1]);
+}
+
+
+std::vector<SoundDevice::Info> CRtAudioDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if 0
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ std::vector<RtAudio::Api> apis;
+ RtAudio::getCompiledApi(apis);
+ for(const auto &api : apis)
+ {
+ if(api == RtAudio::RTAUDIO_DUMMY)
+ {
+ continue;
+ }
+ try
+ {
+ RtAudio rtaudio(api);
+ for(unsigned int device = 0; device < rtaudio.getDeviceCount(); ++device)
+ {
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = rtaudio.getDeviceInfo(device);
+ } catch(const RtAudioError &)
+ {
+ continue;
+ }
+ if(!rtinfo.probed)
+ {
+ continue;
+ }
+ SoundDevice::Info info = SoundDevice::Info();
+ info.type = MPT_USTRING("RtAudio") + MPT_USTRING("-") + mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiName(rtaudio.getCurrentApi()));
+ std::vector<mpt::ustring> apidev;
+ apidev.push_back(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiName(rtaudio.getCurrentApi())));
+ apidev.push_back(mpt::format<mpt::ustring>::val(device));
+ info.internalID = mpt::join(apidev, MPT_USTRING(","));
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, rtinfo.name);
+ info.apiName = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiDisplayName(rtaudio.getCurrentApi()));
+ info.extraData[MPT_USTRING("RtAudio-ApiDisplayName")] = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiDisplayName(rtaudio.getCurrentApi()));
+ info.apiPath.push_back(MPT_USTRING("RtAudio"));
+ info.useNameAsIdentifier = true;
+ // clang-format off
+ switch(rtaudio.getCurrentApi())
+ {
+ case RtAudio::LINUX_ALSA:
+ info.apiName = MPT_USTRING("ALSA");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::LINUX_PULSE:
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::LINUX_OSS:
+ info.apiName = MPT_USTRING("OSS");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Api::Native : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Mixing::Hardware : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Software : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::UNIX_JACK:
+ info.apiName = MPT_USTRING("JACK");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::MACOSX_CORE:
+ info.apiName = MPT_USTRING("CoreAudio");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_WASAPI:
+ info.apiName = MPT_USTRING("WASAPI");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ?
+ sysInfo.IsWindowsOriginal() ?
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7) ?
+ Info::Usability::Usable
+ :
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ?
+ Info::Usability::Experimental
+ :
+ Info::Usability::NotAvailable
+ :
+ Info::Usability::Usable
+ :
+ Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_ASIO:
+ info.apiName = MPT_USTRING("ASIO");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_DS:
+ info.apiName = MPT_USTRING("DirectSound");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ Info::Usability::Broken, // sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::Windows::Version::Win7) ? Info::Usability:Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ default:
+ // nothing
+ break;
+ }
+ // clang-format on
+
+ devices.push_back(info);
+ }
+ } catch(const RtAudioError &)
+ {
+ // nothing
+ }
+ }
+ return devices;
+}
+
+
+#endif // MPT_WITH_RTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp
new file mode 100644
index 00000000..0a414bee
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceBase.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#ifdef MPT_WITH_RTAUDIO
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4244) // conversion from 'int' to 'unsigned char', possible loss of data
+#endif
+#if MPT_COMPILER_GCC
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-copy"
+#endif
+#include <RtAudio.h>
+#if MPT_COMPILER_GCC
+#pragma GCC diagnostic pop
+#endif
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif
+#endif // MPT_WITH_RTAUDIO
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_RTAUDIO
+
+
+class CRtAudioDevice : public SoundDevice::Base
+{
+
+protected:
+ std::unique_ptr<RtAudio> m_RtAudio;
+
+ RtAudio::StreamParameters m_InputStreamParameters;
+ RtAudio::StreamParameters m_OutputStreamParameters;
+ unsigned int m_FramesPerChunk;
+ RtAudio::StreamOptions m_StreamOptions;
+
+ void *m_CurrentFrameBufferOutput;
+ void *m_CurrentFrameBufferInput;
+ unsigned int m_CurrentFrameBufferCount;
+ double m_CurrentStreamTime;
+
+ std::atomic<uint32> m_StatisticLatencyFrames;
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CRtAudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CRtAudioDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_RtAudio && m_RtAudio->isStreamOpen(); }
+ bool InternalHasGetStreamPosition() const { return true; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+private:
+ void SendError(const RtAudioError &e);
+
+ void AudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
+
+ static int RtAudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData);
+
+ static RtAudio::Api GetApi(SoundDevice::Info info);
+ static unsigned int GetDevice(SoundDevice::Info info);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+
+#endif // MPT_WITH_RTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp
new file mode 100644
index 00000000..67efc310
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp
@@ -0,0 +1,664 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceUtilities.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <thread>
+#include <utility>
+
+#include <cassert>
+
+#if MPT_OS_WINDOWS
+#if(_WIN32_WINNT >= 0x600)
+#include <avrt.h>
+#endif
+#include <mmsystem.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if !MPT_OS_WINDOWS
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#ifdef _POSIX_PRIORITY_SCHEDULING // from unistd.h
+#include <sched.h>
+#endif
+#endif
+
+#if defined(MPT_WITH_DBUS)
+#include <dbus/dbus.h>
+#endif
+#if defined(MPT_WITH_RTKIT)
+#include "rtkit/rtkit.h"
+#endif
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+bool FillWaveFormatExtensible(WAVEFORMATEXTENSIBLE &WaveFormat, const SoundDevice::Settings &m_Settings)
+{
+ WaveFormat = {};
+ if(!m_Settings.sampleFormat.IsValid())
+ {
+ return false;
+ }
+ WaveFormat.Format.wFormatTag = m_Settings.sampleFormat.IsFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
+ WaveFormat.Format.nChannels = (WORD)m_Settings.Channels;
+ WaveFormat.Format.nSamplesPerSec = m_Settings.Samplerate;
+ WaveFormat.Format.nAvgBytesPerSec = (DWORD)m_Settings.GetBytesPerSecond();
+ WaveFormat.Format.nBlockAlign = (WORD)m_Settings.GetBytesPerFrame();
+ WaveFormat.Format.wBitsPerSample = (WORD)m_Settings.sampleFormat.GetBitsPerSample();
+ WaveFormat.Format.cbSize = 0;
+ if((WaveFormat.Format.wBitsPerSample > 16 && m_Settings.sampleFormat.IsInt()) || (WaveFormat.Format.nChannels > 2))
+ {
+ WaveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ WaveFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ WaveFormat.Samples.wValidBitsPerSample = WaveFormat.Format.wBitsPerSample;
+ switch(WaveFormat.Format.nChannels)
+ {
+ case 1: WaveFormat.dwChannelMask = SPEAKER_FRONT_CENTER; break;
+ case 2: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break;
+ case 3: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_CENTER; break;
+ case 4: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
+ default:
+ WaveFormat.dwChannelMask = 0;
+ return false;
+ break;
+ }
+ const GUID guid_MEDIASUBTYPE_PCM = {
+ 0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x0, 0xAA, 0x0, 0x38, 0x9B, 0x71}
+ };
+ const GUID guid_MEDIASUBTYPE_IEEE_FLOAT = {
+ 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}
+ };
+ WaveFormat.SubFormat = m_Settings.sampleFormat.IsFloat() ? guid_MEDIASUBTYPE_IEEE_FLOAT : guid_MEDIASUBTYPE_PCM;
+ }
+ return true;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_WINDOWS
+
+CAudioThread::CAudioThread(CSoundDeviceWithThread &SoundDevice)
+ : m_SoundDevice(SoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_MMCSSClass = mpt::transcode<mpt::winstring>(m_SoundDevice.m_AppInfo.BoostedThreadMMCSSClassVista);
+ m_WakeupInterval = 0.0;
+ m_hPlayThread = NULL;
+ m_dwPlayThreadId = 0;
+ m_hAudioWakeUp = NULL;
+ m_hAudioThreadTerminateRequest = NULL;
+ m_hAudioThreadGoneIdle = NULL;
+ m_hHardwareWakeupEvent = INVALID_HANDLE_VALUE;
+ m_AudioThreadActive = 0;
+ m_hAudioWakeUp = CreateEvent(NULL, FALSE, FALSE, NULL);
+ m_hAudioThreadTerminateRequest = CreateEvent(NULL, FALSE, FALSE, NULL);
+ m_hAudioThreadGoneIdle = CreateEvent(NULL, TRUE, FALSE, NULL);
+ m_hPlayThread = CreateThread(NULL, 0, AudioThreadWrapper, (LPVOID)this, 0, &m_dwPlayThreadId);
+}
+
+
+CAudioThread::~CAudioThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hPlayThread != NULL)
+ {
+ SetEvent(m_hAudioThreadTerminateRequest);
+ WaitForSingleObject(m_hPlayThread, INFINITE);
+ m_dwPlayThreadId = 0;
+ m_hPlayThread = NULL;
+ }
+ if(m_hAudioThreadTerminateRequest)
+ {
+ CloseHandle(m_hAudioThreadTerminateRequest);
+ m_hAudioThreadTerminateRequest = 0;
+ }
+ if(m_hAudioThreadGoneIdle != NULL)
+ {
+ CloseHandle(m_hAudioThreadGoneIdle);
+ m_hAudioThreadGoneIdle = 0;
+ }
+ if(m_hAudioWakeUp != NULL)
+ {
+ CloseHandle(m_hAudioWakeUp);
+ m_hAudioWakeUp = NULL;
+ }
+}
+
+
+CPriorityBooster::CPriorityBooster(SoundDevice::SysInfo sysInfo, bool boostPriority, const mpt::winstring &priorityClass, int priority)
+ : m_SysInfo(sysInfo)
+ , m_BoostPriority(boostPriority)
+ , m_Priority(priority)
+ , task_idx(0)
+ , hTask(NULL)
+ , oldPriority(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#ifdef MPT_BUILD_DEBUG
+ m_BoostPriority = false;
+#endif
+ if(m_BoostPriority)
+ {
+#if(_WIN32_WINNT >= 0x600)
+ if(!priorityClass.empty())
+ {
+ hTask = AvSetMmThreadCharacteristics(priorityClass.c_str(), &task_idx);
+ }
+ MPT_UNUSED(priority);
+#else // < Vista
+ oldPriority = GetThreadPriority(GetCurrentThread());
+ SetThreadPriority(GetCurrentThread(), m_Priority);
+ MPT_UNUSED(priorityClass);
+#endif
+ }
+}
+
+
+CPriorityBooster::~CPriorityBooster()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_BoostPriority)
+ {
+#if(_WIN32_WINNT >= 0x600)
+ if(hTask)
+ {
+ AvRevertMmThreadCharacteristics(hTask);
+ }
+ hTask = NULL;
+ task_idx = 0;
+#else // < Vista
+ SetThreadPriority(GetCurrentThread(), oldPriority);
+#endif
+ }
+}
+
+
+class CPeriodicWaker
+{
+private:
+ double sleepSeconds;
+ long sleepMilliseconds;
+ int64 sleep100Nanoseconds;
+
+ bool periodic_nt_timer;
+
+ HANDLE sleepEvent;
+
+public:
+ explicit CPeriodicWaker(double sleepSeconds_)
+ : sleepSeconds(sleepSeconds_)
+ {
+
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ sleepMilliseconds = static_cast<long>(sleepSeconds * 1000.0);
+ sleep100Nanoseconds = static_cast<int64>(sleepSeconds * 10000000.0);
+ if(sleepMilliseconds < 1) sleepMilliseconds = 1;
+ if(sleep100Nanoseconds < 1) sleep100Nanoseconds = 1;
+
+ periodic_nt_timer = (sleep100Nanoseconds >= 10000); // can be represented as a millisecond period, otherwise use non-periodic timers which allow higher precision but might me slower because we have to set them again in each period
+
+ sleepEvent = NULL;
+
+ if(periodic_nt_timer)
+ {
+ sleepEvent = CreateWaitableTimer(NULL, FALSE, NULL);
+ if(!sleepEvent)
+ {
+ mpt::throw_out_of_memory();
+ }
+ LARGE_INTEGER dueTime;
+ dueTime.QuadPart = 0 - sleep100Nanoseconds; // negative time means relative
+ SetWaitableTimer(sleepEvent, &dueTime, sleepMilliseconds, NULL, NULL, FALSE);
+ } else
+ {
+ sleepEvent = CreateWaitableTimer(NULL, TRUE, NULL);
+ if(!sleepEvent)
+ {
+ mpt::throw_out_of_memory();
+ }
+ }
+ }
+
+ CPeriodicWaker(const CPeriodicWaker &) = delete;
+ CPeriodicWaker &operator=(const CPeriodicWaker &) = delete;
+
+ long GetSleepMilliseconds() const
+ {
+ return sleepMilliseconds;
+ }
+
+ HANDLE GetWakeupEvent() const
+ {
+ return sleepEvent;
+ }
+
+ void Retrigger()
+ {
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!periodic_nt_timer)
+ {
+ LARGE_INTEGER dueTime;
+ dueTime.QuadPart = 0 - sleep100Nanoseconds; // negative time means relative
+ SetWaitableTimer(sleepEvent, &dueTime, 0, NULL, NULL, FALSE);
+ }
+ }
+
+ ~CPeriodicWaker()
+ {
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(periodic_nt_timer)
+ {
+ CancelWaitableTimer(sleepEvent);
+ }
+ CloseHandle(sleepEvent);
+ sleepEvent = NULL;
+ }
+};
+
+
+DWORD WINAPI CAudioThread::AudioThreadWrapper(LPVOID user)
+{
+ return ((CAudioThread *)user)->AudioThread();
+}
+DWORD CAudioThread::AudioThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ bool terminate = false;
+ while(!terminate)
+ {
+
+ bool idle = true;
+ while(!terminate && idle)
+ {
+ HANDLE waithandles[2] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp};
+ SetEvent(m_hAudioThreadGoneIdle);
+ switch(WaitForMultipleObjects(2, waithandles, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ case WAIT_OBJECT_0 + 1:
+ idle = false;
+ break;
+ }
+ }
+
+ if(!terminate)
+ {
+
+ CPriorityBooster priorityBooster(m_SoundDevice.GetSysInfo(), m_SoundDevice.m_Settings.BoostThreadPriority, m_MMCSSClass, m_SoundDevice.m_AppInfo.BoostedThreadPriorityXP);
+ CPeriodicWaker periodicWaker(m_WakeupInterval);
+
+ m_SoundDevice.StartFromSoundThread();
+
+ while(!terminate && IsActive())
+ {
+
+ m_SoundDevice.FillAudioBufferLocked();
+
+ periodicWaker.Retrigger();
+
+ if(m_hHardwareWakeupEvent != INVALID_HANDLE_VALUE)
+ {
+ HANDLE waithandles[4] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp, m_hHardwareWakeupEvent, periodicWaker.GetWakeupEvent()};
+ switch(WaitForMultipleObjects(4, waithandles, FALSE, periodicWaker.GetSleepMilliseconds()))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ }
+ } else
+ {
+ HANDLE waithandles[3] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp, periodicWaker.GetWakeupEvent()};
+ switch(WaitForMultipleObjects(3, waithandles, FALSE, periodicWaker.GetSleepMilliseconds()))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ }
+ }
+ }
+
+ m_SoundDevice.StopFromSoundThread();
+ }
+ }
+
+ SetEvent(m_hAudioThreadGoneIdle);
+
+ return 0;
+}
+
+
+void CAudioThread::SetWakeupEvent(HANDLE ev)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_hHardwareWakeupEvent = ev;
+}
+
+
+void CAudioThread::SetWakeupInterval(double seconds)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_WakeupInterval = seconds;
+}
+
+
+bool CAudioThread::IsActive()
+{
+ return InterlockedExchangeAdd(&m_AudioThreadActive, 0) ? true : false;
+}
+
+
+void CAudioThread::Activate()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(InterlockedExchangeAdd(&m_AudioThreadActive, 0))
+ {
+ assert(false);
+ return;
+ }
+ ResetEvent(m_hAudioThreadGoneIdle);
+ InterlockedExchange(&m_AudioThreadActive, 1);
+ SetEvent(m_hAudioWakeUp);
+}
+
+
+void CAudioThread::Deactivate()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InterlockedExchangeAdd(&m_AudioThreadActive, 0))
+ {
+ assert(false);
+ return;
+ }
+ InterlockedExchange(&m_AudioThreadActive, 0);
+ WaitForSingleObject(m_hAudioThreadGoneIdle, INFINITE);
+}
+
+
+CSoundDeviceWithThread::CSoundDeviceWithThread(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo), m_AudioThread(*this)
+{
+ return;
+}
+
+
+CSoundDeviceWithThread::~CSoundDeviceWithThread()
+{
+ return;
+}
+
+
+void CSoundDeviceWithThread::FillAudioBufferLocked()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ CallbackFillAudioBufferLocked();
+}
+
+
+void CSoundDeviceWithThread::SetWakeupEvent(HANDLE ev)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.SetWakeupEvent(ev);
+}
+
+
+void CSoundDeviceWithThread::SetWakeupInterval(double seconds)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.SetWakeupInterval(seconds);
+}
+
+
+bool CSoundDeviceWithThread::InternalStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.Activate();
+ return true;
+}
+
+
+void CSoundDeviceWithThread::InternalStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.Deactivate();
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+class ThreadPriorityGuardImpl
+{
+
+private:
+ ILogger &m_Logger;
+ bool active;
+ bool successfull;
+ bool realtime;
+ int niceness;
+ int rt_priority;
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ DBusConnection *bus;
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+
+private:
+ ILogger &GetLogger() const
+ {
+ return m_Logger;
+ }
+
+public:
+ ThreadPriorityGuardImpl(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority)
+ : m_Logger(logger)
+ , active(active)
+ , successfull(false)
+ , realtime(realtime)
+ , niceness(niceness)
+ , rt_priority(rt_priority)
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ , bus(NULL)
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ {
+ if(active)
+ {
+ if(realtime)
+ {
+#ifdef _POSIX_PRIORITY_SCHEDULING
+ sched_param p = sched_param{};
+ p.sched_priority = rt_priority;
+#if MPT_OS_LINUX
+ if(sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, &p) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#endif
+ }
+#else
+ if(sched_setscheduler(0, SCHED_RR, &p) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#endif
+ }
+#endif
+#endif
+ } else
+ {
+ if(setpriority(PRIO_PROCESS, 0, niceness) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("setpriority: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("setpriority: {}")(errno));
+#endif
+ }
+ }
+ if(!successfull)
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ DBusError error;
+ dbus_error_init(&error);
+ bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if(!bus)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("DBus: dbus_bus_get: {}")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, error.message)));
+ }
+ dbus_error_free(&error);
+ if(bus)
+ {
+ if(realtime)
+ {
+ int e = rtkit_make_realtime(bus, 0, rt_priority);
+ if(e != 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("RtKit: rtkit_make_realtime: {}")(e));
+ } else
+ {
+ successfull = true;
+ }
+ } else
+ {
+ int e = rtkit_make_high_priority(bus, 0, niceness);
+ if(e != 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("RtKit: rtkit_make_high_priority: {}")(e));
+ } else
+ {
+ successfull = true;
+ }
+ }
+ }
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ }
+ }
+ }
+
+ ~ThreadPriorityGuardImpl()
+ {
+ if(active)
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ if(bus)
+ {
+ // TODO: Do we want to reset priorities here?
+ dbus_connection_unref(bus);
+ bus = NULL;
+ }
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ }
+ }
+};
+
+
+ThreadPriorityGuard::ThreadPriorityGuard(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority)
+ : impl(std::make_unique<ThreadPriorityGuardImpl>(logger, active, realtime, niceness, rt_priority))
+{
+ return;
+}
+
+
+ThreadPriorityGuard::~ThreadPriorityGuard()
+{
+ return;
+}
+
+
+ThreadBase::ThreadBase(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : Base(logger, info, sysInfo)
+ , m_ThreadStopRequest(false)
+{
+ return;
+}
+
+bool ThreadBase::InternalStart()
+{
+ m_ThreadStopRequest.store(false);
+ m_Thread = std::move(std::thread(&ThreadProcStatic, this));
+ m_ThreadStarted.wait();
+ m_ThreadStarted.post();
+ return true;
+}
+
+void ThreadBase::ThreadProcStatic(ThreadBase *this_)
+{
+ this_->ThreadProc();
+}
+
+void ThreadBase::ThreadProc()
+{
+ ThreadPriorityGuard priorityGuard(GetLogger(), m_Settings.BoostThreadPriority, m_AppInfo.BoostedThreadRealtimePosix, m_AppInfo.BoostedThreadNicenessPosix, m_AppInfo.BoostedThreadRealtimePosix);
+ m_ThreadStarted.post();
+ InternalStartFromSoundThread();
+ while(!m_ThreadStopRequest.load())
+ {
+ CallbackFillAudioBufferLocked();
+ InternalWaitFromSoundThread();
+ }
+ InternalStopFromSoundThread();
+}
+
+void ThreadBase::InternalStop()
+{
+ m_ThreadStopRequest.store(true);
+ m_Thread.join();
+ m_Thread = std::move(std::thread());
+ m_ThreadStopRequest.store(false);
+}
+
+ThreadBase::~ThreadBase()
+{
+ return;
+}
+
+
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp
new file mode 100644
index 00000000..68234874
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+// we use c++11 in native support library
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <thread>
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+#if MPT_OS_WINDOWS
+#include <mmreg.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+bool FillWaveFormatExtensible(WAVEFORMATEXTENSIBLE &WaveFormat, const SoundDevice::Settings &m_Settings);
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_WINDOWS
+
+
+class CSoundDeviceWithThread;
+
+
+class CPriorityBooster
+{
+private:
+ SoundDevice::SysInfo m_SysInfo;
+ bool m_BoostPriority;
+ int m_Priority;
+ DWORD task_idx;
+ HANDLE hTask;
+ int oldPriority;
+
+public:
+ CPriorityBooster(SoundDevice::SysInfo sysInfo, bool boostPriority, const mpt::winstring &priorityClass, int priority);
+ ~CPriorityBooster();
+};
+
+
+class CAudioThread
+{
+ friend class CPeriodicWaker;
+
+private:
+ CSoundDeviceWithThread &m_SoundDevice;
+ mpt::winstring m_MMCSSClass;
+ double m_WakeupInterval;
+ HANDLE m_hAudioWakeUp;
+ HANDLE m_hPlayThread;
+ HANDLE m_hAudioThreadTerminateRequest;
+ HANDLE m_hAudioThreadGoneIdle;
+ HANDLE m_hHardwareWakeupEvent;
+ DWORD m_dwPlayThreadId;
+ LONG m_AudioThreadActive;
+ static DWORD WINAPI AudioThreadWrapper(LPVOID user);
+ DWORD AudioThread();
+ bool IsActive();
+
+public:
+ CAudioThread(CSoundDeviceWithThread &SoundDevice);
+ CAudioThread(const CAudioThread &) = delete;
+ CAudioThread &operator=(const CAudioThread &) = delete;
+ ~CAudioThread();
+ void Activate();
+ void Deactivate();
+ void SetWakeupEvent(HANDLE ev);
+ void SetWakeupInterval(double seconds);
+};
+
+
+class CSoundDeviceWithThread
+ : public SoundDevice::Base
+{
+ friend class CAudioThread;
+
+protected:
+ CAudioThread m_AudioThread;
+
+private:
+ void FillAudioBufferLocked();
+
+protected:
+ void SetWakeupEvent(HANDLE ev);
+ void SetWakeupInterval(double seconds);
+
+public:
+ CSoundDeviceWithThread(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ virtual ~CSoundDeviceWithThread();
+ bool InternalStart();
+ void InternalStop();
+ virtual void StartFromSoundThread() = 0;
+ virtual void StopFromSoundThread() = 0;
+};
+
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+class semaphore
+{
+private:
+ unsigned int count;
+ unsigned int waiters_count;
+ std::mutex mutex;
+ std::condition_variable count_nonzero;
+
+public:
+ semaphore(unsigned int initial_count = 0)
+ : count(initial_count)
+ , waiters_count(0)
+ {
+ return;
+ }
+ ~semaphore()
+ {
+ return;
+ }
+ void wait()
+ {
+ std::unique_lock<std::mutex> l(mutex);
+ waiters_count++;
+ while(count == 0)
+ {
+ count_nonzero.wait(l);
+ }
+ waiters_count--;
+ count--;
+ }
+ void post()
+ {
+ std::unique_lock<std::mutex> l(mutex);
+ if(waiters_count > 0)
+ {
+ count_nonzero.notify_one();
+ }
+ count++;
+ }
+ void lock()
+ {
+ wait();
+ }
+ void unlock()
+ {
+ post();
+ }
+};
+
+
+class ThreadPriorityGuardImpl;
+
+class ThreadPriorityGuard
+{
+private:
+ std::unique_ptr<ThreadPriorityGuardImpl> impl;
+
+public:
+ ThreadPriorityGuard(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority);
+ ~ThreadPriorityGuard();
+};
+
+
+class ThreadBase
+ : public SoundDevice::Base
+{
+private:
+ semaphore m_ThreadStarted;
+ std::atomic<bool> m_ThreadStopRequest;
+ std::thread m_Thread;
+
+private:
+ static void ThreadProcStatic(ThreadBase *this_);
+ void ThreadProc();
+
+public:
+ ThreadBase(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ virtual ~ThreadBase();
+ bool InternalStart();
+ void InternalStop();
+ virtual void InternalStartFromSoundThread() = 0;
+ virtual void InternalWaitFromSoundThread() = 0;
+ virtual void InternalStopFromSoundThread() = 0;
+};
+
+
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp
new file mode 100644
index 00000000..6665a4bb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp
@@ -0,0 +1,707 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceWaveout.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/buffer.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <array>
+#include <set>
+#include <vector>
+
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+
+
+static constexpr std::size_t WAVEOUT_MINBUFFERS = 3;
+static constexpr std::size_t WAVEOUT_MAXBUFFERS = 4096;
+static constexpr std::size_t WAVEOUT_MINBUFFERFRAMECOUNT = 8;
+static constexpr std::size_t WAVEOUT_MAXBUFFERSIZE = 16384; // fits in int16
+
+
+static inline LONG *interlocked_access(DWORD *p)
+{
+ static_assert(sizeof(LONG) == sizeof(DWORD));
+ return reinterpret_cast<LONG *>(p);
+}
+
+
+CWaveDevice::CWaveDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : CSoundDeviceWithThread(logger, info, sysInfo)
+ , m_DriverBugs(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_ThreadWakeupEvent = NULL;
+ m_Failed = false;
+ m_hWaveOut = NULL;
+ m_nWaveBufferSize = 0;
+ m_JustStarted = false;
+ m_nPreparedHeaders = 0;
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ m_nBuffersPending = 0;
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+}
+
+
+CWaveDevice::~CWaveDevice()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ Close();
+}
+
+
+int CWaveDevice::GetDeviceIndex() const
+{
+ return mpt::ConvertStringTo<int>(GetDeviceInternalID());
+}
+
+
+SoundDevice::Caps CWaveDevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = (GetDeviceIndex() > 0); // no direct mode for WAVE_MAPPER, makes no sense there
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use direct mode");
+ if(GetSysInfo().IsWine)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ } else if(GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CWaveDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps caps;
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ { // emulated on WASAPI
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ } else
+ { // native WDM/VDX, or Wine
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ if(GetDeviceIndex() > 0)
+ { // direct mode
+ if((GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista)) || !GetSysInfo().IsOriginal())
+ { // emulated on WASAPI, or Wine
+ WAVEOUTCAPS woc = {};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(waveOutGetDevCaps(GetDeviceIndex() - 1, &woc, sizeof(woc)) == MMSYSERR_NOERROR)
+ {
+ if(woc.dwFormats & (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(96000);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(48000);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(44100);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(22050);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(11025);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 | WAVE_FORMAT_96M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_96S08))
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Unsigned8);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16))
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ }
+ } else
+ { // native WDM/VDX
+ caps.supportedExclusiveSampleRates.clear();
+ caps.supportedExclusiveModeSampleFormats.clear();
+ std::set<uint32> supportedSampleRates;
+ std::set<SampleFormat> supportedSampleFormats;
+ std::array<SampleFormat, 5> baseSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ for(const uint32 sampleRate : baseSampleRates)
+ {
+ for(const SampleFormat sampleFormat : baseSampleFormats)
+ {
+ WAVEFORMATEXTENSIBLE wfex = {};
+ Settings settings;
+ settings.Samplerate = sampleRate;
+ settings.Channels = 2;
+ settings.sampleFormat = sampleFormat;
+ if(FillWaveFormatExtensible(wfex, settings))
+ {
+ if(waveOutOpen(NULL, GetDeviceIndex() - 1, &wfex.Format, NULL, NULL, CALLBACK_NULL | WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
+ {
+ supportedSampleRates.insert(sampleRate);
+ supportedSampleFormats.insert(sampleFormat);
+ }
+ }
+ }
+ }
+ for(const uint32 sampleRate : baseSampleRates)
+ {
+ if(supportedSampleRates.count(sampleRate) > 0)
+ {
+ caps.supportedExclusiveSampleRates.push_back(sampleRate);
+ }
+ }
+ for(const SampleFormat sampleFormat : baseSampleFormats)
+ {
+ if(supportedSampleFormats.count(sampleFormat) > 0)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ }
+ }
+ }
+ }
+ return caps;
+}
+
+
+bool CWaveDevice::InternalOpen()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Settings.InputChannels > 0)
+ {
+ return false;
+ }
+ WAVEFORMATEXTENSIBLE wfext;
+ if(!FillWaveFormatExtensible(wfext, m_Settings))
+ {
+ return false;
+ }
+ WAVEFORMATEX *pwfx = &wfext.Format;
+ UINT nWaveDev = GetDeviceIndex();
+ nWaveDev = (nWaveDev > 0) ? nWaveDev - 1 : WAVE_MAPPER;
+ m_ThreadWakeupEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if(m_ThreadWakeupEvent == INVALID_HANDLE_VALUE)
+ {
+ InternalClose();
+ return false;
+ }
+ m_Failed = false;
+ m_DriverBugs = 0;
+ m_hWaveOut = NULL;
+ if(waveOutOpen(&m_hWaveOut, nWaveDev, pwfx, (DWORD_PTR)WaveOutCallBack, (DWORD_PTR)this, CALLBACK_FUNCTION | (m_Settings.ExclusiveMode ? WAVE_FORMAT_DIRECT : 0)) != MMSYSERR_NOERROR)
+ {
+ InternalClose();
+ return false;
+ }
+ if(waveOutPause(m_hWaveOut) != MMSYSERR_NOERROR)
+ {
+ InternalClose();
+ return false;
+ }
+ m_nWaveBufferSize = mpt::saturate_round<int32>(m_Settings.UpdateInterval * pwfx->nAvgBytesPerSec);
+ m_nWaveBufferSize = mpt::align_up<uint32>(m_nWaveBufferSize, pwfx->nBlockAlign);
+ m_nWaveBufferSize = std::clamp(m_nWaveBufferSize, static_cast<uint32>(WAVEOUT_MINBUFFERFRAMECOUNT * pwfx->nBlockAlign), static_cast<uint32>(mpt::align_down<uint32>(WAVEOUT_MAXBUFFERSIZE, pwfx->nBlockAlign)));
+ std::size_t numBuffers = mpt::saturate_round<int32>(m_Settings.Latency * pwfx->nAvgBytesPerSec / m_nWaveBufferSize);
+ numBuffers = std::clamp(numBuffers, WAVEOUT_MINBUFFERS, WAVEOUT_MAXBUFFERS);
+ m_nPreparedHeaders = 0;
+ m_WaveBuffers.resize(numBuffers);
+ m_WaveBuffersData.resize(numBuffers);
+ for(std::size_t buf = 0; buf < numBuffers; ++buf)
+ {
+ m_WaveBuffers[buf] = {};
+ m_WaveBuffersData[buf].resize(m_nWaveBufferSize);
+ m_WaveBuffers[buf].dwFlags = 0;
+ m_WaveBuffers[buf].lpData = &m_WaveBuffersData[buf][0];
+ m_WaveBuffers[buf].dwBufferLength = m_nWaveBufferSize;
+ if(waveOutPrepareHeader(m_hWaveOut, &m_WaveBuffers[buf], sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
+ {
+ break;
+ }
+ m_WaveBuffers[buf].dwFlags |= WHDR_DONE;
+ m_nPreparedHeaders++;
+ }
+ if(!m_nPreparedHeaders)
+ {
+ InternalClose();
+ return false;
+ }
+ if(m_Settings.sampleFormat == SampleFormat::Int8)
+ {
+ m_Settings.sampleFormat = SampleFormat::Unsigned8;
+ }
+ m_nBuffersPending = 0;
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ SetWakeupEvent(m_ThreadWakeupEvent);
+ SetWakeupInterval(m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond());
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ return true;
+}
+
+
+bool CWaveDevice::InternalClose()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ waveOutReset(m_hWaveOut);
+ m_JustStarted = false;
+ InterlockedExchange(&m_nBuffersPending, 0);
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ while(m_nPreparedHeaders > 0)
+ {
+ m_nPreparedHeaders--;
+ waveOutUnprepareHeader(m_hWaveOut, &m_WaveBuffers[m_nPreparedHeaders], sizeof(WAVEHDR));
+ }
+ waveOutClose(m_hWaveOut);
+ m_hWaveOut = NULL;
+ }
+#ifdef MPT_BUILD_DEBUG
+ if(m_DriverBugs.load())
+ {
+ SendDeviceMessage(LogError, MPT_USTRING("Errors were detected while playing sound:\n") + GetStatistics().text);
+ }
+#endif
+ m_DriverBugs = 0;
+ m_Failed = false;
+ if(m_ThreadWakeupEvent)
+ {
+ CloseHandle(m_ThreadWakeupEvent);
+ m_ThreadWakeupEvent = NULL;
+ }
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ return true;
+}
+
+
+void CWaveDevice::StartFromSoundThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ m_JustStarted = true;
+ // Actual starting is done in InternalFillAudioBuffer to avoid crackling with tiny buffers.
+ }
+}
+
+
+void CWaveDevice::StopFromSoundThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ CheckResult(waveOutPause(m_hWaveOut));
+ m_JustStarted = false;
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ }
+}
+
+
+bool CWaveDevice::CheckResult(MMRESULT result)
+{
+ if(result == MMSYSERR_NOERROR)
+ {
+ return true;
+ }
+ if(!m_Failed)
+ { // only show the first error
+ m_Failed = true;
+ TCHAR errortext[MAXERRORLENGTH + 1] = {};
+ waveOutGetErrorText(result, errortext, MAXERRORLENGTH);
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("WaveOut error: 0x{}: {}")(mpt::format<mpt::ustring>::hex0<8>(result), mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(errortext)))));
+ }
+ RequestClose();
+ return false;
+}
+
+
+bool CWaveDevice::CheckResult(MMRESULT result, DWORD param)
+{
+ if(result == MMSYSERR_NOERROR)
+ {
+ return true;
+ }
+ if(!m_Failed)
+ { // only show the first error
+ m_Failed = true;
+ TCHAR errortext[MAXERRORLENGTH + 1] = {};
+ waveOutGetErrorText(result, errortext, MAXERRORLENGTH);
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("WaveOut error: 0x{} (param 0x{}): {}")(mpt::format<mpt::ustring>::hex0<8>(result), mpt::format<mpt::ustring>::hex0<8>(param), mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(errortext)))));
+ }
+ RequestClose();
+ return false;
+}
+
+
+void CWaveDevice::InternalFillAudioBuffer()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_hWaveOut)
+ {
+ return;
+ }
+
+ const std::size_t bytesPerFrame = m_Settings.GetBytesPerFrame();
+
+ ULONG oldBuffersPending = InterlockedExchangeAdd(&m_nBuffersPending, 0); // read
+ ULONG nLatency = oldBuffersPending * m_nWaveBufferSize;
+
+ ULONG nBytesWritten = 0;
+ while((oldBuffersPending < m_nPreparedHeaders) && !m_Failed)
+ {
+#if(_WIN32_WINNT >= 0x0600)
+ DWORD oldFlags = InterlockedOr(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), 0);
+#else
+ DWORD oldFlags = _InterlockedOr(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), 0);
+#endif
+ uint32 driverBugs = 0;
+ if(oldFlags & WHDR_INQUEUE)
+ {
+ driverBugs |= DriverBugBufferFillAndHeaderInQueue;
+ }
+ if(!(oldFlags & WHDR_DONE))
+ {
+ driverBugs |= DriverBugBufferFillAndHeaderNotDone;
+ }
+ driverBugs |= m_DriverBugs.fetch_or(driverBugs);
+ if(oldFlags & WHDR_INQUEUE)
+ {
+ if(driverBugs & DriverBugDoneNotificationOutOfOrder)
+ {
+ // Some drivers/setups can return WaveHeader notifications out of
+ // order. WaveHeaders which have not yet been notified to be ready stay
+ // in the INQUEUE and !DONE state internally and cannot be reused yet
+ // even though they causally should be able to. waveOutWrite fails for
+ // them.
+ // In this case we skip filling the buffers until we actually see the
+ // next expected buffer to be ready for refilling.
+ // This problem has been spotted on Wine 1.7.46 (non-official packages)
+ // running on Debian 8 Jessie 32bit. It may also be related to WaveOut
+ // playback being too fast and crackling which had benn reported on
+ // Wine 1.6 + WinePulse on UbuntuStudio 12.04 32bit (this has not been
+ // verified yet because the problem is not always reproducable on the
+ // system in question).
+ return;
+ }
+ }
+ nLatency += m_nWaveBufferSize;
+ CallbackLockedAudioReadPrepare(m_nWaveBufferSize / bytesPerFrame, nLatency / bytesPerFrame);
+ CallbackLockedAudioProcessVoid(m_WaveBuffers[m_nWriteBuffer].lpData, nullptr, m_nWaveBufferSize / bytesPerFrame);
+ nBytesWritten += m_nWaveBufferSize;
+#if(_WIN32_WINNT >= 0x0600)
+ InterlockedAnd(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), ~static_cast<DWORD>(WHDR_INQUEUE | WHDR_DONE));
+#else
+ _InterlockedAnd(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), ~static_cast<DWORD>(WHDR_INQUEUE | WHDR_DONE));
+#endif
+ InterlockedExchange(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwBufferLength), m_nWaveBufferSize);
+ InterlockedIncrement(&m_nBuffersPending);
+ oldBuffersPending++; // increment separately to avoid looping without leaving at all when rendering takes more than 100% CPU
+ CheckResult(waveOutWrite(m_hWaveOut, &m_WaveBuffers[m_nWriteBuffer], sizeof(WAVEHDR)), oldFlags);
+ m_nWriteBuffer++;
+ m_nWriteBuffer %= m_nPreparedHeaders;
+ CallbackLockedAudioProcessDone();
+ }
+
+ if(m_JustStarted && !m_Failed)
+ {
+ // Fill the buffers completely before starting the stream.
+ // This avoids buffer underruns which result in audible crackling with small buffers.
+ m_JustStarted = false;
+ CheckResult(waveOutRestart(m_hWaveOut));
+ }
+}
+
+
+int64 CWaveDevice::InternalGetStreamPositionFrames() const
+{
+ // Apparently, at least with Windows XP, TIME_SAMPLES wraps aroud at 0x7FFFFFF (see
+ // http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2005-02/0070.html
+ // ).
+ // We may also, additionally, default to TIME_BYTES which would wraparound the earliest.
+ // We could thereby try to avoid any potential wraparound inside the driver on older
+ // Windows versions, which would be, once converted into other units, really
+ // difficult to detect or handle.
+ static constexpr UINT timeType = TIME_SAMPLES; // should work for sane systems
+ //static constexpr std::size_t valid_bits = 32; // should work for sane systems
+ //static constexpr UINT timeType = TIME_BYTES; // safest
+ static constexpr std::size_t valid_bits = 27; // safe for WinXP TIME_SAMPLES
+ static constexpr uint32 valid_mask = static_cast<uint32>((uint64(1) << valid_bits) - 1u);
+ static constexpr uint32 valid_watermark = static_cast<uint32>(uint64(1) << (valid_bits - 1u)); // half the valid range in order to be able to catch backwards fluctuations
+
+ MMTIME mmtime = {};
+ mmtime.wType = timeType;
+ if(waveOutGetPosition(m_hWaveOut, &mmtime, sizeof(mmtime)) != MMSYSERR_NOERROR)
+ {
+ return 0;
+ }
+ if(mmtime.wType != TIME_MS && mmtime.wType != TIME_BYTES && mmtime.wType != TIME_SAMPLES)
+ { // unsupported time format
+ return 0;
+ }
+ int64 offset = 0;
+ {
+ // handle wraparound
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ if(!m_PositionLast.wType)
+ {
+ // first call
+ m_PositionWrappedCount = 0;
+ } else if(mmtime.wType != m_PositionLast.wType)
+ {
+ // what? value type changed, do not try handling that for now.
+ m_PositionWrappedCount = 0;
+ } else
+ {
+ DWORD oldval = 0;
+ DWORD curval = 0;
+ switch(mmtime.wType)
+ {
+ case TIME_MS:
+ oldval = m_PositionLast.u.ms;
+ curval = mmtime.u.ms;
+ break;
+ case TIME_BYTES:
+ oldval = m_PositionLast.u.cb;
+ curval = mmtime.u.cb;
+ break;
+ case TIME_SAMPLES:
+ oldval = m_PositionLast.u.sample;
+ curval = mmtime.u.sample;
+ break;
+ }
+ oldval &= valid_mask;
+ curval &= valid_mask;
+ if(((curval - oldval) & valid_mask) >= valid_watermark) // guard against driver problems resulting in time jumping backwards for short periods of time. BEWARE of integer wraparound when refactoring
+ {
+ curval = oldval;
+ }
+ switch(mmtime.wType)
+ {
+ case TIME_MS: mmtime.u.ms = curval; break;
+ case TIME_BYTES: mmtime.u.cb = curval; break;
+ case TIME_SAMPLES: mmtime.u.sample = curval; break;
+ }
+ if((curval ^ oldval) & valid_watermark) // MSB flipped
+ {
+ if(!(curval & valid_watermark)) // actually wrapped
+ {
+ m_PositionWrappedCount += 1;
+ }
+ }
+ }
+ m_PositionLast = mmtime;
+ offset = (static_cast<uint64>(m_PositionWrappedCount) << valid_bits);
+ }
+ int64 result = 0;
+ switch(mmtime.wType)
+ {
+ case TIME_MS: result += (static_cast<int64>(mmtime.u.ms & valid_mask) + offset) * m_Settings.GetBytesPerSecond() / (1000 * m_Settings.GetBytesPerFrame()); break;
+ case TIME_BYTES: result += (static_cast<int64>(mmtime.u.cb & valid_mask) + offset) / m_Settings.GetBytesPerFrame(); break;
+ case TIME_SAMPLES: result += (static_cast<int64>(mmtime.u.sample & valid_mask) + offset); break;
+ }
+ return result;
+}
+
+
+void CWaveDevice::HandleWaveoutDone(WAVEHDR *hdr)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if(_WIN32_WINNT >= 0x0600)
+ DWORD flags = InterlockedOr(interlocked_access(&hdr->dwFlags), 0);
+#else
+ DWORD flags = _InterlockedOr(interlocked_access(&hdr->dwFlags), 0);
+#endif
+ std::size_t hdrIndex = hdr - &(m_WaveBuffers[0]);
+ uint32 driverBugs = 0;
+ if(hdrIndex != m_nDoneBuffer)
+ {
+ driverBugs |= DriverBugDoneNotificationOutOfOrder;
+ }
+ if(!(flags & WHDR_DONE))
+ {
+ driverBugs |= DriverBugDoneNotificationAndHeaderNotDone;
+ }
+ if(flags & WHDR_INQUEUE)
+ {
+ driverBugs |= DriverBugDoneNotificationAndHeaderInQueue;
+ }
+ if(driverBugs)
+ {
+ m_DriverBugs.fetch_or(driverBugs);
+ }
+ m_nDoneBuffer += 1;
+ m_nDoneBuffer %= m_nPreparedHeaders;
+ InterlockedDecrement(&m_nBuffersPending);
+ SetEvent(m_ThreadWakeupEvent);
+}
+
+
+void CWaveDevice::WaveOutCallBack(HWAVEOUT, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR param1, DWORD_PTR /* param2 */)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if((uMsg == WOM_DONE) && (dwUser))
+ {
+ CWaveDevice *that = (CWaveDevice *)dwUser;
+ that->HandleWaveoutDone((WAVEHDR *)param1);
+ }
+}
+
+
+SoundDevice::BufferAttributes CWaveDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_nWaveBufferSize * m_nPreparedHeaders * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.UpdateInterval = m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.NumBuffers = m_nPreparedHeaders;
+ return bufferAttributes;
+}
+
+
+SoundDevice::Statistics CWaveDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = InterlockedExchangeAdd(&m_nBuffersPending, 0) * m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ result.LastUpdateInterval = 1.0 * m_nWaveBufferSize / m_Settings.GetBytesPerSecond();
+ uint32 bugs = m_DriverBugs.load();
+ if(bugs != 0)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("Problematic driver detected! Error flags: {}")(mpt::format<mpt::ustring>::hex0<8>(bugs));
+ } else
+ {
+ result.text = MPT_UFORMAT_MESSAGE("Driver working as expected.")();
+ }
+ return result;
+}
+
+
+std::vector<SoundDevice::Info> CWaveDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ std::vector<SoundDevice::Info> devices;
+ UINT numDevs = waveOutGetNumDevs();
+ for(UINT index = 0; index <= numDevs; ++index)
+ {
+ SoundDevice::Info info;
+ info.type = TypeWAVEOUT;
+ info.internalID = mpt::format<mpt::ustring>::dec(index);
+ info.apiName = MPT_USTRING("MME");
+ info.useNameAsIdentifier = true;
+ WAVEOUTCAPS woc = {};
+ if(waveOutGetDevCaps((index == 0) ? WAVE_MAPPER : (index - 1), &woc, sizeof(woc)) == MMSYSERR_NOERROR)
+ {
+ info.name = mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(woc.szPname)));
+ info.extraData[MPT_USTRING("DriverID")] = MPT_UFORMAT_MESSAGE("{}:{}")(mpt::format<mpt::ustring>::hex0<4>(woc.wMid), mpt::format<mpt::ustring>::hex0<4>(woc.wPid));
+ info.extraData[MPT_USTRING("DriverVersion")] = MPT_UFORMAT_MESSAGE("{}.{}")(mpt::format<mpt::ustring>::dec((static_cast<uint32>(woc.vDriverVersion) >> 24) & 0xff), mpt::format<mpt::ustring>::dec((static_cast<uint32>(woc.vDriverVersion) >> 0) & 0xff));
+ }
+ if(info.name.empty())
+ {
+ if(index == 0)
+ {
+ info.name = MPT_UFORMAT_MESSAGE("Auto (Wave Mapper)")();
+ } else
+ {
+ info.name = MPT_UFORMAT_MESSAGE("Device {}")(index - 1);
+ }
+ }
+ info.default_ = ((index == 0) ? Info::Default::Managed : Info::Default::None);
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Legacy : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Compatible::Yes : Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::OutputOnly,
+ Info::Mixing::Software,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ devices.push_back(info);
+ }
+ return devices;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp
new file mode 100644
index 00000000..bf65dfde
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <MMSystem.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+
+class CWaveDevice : public CSoundDeviceWithThread
+{
+protected:
+ HANDLE m_ThreadWakeupEvent;
+ bool m_Failed;
+ HWAVEOUT m_hWaveOut;
+ uint32 m_nWaveBufferSize;
+ bool m_JustStarted;
+ ULONG m_nPreparedHeaders;
+ ULONG m_nWriteBuffer;
+ ULONG m_nDoneBuffer;
+ mutable LONG m_nBuffersPending;
+ std::vector<WAVEHDR> m_WaveBuffers;
+ std::vector<std::vector<char>> m_WaveBuffersData;
+
+ mutable mpt::mutex m_PositionWraparoundMutex;
+ mutable MMTIME m_PositionLast;
+ mutable std::size_t m_PositionWrappedCount;
+
+ static constexpr uint32 DriverBugDoneNotificationAndHeaderInQueue = (1u << 0u); // 1
+ static constexpr uint32 DriverBugDoneNotificationAndHeaderNotDone = (1u << 1u); // 2
+ static constexpr uint32 DriverBugBufferFillAndHeaderInQueue = (1u << 2u); // 4
+ static constexpr uint32 DriverBugBufferFillAndHeaderNotDone = (1u << 3u); // 8
+ static constexpr uint32 DriverBugDoneNotificationOutOfOrder = (1u << 4u); // 10
+ std::atomic<uint32> m_DriverBugs;
+
+public:
+ CWaveDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CWaveDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ void StartFromSoundThread();
+ void StopFromSoundThread();
+ bool InternalIsOpen() const { return (m_hWaveOut != NULL); }
+ bool InternalHasGetStreamPosition() const { return true; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+
+ SoundDevice::Statistics GetStatistics() const;
+
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+private:
+ bool CheckResult(MMRESULT result);
+ bool CheckResult(MMRESULT result, DWORD param);
+
+ void HandleWaveoutDone(WAVEHDR *hdr);
+
+ int GetDeviceIndex() const;
+
+public:
+ static void CALLBACK WaveOutCallBack(HWAVEOUT, UINT uMsg, DWORD_PTR, DWORD_PTR dw1, DWORD_PTR dw2);
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+#endif // MPT_OS_WINDOWS
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END