AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
ACompileTimeSoundResampler.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14#include <cstdint>
   15#include <span>
   16#include <utility>
   17#include <AUI/Audio/ASampleFormat.h>
   18#include <AUI/Audio/ISoundInputStream.h>
   19#include <AUI/Audio/Platform/RequestedAudioFormat.h>
   20#include <AUI/Audio/ASampleRateConverter.h>
   21#include <AUI/Audio/VolumeLevel.h>
   22
   23namespace aui::audio::impl {
   24template <int power, typename T>
   25constexpr T multByPowerOf2(T value) {
   26    if constexpr (power > 0) {
   27        return value << power;
   28    } else {
   29        return value >> -power;
   30    }
   31}
   32
   33template <int shift, typename T>
   34constexpr T logicalShift(T value) {
   35    if constexpr (std::is_floating_point_v<T>) {
   36        return value;
   37    } else {
   38        if constexpr (shift > 0) {
   39            return static_cast<T>(static_cast<std::make_unsigned_t<T>>(value) << shift);
   40        } else {
   41            return static_cast<T>(static_cast<std::make_unsigned_t<T>>(value) >> -shift);
   42        }
   43    }
   44}
   45
   46template <ASampleFormat f>
   48
   49template <>
   51    using type = int16_t;
   52    constexpr static int size_bits = 16;
   53};
   54
   55template <>
   57    using type = int32_t;
   58    constexpr static int size_bits = 24;
   59};
   60
   61template <>
   63    using type = int32_t;
   64    constexpr static int size_bits = 32;
   65};
   66
   67template <>
   69    using type = float;
   70    constexpr static int size_bits = 32;
   71};
   72
   73template <ASampleFormat f>
   74constexpr int size_bytes() {
   75    return sample_type<f>::size_bits / 8;
   76}
   77
   78template <ASampleFormat f>
   79using sample_type_t = typename sample_type<f>::type;
   80
   81template <ASampleFormat f>
   82constexpr int type_size() {
   83    return sizeof(sample_type_t<f>);
   84}
   85
   86template <ASampleFormat f>
   87constexpr int type_size_bits() {
   88    return type_size<f>() * 8;
   89}
   90
   91template <ASampleFormat to, ASampleFormat from>
   92constexpr sample_type_t<to> sample_cast(sample_type_t<from> sample) {
   93    using FromT = sample_type_t<from>;
   94    if constexpr (std::is_floating_point_v<FromT>) {
   95        return glm::mix(
   96            FromT(std::numeric_limits<sample_type_t<to>>::min()), FromT(std::numeric_limits<sample_type_t<to>>::max()),
   97            (sample + 1) / 2.f);
   98    } else {
   99        if constexpr (type_size<to>() > type_size<from>()) {
  100            return logicalShift<type_size_bits<to>() - type_size_bits<from>()>(static_cast<sample_type_t<to>>(sample));
  101        }
  102        return logicalShift<type_size_bits<to>() - type_size_bits<from>()>(sample);
  103    }
  104}
  105
  106#pragma pack(push, 1)
  107template <ASampleFormat f>
  109    sample_type_t<f> value : sample_type<f>::size_bits;
  110    unsigned _pad : (32 - sample_type<f>::size_bits);
  111};
  112#pragma pack(pop)
  113
  114template <ASampleFormat f>
  115    requires requires { sample_type<f>::size_bits >= 32; }
  117    sample_type_t<f> value;
  118};
  119
  120template <ASampleFormat f>
  121sample_type_t<f> extractSample(std::byte* src) {
  122    return logicalShift<type_size_bits<f>() - sample_type<f>::size_bits>(
  123        reinterpret_cast<packed_accessor<f>*>(src)->value);
  124}
  125
  126template <ASampleFormat f>
  127void pushSample(sample_type_t<f> sample, std::byte* dst) {
  128    reinterpret_cast<packed_accessor<f>*>(dst)->value =
  129        logicalShift<sample_type<f>::size_bits - type_size_bits<f>()>(sample);
  130}
  131}   // namespace aui::audio::impl
  132
  134    std::byte* destinationBufferBegin = nullptr;
  135    std::byte* destinationBufferEnd = nullptr;
  136    std::byte* destinationBufferIt = nullptr;
  137    AOptional<aui::audio::VolumeLevel> volumeLevel;
  138
  139    [[nodiscard]]
  140    bool isFull() const {
  141        return destinationBufferBegin != destinationBufferIt;
  142    }
  143
  144    [[nodiscard]]
  145    size_t writtenSize() const {
  146        return destinationBufferIt - destinationBufferBegin;
  147    }
  148
  149    template <ASampleFormat sample_out>
  150    [[nodiscard]]
  151    size_t remainingSampleCount() const {
  152        return (destinationBufferEnd - destinationBufferIt) / aui::audio::impl::size_bytes<sample_out>();
  153    }
  154};
  155
  156template <
  157    ASampleFormat sample_in, AChannelFormat channels_in,
  158    ASampleFormat sample_out = aui::audio::platform::requested_sample_format,
  159    AChannelFormat channels_out = aui::audio::platform::requested_channels_format>
  160class ACompileTimeSoundResampler {
  161public:
  162    explicit ACompileTimeSoundResampler(_<ISoundInputStream> source) noexcept
  163      : mInputSampleRate(source->info().sampleRate)
  164      , mConverter(aui::audio::platform::requested_sample_rate, std::move(source)) {}
  165
  166    template <ASampleFormat format>
  167    inline void commitSample(Transaction& transaction, aui::audio::impl::sample_type_t<format> sample) {
  168        AUI_ASSERTX(transaction.destinationBufferIt <= transaction.destinationBufferEnd, "buffer overrun");
  169        // use int64_t for overflow preventing
  170        int64_t newSample = int64_t(aui::audio::impl::sample_cast<sample_out, format>(sample));
  171        if (transaction.volumeLevel) {
  172            newSample = (*transaction.volumeLevel * newSample) / aui::audio::VolumeLevel::MAX;
  173        }
  174        newSample += int64_t(aui::audio::impl::extractSample<sample_out>(transaction.destinationBufferIt));
  175        newSample = glm::clamp(newSample, MIN_VAL, MAX_VAL);
  176        aui::audio::impl::pushSample<sample_out>(newSample, transaction.destinationBufferIt);
  177        transaction.destinationBufferIt += aui::audio::impl::size_bytes<sample_out>();
  178    }
  179
  180    constexpr size_t canReadSamples(size_t canPushSamples) {
  181        return (canPushSamples / size_t(channels_out)) * size_t(channels_in);
  182    }
  183
  184    inline void commitAllSamples(Transaction& transaction) {
  185        int deadbeef1 = 0xdeadbeef;
  186        std::byte buf[BUFFER_SIZE];
  187        int deadbeef2 = 0xdeadbeef;
  188        while (auto remSampleCount = transaction.remainingSampleCount<sample_out>()) {
  189            size_t samplesToRead = canReadSamples(transaction.remainingSampleCount<sample_out>());
  190            size_t r;
  191            if (mInputSampleRate == aui::audio::platform::requested_sample_rate) {
  192                std::span dst(buf, std::min(aui::audio::impl::size_bytes<sample_in>() * samplesToRead, sizeof(buf)));
  193                r = mConverter.source()->read(dst);
  194                AUI_ASSERTX(r <= dst.size(), "result larger than supplied buffer?");
  195                AUI_ASSERTX(deadbeef1 == 0xdeadbeef, "stack corruption");
  196                AUI_ASSERTX(deadbeef2 == 0xdeadbeef, "stack corruption");
  197                iterateOverBuffer<sample_in>(transaction, buf, buf + r);
  198            } else {
  199                static constexpr auto conv_sample_format = ASampleRateConverter::outputSampleFormat();
  200                std::span dst(
  201                    buf, std::min(aui::audio::impl::size_bytes<conv_sample_format>() * samplesToRead, sizeof(buf)));
  202                r = mConverter.convert(dst);
  203                AUI_ASSERTX(r <= dst.size(), "result larger than supplied buffer?");
  204                iterateOverBuffer<conv_sample_format>(transaction, buf, buf + r);
  205                AUI_ASSERTX(deadbeef1 == 0xdeadbeef, "stack corruption");
  206                AUI_ASSERTX(deadbeef2 == 0xdeadbeef, "stack corruption");
  207            }
  208
  209            if (r == 0) {
  210                break;
  211            }
  212        }
  213    }
  214
  215    template <ASampleFormat format>
  216    void iterateOverBuffer(Transaction& transaction, std::byte* begin, std::byte* end) {
  217        static constexpr size_t stepSize = static_cast<size_t>(channels_in) * aui::audio::impl::size_bytes<format>();
  218        for (std::byte* it = begin; it + stepSize <= end; it += stepSize) {
  219            if constexpr (channels_in == channels_out) {
  220                for (size_t i = 0; i < static_cast<size_t>(channels_in); i++) {
  221                    commitSample<format>(
  222                        transaction,
  223                        aui::audio::impl::extractSample<format>(it + i * aui::audio::impl::size_bytes<format>()));
  224                }
  225            } else {
  226                if constexpr (channels_in == AChannelFormat::MONO) {
  227                    // mono to stereo resampling
  228                    auto sample = aui::audio::impl::extractSample<format>(it);
  229                    commitSample<format>(transaction, sample);
  230                    commitSample<format>(transaction, sample);
  231                } else {
  232                    // TODO implement stereo to mono resampling
  233                }
  234            }
  235        }
  236    }
  237
  238    using input_t = aui::audio::impl::sample_type<sample_in>;
  239    using output_t = aui::audio::impl::sample_type<sample_out>;
  240
  241    static constexpr int64_t MIN_VAL = std::numeric_limits<typename output_t::type>::min();
  242    static constexpr int64_t MAX_VAL = std::numeric_limits<typename output_t::type>::max();
  243    static constexpr size_t BUFFER_SIZE = 0x3000;
  244
  245private:
  246    std::uint32_t mInputSampleRate;
  247    ASampleRateConverter mConverter;
  248};
Utility wrapper implementing the stack-allocated (fast) optional idiom.
Definition AOptional.h:33
Definition ASampleRateConverter.h:10
static constexpr ASampleFormat outputSampleFormat()
Definition ASampleRateConverter.h:24
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:179
ASampleFormat
Sample formats supported for mixing.
Definition ASampleFormat.h:12
@ F32
32-bit IEEE floating point.
Definition ASampleFormat.h:31
@ I16
Signed 16-bit integer.
Definition ASampleFormat.h:16
@ I24
Signed 24-bit integer.
Definition ASampleFormat.h:21
@ I32
Signed 32-bit integer.
Definition ASampleFormat.h:26
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition Assert.h:74
Definition ACompileTimeSoundResampler.h:133
Definition ACompileTimeSoundResampler.h:108
Definition ACompileTimeSoundResampler.h:47