AUI Framework  master
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 <AUI/Audio/ASampleFormat.h>
   17#include <AUI/Audio/ISoundInputStream.h>
   18#include <AUI/Audio/Platform/RequestedAudioFormat.h>
   19#include <AUI/Audio/ASampleRateConverter.h>
   20#include <AUI/Audio/VolumeLevel.h>
   21
   22
   23namespace aui::audio::impl {
   24    template<int power, typename T>
   25    constexpr T multByPowerOf2(T value) {
   26        if constexpr (power > 0) {
   27            return value << power;
   28        } else {
   29            return value >> -power;
   30        }
   31    }
   32
   33    template<int shift, typename T>
   34    constexpr T logicalShift(T value) {
   35        if constexpr (shift > 0) {
   36            return static_cast<T>(static_cast<std::make_unsigned_t<T>>(value) << shift);
   37        } else {
   38            return static_cast<T>(static_cast<std::make_unsigned_t<T>>(value) >> -shift);
   39        }
   40    }
   41
   42    template<ASampleFormat f>
   43    struct sample_type;
   44
   45    template<>
   47        using type = int16_t;
   48        constexpr static int size_bits = 16;
   49    };
   50
   51    template<>
   53        using type = int32_t;
   54        constexpr static int size_bits = 24;
   55    };
   56
   57    template<>
   59        using type = int32_t;
   60        constexpr static int size_bits = 32;
   61    };
   62
   63    template<ASampleFormat f>
   64    constexpr int size_bytes() {
   65        return sample_type<f>::size_bits / 8;
   66    }
   67
   68    template<ASampleFormat f>
   69    using sample_type_t = typename sample_type<f>::type;
   70
   71    template<ASampleFormat f>
   72    constexpr int type_size() {
   73        return sizeof(sample_type_t<f>);
   74    }
   75
   76    template<ASampleFormat f>
   77    constexpr int type_size_bits() {
   78        return type_size<f>() * 8;
   79    }
   80
   81    template<ASampleFormat to, ASampleFormat from>
   82    constexpr sample_type_t<to> sample_cast(sample_type_t<from> sample) {
   83        if constexpr (type_size<to>() > type_size<from>()) {
   84            return logicalShift<type_size_bits<to>() - type_size_bits<from>()>(
   85                    static_cast<sample_type_t<to>>(sample));
   86        }
   87        return logicalShift<type_size_bits<to>() - type_size_bits<from>()>(sample);
   88    }
   89
   90#pragma pack(push, 1)
   91    template<ASampleFormat f>
   92    struct packed_accessor {
   93        sample_type_t<f> value: sample_type<f>::size_bits;
   94        unsigned _pad: (32 - sample_type<f>::size_bits);
   95    };
   96#pragma pack(pop)
   97
   98    template<>
  100        sample_type_t<ASampleFormat::I32> value: sample_type<ASampleFormat::I32>::size_bits;
  101    };
  102
  103    template<ASampleFormat f>
  104    sample_type_t<f> extractSample(std::byte *src) {
  105        return logicalShift<type_size_bits<f>() - sample_type<f>::size_bits>(
  106                reinterpret_cast<packed_accessor<f> *>(src)->value);
  107    }
  108
  109    template<ASampleFormat f>
  110    void pushSample(sample_type_t<f> sample, std::byte *dst) {
  111        reinterpret_cast<packed_accessor<f> *>(dst)->value = logicalShift<
  112                sample_type<f>::size_bits - type_size_bits<f>()>(sample);
  113    }
  114}
  115
  116
  117template<ASampleFormat sample_in, AChannelFormat channels_in,
  118        ASampleFormat sample_out = aui::audio::platform::requested_sample_format,
  119        AChannelFormat channels_out = aui::audio::platform::requested_channels_format>
  120class ACompileTimeSoundResampler {
  121public:
  122    explicit ACompileTimeSoundResampler(_<ISoundInputStream> source) noexcept :
  123            mInputSampleRate(source->info().sampleRate), mSource(source),
  124            mConverter(aui::audio::platform::requested_sample_rate, std::move(source)) {
  125    }
  126
  127    void setVolume(aui::audio::VolumeLevel volume) {
  128        mVolumeLevel = volume;
  129    }
  130
  131    template<ASampleFormat format>
  132    inline void commitSample(aui::audio::impl::sample_type_t<format> sample) {
  133        AUI_ASSERTX(mDestinationBufferIt <= mDestinationBufferEnd, "buffer overrun");
  134        //use int64_t for overflow preventing
  135        int64_t newSample = int64_t(aui::audio::impl::sample_cast<sample_out, format>(sample));
  136        if (mVolumeLevel) {
  137            newSample = (*mVolumeLevel * newSample) / aui::audio::VolumeLevel::MAX;
  138        }
  139        newSample += int64_t(aui::audio::impl::extractSample<sample_out>(mDestinationBufferIt));
  140        newSample = glm::clamp(newSample, MIN_VAL, MAX_VAL);
  141        aui::audio::impl::pushSample<sample_out>(newSample, mDestinationBufferIt);
  142        mDestinationBufferIt += aui::audio::impl::size_bytes<sample_out>();
  143    }
  144
  145    [[nodiscard]]
  146    size_t remainingSampleCount() const {
  147        return (mDestinationBufferEnd - mDestinationBufferIt) / aui::audio::impl::size_bytes<sample_out>();
  148    }
  149
  150    constexpr size_t canReadSamples(size_t canPushSamples) {
  151        return (canPushSamples / size_t(channels_out)) * size_t(channels_in);
  152    }
  153
  154    inline void commitAllSamples() {
  155        std::byte buf[BUFFER_SIZE];
  156        while (auto remSampleCount = remainingSampleCount()) {
  157            size_t samplesToRead = canReadSamples(remainingSampleCount());
  158            size_t r;
  159            if (mInputSampleRate == aui::audio::platform::requested_sample_rate) {
  160                std::span dst(buf, std::min(aui::audio::impl::size_bytes<sample_in>() * samplesToRead, sizeof(buf)));
  161                r = mSource->read(dst);
  162                iterateOverBuffer<sample_in>(buf, buf + r);
  163            }
  164            else {
  165                static constexpr auto conv_sample_format = ASampleRateConverter::outputSampleFormat();
  166                std::span dst(buf, std::min(aui::audio::impl::size_bytes<conv_sample_format>() * samplesToRead, sizeof(buf)));
  167                r = mConverter.convert(dst);
  168                iterateOverBuffer<conv_sample_format>(buf, buf + r);
  169            }
  170
  171            if (r == 0) {
  172                break;
  173            }
  174        }
  175    }
  176
  177    template <ASampleFormat format>
  178    void iterateOverBuffer(std::byte* begin, std::byte* end) {
  179        static constexpr size_t stepSize = static_cast<size_t>(channels_in) * aui::audio::impl::size_bytes<format>();
  180        for (std::byte *it = begin; it + stepSize <= end; it += stepSize) {
  181            if constexpr (channels_in == channels_out) {
  182                for (size_t i = 0; i < static_cast<size_t>(channels_in); i++) {
  183                    commitSample<format>(aui::audio::impl::extractSample<format>(
  184                            it + i * aui::audio::impl::size_bytes<format>()));
  185                }
  186            } else {
  187                if constexpr (channels_in == AChannelFormat::MONO) {
  188                    //mono to stereo resampling
  189                    auto sample = aui::audio::impl::extractSample<format>(it);
  190                    commitSample<format>(sample);
  191                    commitSample<format>(sample);
  192                } else {
  193                    //TODO implement stereo to mono resampling
  194                }
  195            }
  196        }
  197    }
  198
  199    void setDestination(std::span<std::byte> dst) {
  200        mDestinationBufferBegin = dst.data();
  201        mDestinationBufferEnd = dst.data() + dst.size();
  202        mDestinationBufferIt = mDestinationBufferBegin;
  203    }
  204
  205    [[nodiscard]]
  206    bool isFull() const {
  207        return mDestinationBufferBegin != mDestinationBufferIt;
  208    }
  209
  210    [[nodiscard]]
  211    size_t writtenSize() const {
  212        return mDestinationBufferIt - mDestinationBufferBegin;
  213    }
  214
  215    using input_t = aui::audio::impl::sample_type<sample_in>;
  216    using output_t = aui::audio::impl::sample_type<sample_out>;
  217
  218    static constexpr int64_t MIN_VAL = std::numeric_limits<typename output_t::type>::min();
  219    static constexpr int64_t MAX_VAL = std::numeric_limits<typename output_t::type>::max();
  220    static constexpr size_t BUFFER_SIZE = 0x3000;
  221
  222private:
  223    std::byte *mDestinationBufferBegin = nullptr;
  224    std::byte *mDestinationBufferEnd = nullptr;
  225    std::byte *mDestinationBufferIt = nullptr;
  226    std::uint32_t mInputSampleRate;
  227    _<ISoundInputStream> mSource;
  228    ASampleRateConverter mConverter;
  229    AOptional<aui::audio::VolumeLevel> mVolumeLevel;
  230
  231};
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
@ 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:92
Definition ACompileTimeSoundResampler.h:43