Brenta Engine 1.2
Loading...
Searching...
No Matches
miniaudio.hpp
1// SPDX-License-Identifier: MIT
2// Author: Giovanni Santini
3// Mail: giovanni.santini@proton.me
4// Github: @San7o
5
6#pragma once
7
8#include <brenta/sound.hpp>
9
10#include <miniaudio/miniaudio.h>
11
12#include <tenno/array.hpp>
13#include <deque>
14
15#ifndef BRENTA_NUM_STREAMS
16 #define BRENTA_NUM_STREAMS 64
17#endif
18
19namespace brenta
20{
21
22//
23// SoundManager Driver using Miniaudio
24// -----------------------------------
25//
26// The implementation for this driver is designed to be O(#sounds)
27// when allocating new sounds, and O(1) for sound operations like
28// play and stop.
29//
30// We keep a list of sound and stream slots, and whether they are
31// being used or not. When we deallocate a sound or stream, we mark
32// that as unused so that it may be used for future allocations. This
33// reduces the number of heap memory allocations, but we iterate
34// over the slots. The time complexity could be improved by keeping
35// a free list, but I that also adds additional memory allocations
36// and I don't think it is worth it.
37//
38// To invalidate indexed to deallocated / reallocated slots we keep
39// track of which sound a stream is bound to. We identify a sound
40// by its index in the sounds vector, and a generation number. When
41// a new sound is loaded, the generation of its slot is increased,
42// so we have an unique way to identify that sound.
43//
45{
46public:
47
48 using MiniaudioBuffer = ma_audio_buffer;
49 using MiniaudioStream = ma_sound;
50
52 {
53 MiniaudioBuffer buffer;
54 bool in_use = false;
55 int generation;
56
57 SoundAssetSlot() = default;
58 SoundAssetSlot(const SoundAssetSlot&) = delete;
59 SoundAssetSlot &operator=(const SoundAssetSlot&) = delete;
60
61 SoundAssetSlot(SoundAssetSlot&&) = default;
62 SoundAssetSlot &operator=(SoundAssetSlot&&) = default;
63
64 };
65
66 // An instance of a sound asset
68 {
69 MiniaudioStream handle;
70 MiniaudioBuffer local_buffer;
71 bool in_use = false;
72 // We keep a sound_index + generation number to make sure that
73 // this stream is referring to a particular valid sound
74 int generation;
75 int sound_index;
76
77 StreamSlot() = default;
78 StreamSlot(const StreamSlot&) = delete;
79 StreamSlot &operator=(const StreamSlot&) = delete;
80
81 StreamSlot(StreamSlot&&) = default;
82 StreamSlot &operator=(StreamSlot&&) = default;
83
84 };
85
86 MiniaudioDriver() = default;
87 MiniaudioDriver(const MiniaudioDriver&) = delete;
88 MiniaudioDriver &operator=(const MiniaudioDriver&) = delete;
89 ~MiniaudioDriver() = default;
90
91 std::expected<void, std::string> initialize() override;
92 std::expected<void, std::string> terminate() override;
93
94 std::optional<Stream> request_stream(const SoundAsset& sound) override;
95 void release_stream(Stream stream) override;
96
97 // Loading sounds is O(#sounds)
98 std::optional<SoundAsset> load(const std::filesystem::path& path) override;
99 void unload(const SoundAsset& sound) override;
100
101 // play / stop is O(1) on the number of sounds
102 void play(Stream stream) override;
103 void stop(Stream stream) override;
104 void set_volume(Stream stream, float volume) override;
105
106private:
107
108 ma_engine engine;
109
110 // SoundAssetId.index is an index in this vector
111 // We use a deque becaues it does not reallocate obejcts, since
112 // miniaudio decoders are not move safe!
113 std::deque<SoundAssetSlot> sound_assets;
114 // Stream is an index in this array
115 // We use a fixed number of streams which are allocated and
116 // deallocated in initialize() / terminate(). We keep track
117 // of which slots are used and which ones are free.
118 tenno::array<StreamSlot, BRENTA_NUM_STREAMS> stream_pool;
119
120 std::optional<MiniaudioStream> create_stream();
121
122};
123
124} // namespace brenta