diff options
author | Jean-Francois Mauguit <jfmauguit@mac.com> | 2024-09-24 09:03:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-24 09:03:25 -0400 |
commit | bab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/src | |
parent | 4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff) | |
parent | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff) | |
download | winamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz |
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/src')
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(®s, 0, sizeof(__dpmi_regs)); + regs.x.ax = 0x6601; + if (__dpmi_int(0x21, ®s) == 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 |