mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
524 lines
17 KiB
C++
524 lines
17 KiB
C++
/*!
|
|
@file AudioUnitSDK/AUUtility.h
|
|
@copyright © 2000-2021 Apple Inc. All rights reserved.
|
|
*/
|
|
#ifndef AudioUnitSDK_AUUtility_h
|
|
#define AudioUnitSDK_AUUtility_h
|
|
|
|
// OS
|
|
#if defined __has_include && __has_include(<CoreAudioTypes/CoreAudioTypes.h>)
|
|
#include <CoreAudioTypes/CoreAudioTypes.h>
|
|
#else
|
|
#include <CoreAudio/CoreAudioTypes.h>
|
|
#endif
|
|
#include <libkern/OSByteOrder.h>
|
|
#include <mach/mach_time.h>
|
|
#include <os/log.h>
|
|
#include <syslog.h>
|
|
|
|
// std
|
|
#include <bitset>
|
|
#include <cstddef>
|
|
#include <exception>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <vector>
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark General
|
|
|
|
#ifdef AUSDK_NO_DEPRECATIONS
|
|
#define AUSDK_DEPRECATED(msg)
|
|
#else
|
|
#define AUSDK_DEPRECATED(msg) [[deprecated(msg)]] // NOLINT macro
|
|
#endif
|
|
|
|
#ifndef AUSDK_LOG_OBJECT
|
|
#define AUSDK_LOG_OBJECT OS_LOG_DEFAULT // NOLINT macro
|
|
#endif
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Version
|
|
|
|
#define AUSDK_VERSION_MAJOR 1
|
|
#define AUSDK_VERSION_MINOR 1
|
|
#define AUSDK_VERSION_PATCH 0
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark Error-handling macros
|
|
|
|
#ifdef AUSDK_NO_LOGGING
|
|
#define AUSDK_LogError(...) /* NOLINT macro */
|
|
#else
|
|
#define AUSDK_LogError(...) /* NOLINT macro */ \
|
|
if (__builtin_available(macOS 10.11, *)) { \
|
|
os_log_error(AUSDK_LOG_OBJECT, __VA_ARGS__); \
|
|
} else { \
|
|
syslog(LOG_ERR, __VA_ARGS__); \
|
|
}
|
|
#endif
|
|
|
|
#define AUSDK_Catch(result) /* NOLINT(cppcoreguidelines-macro-usage) */ \
|
|
catch (const ausdk::AUException& exc) { (result) = exc.mError; } \
|
|
catch (const std::bad_alloc&) { (result) = kAudio_MemFullError; } \
|
|
catch (const OSStatus& catch_err) { (result) = catch_err; } \
|
|
catch (const std::system_error& exc) { (result) = exc.code().value(); } \
|
|
catch (...) { (result) = -1; }
|
|
|
|
#define AUSDK_Require(expr, error) /* NOLINT(cppcoreguidelines-macro-usage) */ \
|
|
do { \
|
|
if (!(expr)) { \
|
|
return error; \
|
|
} \
|
|
} while (0) /* NOLINT */
|
|
|
|
#define AUSDK_Require_noerr(expr) /* NOLINT(cppcoreguidelines-macro-usage) */ \
|
|
do { \
|
|
if (const auto status_tmp_macro_detail_ = (expr); status_tmp_macro_detail_ != noErr) { \
|
|
return status_tmp_macro_detail_; \
|
|
} \
|
|
} while (0)
|
|
|
|
#pragma mark -
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
namespace ausdk {
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
/// A subclass of std::runtime_error that holds an OSStatus error.
|
|
class AUException : public std::runtime_error {
|
|
public:
|
|
explicit AUException(OSStatus err)
|
|
: std::runtime_error{ std::string("OSStatus ") + std::to_string(err) }, mError{ err }
|
|
{
|
|
}
|
|
|
|
const OSStatus mError;
|
|
};
|
|
|
|
inline void ThrowExceptionIf(bool condition, OSStatus err)
|
|
{
|
|
if (condition) {
|
|
AUSDK_LogError("throwing %d", static_cast<int>(err));
|
|
throw AUException{ err };
|
|
}
|
|
}
|
|
|
|
[[noreturn]] inline void Throw(OSStatus err)
|
|
{
|
|
AUSDK_LogError("throwing %d", static_cast<int>(err));
|
|
throw AUException{ err };
|
|
}
|
|
|
|
inline void ThrowQuietIf(bool condition, OSStatus err)
|
|
{
|
|
if (condition) {
|
|
throw AUException{ err };
|
|
}
|
|
}
|
|
|
|
[[noreturn]] inline void ThrowQuiet(OSStatus err) { throw AUException{ err }; }
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
/// Wrap a std::recursive_mutex in a C++ Mutex (named requirement). Methods are virtual to support
|
|
/// customization.
|
|
class AUMutex {
|
|
public:
|
|
AUMutex() = default;
|
|
virtual ~AUMutex() = default;
|
|
|
|
AUMutex(const AUMutex&) = delete;
|
|
AUMutex(AUMutex&&) = delete;
|
|
AUMutex& operator=(const AUMutex&) = delete;
|
|
AUMutex& operator=(AUMutex&&) = delete;
|
|
|
|
virtual void lock() { mImpl.lock(); }
|
|
virtual void unlock() { mImpl.unlock(); }
|
|
virtual bool try_lock() { return mImpl.try_lock(); }
|
|
|
|
private:
|
|
std::recursive_mutex mImpl;
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
/// Implement optional locking at AudioUnit non-realtime entry points (required only for a small
|
|
/// number of plug-ins which must synchronize against external entry points).
|
|
class AUEntryGuard {
|
|
public:
|
|
explicit AUEntryGuard(AUMutex* maybeMutex) : mMutex{ maybeMutex }
|
|
{
|
|
if (mMutex != nullptr) {
|
|
mMutex->lock();
|
|
}
|
|
}
|
|
|
|
~AUEntryGuard()
|
|
{
|
|
if (mMutex != nullptr) {
|
|
mMutex->unlock();
|
|
}
|
|
}
|
|
|
|
AUEntryGuard(const AUEntryGuard&) = delete;
|
|
AUEntryGuard(AUEntryGuard&&) = delete;
|
|
AUEntryGuard& operator=(const AUEntryGuard&) = delete;
|
|
AUEntryGuard& operator=(AUEntryGuard&&) = delete;
|
|
|
|
private:
|
|
AUMutex* mMutex;
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark ASBD
|
|
|
|
/// Utility functions relating to AudioStreamBasicDescription.
|
|
namespace ASBD {
|
|
|
|
constexpr bool IsInterleaved(const AudioStreamBasicDescription& format) noexcept
|
|
{
|
|
return (format.mFormatFlags & kLinearPCMFormatFlagIsNonInterleaved) == 0u;
|
|
}
|
|
|
|
constexpr UInt32 NumberInterleavedChannels(const AudioStreamBasicDescription& format) noexcept
|
|
{
|
|
return IsInterleaved(format) ? format.mChannelsPerFrame : 1;
|
|
}
|
|
|
|
constexpr UInt32 NumberChannelStreams(const AudioStreamBasicDescription& format) noexcept
|
|
{
|
|
return IsInterleaved(format) ? 1 : format.mChannelsPerFrame;
|
|
}
|
|
|
|
constexpr bool IsCommonFloat32(const AudioStreamBasicDescription& format) noexcept
|
|
{
|
|
return (
|
|
format.mFormatID == kAudioFormatLinearPCM && format.mFramesPerPacket == 1 &&
|
|
format.mBytesPerPacket == format.mBytesPerFrame
|
|
// so far, it's a valid PCM format
|
|
&& (format.mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0 &&
|
|
(format.mChannelsPerFrame == 1 ||
|
|
(format.mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) &&
|
|
((format.mFormatFlags & kAudioFormatFlagIsBigEndian) == kAudioFormatFlagsNativeEndian) &&
|
|
format.mBitsPerChannel == 32 // NOLINT
|
|
&& format.mBytesPerFrame == NumberInterleavedChannels(format) * sizeof(float));
|
|
}
|
|
|
|
constexpr AudioStreamBasicDescription CreateCommonFloat32(
|
|
Float64 sampleRate, UInt32 numChannels, bool interleaved = false) noexcept
|
|
{
|
|
constexpr auto sampleSize = sizeof(Float32);
|
|
|
|
AudioStreamBasicDescription asbd{};
|
|
asbd.mFormatID = kAudioFormatLinearPCM;
|
|
asbd.mFormatFlags = kAudioFormatFlagIsFloat |
|
|
static_cast<AudioFormatFlags>(kAudioFormatFlagsNativeEndian) |
|
|
kAudioFormatFlagIsPacked;
|
|
asbd.mBitsPerChannel = 8 * sampleSize; // NOLINT magic number
|
|
asbd.mChannelsPerFrame = numChannels;
|
|
asbd.mFramesPerPacket = 1;
|
|
asbd.mSampleRate = sampleRate;
|
|
if (interleaved) {
|
|
asbd.mBytesPerPacket = asbd.mBytesPerFrame = numChannels * sampleSize;
|
|
} else {
|
|
asbd.mBytesPerPacket = asbd.mBytesPerFrame = sampleSize;
|
|
asbd.mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
|
|
}
|
|
return asbd;
|
|
}
|
|
|
|
constexpr bool MinimalSafetyCheck(const AudioStreamBasicDescription& x) noexcept
|
|
{
|
|
// This function returns false if there are sufficiently unreasonable values in any field.
|
|
// It is very conservative so even some very unlikely values will pass.
|
|
// This is just meant to catch the case where the data from a file is corrupted.
|
|
|
|
return (x.mSampleRate >= 0.) && (x.mSampleRate < 3e6) // NOLINT SACD sample rate is 2.8224 MHz
|
|
&& (x.mBytesPerPacket < 1000000) // NOLINT
|
|
&& (x.mFramesPerPacket < 1000000) // NOLINT
|
|
&& (x.mBytesPerFrame < 1000000) // NOLINT
|
|
&& (x.mChannelsPerFrame > 0) && (x.mChannelsPerFrame <= 1024) // NOLINT
|
|
&& (x.mBitsPerChannel <= 1024) // NOLINT
|
|
&& (x.mFormatID != 0) &&
|
|
!(x.mFormatID == kAudioFormatLinearPCM &&
|
|
(x.mFramesPerPacket != 1 || x.mBytesPerPacket != x.mBytesPerFrame));
|
|
}
|
|
|
|
inline bool IsEqual(
|
|
const AudioStreamBasicDescription& lhs, const AudioStreamBasicDescription& rhs) noexcept
|
|
{
|
|
return memcmp(&lhs, &rhs, sizeof(AudioStreamBasicDescription)) == 0;
|
|
}
|
|
|
|
} // namespace ASBD
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark ACL
|
|
|
|
/// Utility functions relating to AudioChannelLayout.
|
|
namespace ACL {
|
|
|
|
constexpr bool operator==(const AudioChannelLayout& lhs, const AudioChannelLayout& rhs) noexcept
|
|
{
|
|
if (lhs.mChannelLayoutTag != rhs.mChannelLayoutTag) {
|
|
return false;
|
|
}
|
|
if (lhs.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
|
return lhs.mChannelBitmap == rhs.mChannelBitmap;
|
|
}
|
|
if (lhs.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
|
|
if (lhs.mNumberChannelDescriptions != rhs.mNumberChannelDescriptions) {
|
|
return false;
|
|
}
|
|
for (auto i = 0u; i < lhs.mNumberChannelDescriptions; ++i) {
|
|
const auto& lhdesc = lhs.mChannelDescriptions[i]; // NOLINT array subscript
|
|
const auto& rhdesc = rhs.mChannelDescriptions[i]; // NOLINT array subscript
|
|
|
|
if (lhdesc.mChannelLabel != rhdesc.mChannelLabel) {
|
|
return false;
|
|
}
|
|
if (lhdesc.mChannelLabel == kAudioChannelLabel_UseCoordinates) {
|
|
if (memcmp(&lhdesc, &rhdesc, sizeof(AudioChannelDescription)) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace ACL
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
/// Utility wrapper for the variably-sized AudioChannelLayout struct.
|
|
class AUChannelLayout {
|
|
public:
|
|
AUChannelLayout() : AUChannelLayout(0, kAudioChannelLayoutTag_UseChannelDescriptions, 0) {}
|
|
|
|
/// Can construct from a layout tag.
|
|
explicit AUChannelLayout(AudioChannelLayoutTag inTag) : AUChannelLayout(0, inTag, 0) {}
|
|
|
|
AUChannelLayout(uint32_t inNumberChannelDescriptions, AudioChannelLayoutTag inChannelLayoutTag,
|
|
AudioChannelBitmap inChannelBitMap)
|
|
: mStorage(
|
|
kHeaderSize + (inNumberChannelDescriptions * sizeof(AudioChannelDescription)), {})
|
|
{
|
|
auto* const acl = reinterpret_cast<AudioChannelLayout*>(mStorage.data()); // NOLINT
|
|
|
|
acl->mChannelLayoutTag = inChannelLayoutTag;
|
|
acl->mChannelBitmap = inChannelBitMap;
|
|
acl->mNumberChannelDescriptions = inNumberChannelDescriptions;
|
|
}
|
|
|
|
/// Implicit conversion from AudioChannelLayout& is allowed.
|
|
AUChannelLayout(const AudioChannelLayout& acl) // NOLINT
|
|
: mStorage(kHeaderSize + (acl.mNumberChannelDescriptions * sizeof(AudioChannelDescription)))
|
|
{
|
|
memcpy(mStorage.data(), &acl, mStorage.size());
|
|
}
|
|
|
|
bool operator==(const AUChannelLayout& other) const noexcept
|
|
{
|
|
return ACL::operator==(Layout(), other.Layout());
|
|
}
|
|
|
|
bool operator!=(const AUChannelLayout& y) const noexcept { return !(*this == y); }
|
|
|
|
[[nodiscard]] bool IsValid() const noexcept { return NumberChannels() > 0; }
|
|
|
|
[[nodiscard]] const AudioChannelLayout& Layout() const noexcept { return *LayoutPtr(); }
|
|
|
|
[[nodiscard]] const AudioChannelLayout* LayoutPtr() const noexcept
|
|
{
|
|
return reinterpret_cast<const AudioChannelLayout*>(mStorage.data()); // NOLINT
|
|
}
|
|
|
|
/// After default construction, this method will return
|
|
/// kAudioChannelLayoutTag_UseChannelDescriptions with 0 channel descriptions.
|
|
[[nodiscard]] AudioChannelLayoutTag Tag() const noexcept { return Layout().mChannelLayoutTag; }
|
|
|
|
[[nodiscard]] uint32_t NumberChannels() const noexcept { return NumberChannels(*LayoutPtr()); }
|
|
|
|
[[nodiscard]] uint32_t Size() const noexcept { return static_cast<uint32_t>(mStorage.size()); }
|
|
|
|
static uint32_t NumberChannels(const AudioChannelLayout& inLayout) noexcept
|
|
{
|
|
if (inLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
|
|
return inLayout.mNumberChannelDescriptions;
|
|
}
|
|
if (inLayout.mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
|
return static_cast<uint32_t>(
|
|
std::bitset<32>(inLayout.mChannelBitmap).count()); // NOLINT magic #
|
|
}
|
|
return AudioChannelLayoutTag_GetNumberOfChannels(inLayout.mChannelLayoutTag);
|
|
}
|
|
|
|
private:
|
|
constexpr static size_t kHeaderSize = offsetof(AudioChannelLayout, mChannelDescriptions[0]);
|
|
std::vector<std::byte> mStorage;
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark AudioBufferList
|
|
|
|
/// Utility functions relating to AudioBufferList.
|
|
namespace ABL {
|
|
|
|
// if the return result is odd, there was a null buffer.
|
|
inline uint32_t IsBogusAudioBufferList(const AudioBufferList& abl)
|
|
{
|
|
const AudioBuffer *buf = abl.mBuffers, *const bufEnd = buf + abl.mNumberBuffers;
|
|
uint32_t sum =
|
|
0; // defeat attempts by the compiler to optimize away the code that touches the buffers
|
|
uint32_t anyNull = 0;
|
|
for (; buf < bufEnd; ++buf) {
|
|
const uint32_t* const p = static_cast<const uint32_t*>(buf->mData);
|
|
if (p == nullptr) {
|
|
anyNull = 1;
|
|
continue;
|
|
}
|
|
const auto dataSize = buf->mDataByteSize;
|
|
if (dataSize >= sizeof(*p)) {
|
|
const size_t frameCount = dataSize / sizeof(*p);
|
|
sum += p[0];
|
|
sum += p[frameCount - 1];
|
|
}
|
|
}
|
|
return anyNull | (sum & ~1u);
|
|
}
|
|
|
|
} // namespace ABL
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
#pragma mark -
|
|
#pragma mark HostTime
|
|
|
|
/// Utility functions relating to Mach absolute time.
|
|
namespace HostTime {
|
|
|
|
/// Returns the current host time
|
|
inline uint64_t Current() { return mach_absolute_time(); }
|
|
|
|
/// Returns the frequency of the host timebase, in ticks per second.
|
|
inline double Frequency()
|
|
{
|
|
struct mach_timebase_info timeBaseInfo {
|
|
}; // NOLINT
|
|
mach_timebase_info(&timeBaseInfo);
|
|
// the frequency of that clock is: (sToNanosDenominator / sToNanosNumerator) * 10^9
|
|
return static_cast<double>(timeBaseInfo.denom) / static_cast<double>(timeBaseInfo.numer) *
|
|
1.0e9; // NOLINT
|
|
}
|
|
|
|
} // namespace HostTime
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
/// Basic RAII wrapper for CoreFoundation types
|
|
template <typename T>
|
|
class Owned {
|
|
explicit Owned(T obj, bool fromget) noexcept : mImpl{ obj }
|
|
{
|
|
if (fromget) {
|
|
retainRef();
|
|
}
|
|
}
|
|
|
|
public:
|
|
static Owned from_get(T obj) noexcept { return Owned{ obj, true }; }
|
|
|
|
static Owned from_create(T obj) noexcept { return Owned{ obj, false }; }
|
|
static Owned from_copy(T obj) noexcept { return Owned{ obj, false }; }
|
|
|
|
Owned() noexcept = default;
|
|
~Owned() noexcept { releaseRef(); }
|
|
|
|
Owned(const Owned& other) noexcept : mImpl{ other.mImpl } { retainRef(); }
|
|
|
|
Owned(Owned&& other) noexcept : mImpl{ std::exchange(other.mImpl, nullptr) } {}
|
|
|
|
Owned& operator=(const Owned& other) noexcept
|
|
{
|
|
if (this != &other) {
|
|
releaseRef();
|
|
mImpl = other.mImpl;
|
|
retainRef();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Owned& operator=(Owned&& other) noexcept
|
|
{
|
|
std::swap(mImpl, other.mImpl);
|
|
return *this;
|
|
}
|
|
|
|
T operator*() const noexcept { return get(); }
|
|
T get() const noexcept { return mImpl; }
|
|
|
|
/// As with `unique_ptr<T>::release()`, releases ownership of the reference to the caller (not
|
|
/// to be confused with decrementing the reference count as with `CFRelease()`).
|
|
T release() noexcept { return std::exchange(mImpl, nullptr); }
|
|
|
|
/// This is a from_get operation.
|
|
Owned& operator=(T cfobj) noexcept
|
|
{
|
|
if (mImpl != cfobj) {
|
|
releaseRef();
|
|
mImpl = cfobj;
|
|
retainRef();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
void retainRef() noexcept
|
|
{
|
|
if (mImpl != nullptr) {
|
|
CFRetain(mImpl);
|
|
}
|
|
}
|
|
|
|
void releaseRef() noexcept
|
|
{
|
|
if (mImpl != nullptr) {
|
|
CFRelease(mImpl);
|
|
}
|
|
}
|
|
|
|
T mImpl{ nullptr };
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
constexpr bool safe_isprint(char in_char) noexcept { return (in_char >= ' ') && (in_char <= '~'); }
|
|
|
|
inline std::string make_string_from_4cc(uint32_t in_4cc) noexcept
|
|
{
|
|
#if !TARGET_RT_BIG_ENDIAN
|
|
in_4cc = OSSwapInt32(in_4cc); // NOLINT
|
|
#endif
|
|
|
|
char* const string = reinterpret_cast<char*>(&in_4cc); // NOLINT
|
|
for (size_t i = 0; i < sizeof(in_4cc); ++i) {
|
|
if (!safe_isprint(string[i])) { // NOLINT
|
|
string[i] = '.'; // NOLINT
|
|
}
|
|
}
|
|
return std::string{ string, sizeof(in_4cc) };
|
|
}
|
|
|
|
} // namespace ausdk
|
|
|
|
#endif // AudioUnitSDK_AUUtility_h
|