/* Minetest Copyright (C) 2022 DS This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once /* * * The coordinate space for sounds (sound-space): * ---------------------------------------------- * * * The functions from ISoundManager (see sound.h) take spatial vectors in node-space. * * All other `v3f`s here are, if not told otherwise, in sound-space, which is * defined as node-space mirrored along the x-axis. * (This is needed because OpenAL uses a right-handed coordinate system.) * * Use `swap_handedness()` from `al_helpers.h` to convert between those two * coordinate spaces. * * * How sounds are loaded: * ---------------------- * * * Step 1: * `loadSoundFile` or `loadSoundFile` is called. This adds an unopen sound with * the given name to `m_sound_datas_unopen`. * Unopen / lazy sounds (`ISoundDataUnopen`) are ogg-vorbis files that we did not yet * start to decode. (Decoding an unopen sound does not fail under normal circumstances * (because we check whether the file exists at least), if it does fail anyways, * we should notify the user.) * * Step 2: * `addSoundToGroup` is called, to add the name from step 1 to a group. If the * group does not yet exist, a new one is created. A group can later be played. * (The mapping is stored in `m_sound_groups`.) * * Step 3: * `playSound` or `playSoundAt` is called. * * Step 3.1: * If the group with the name `spec.name` does not exist, and `spec.use_local_fallback` * is true, a new group is created using the user's sound-pack. * * Step 3.2: * We choose one random sound name from the given group. * * Step 3.3: * We open the sound (see `openSingleSound`). * If the sound is already open (in `m_sound_datas_open`), we take that one. * Otherwise we open it by calling `ISoundDataUnopen::open`. We choose (by * sound length), whether it's a single-buffer (`SoundDataOpenBuffer`) or * streamed (`SoundDataOpenStream`) sound. * Single-buffer sounds are always completely loaded. Streamed sounds can be * partially loaded. * The sound is erased from `m_sound_datas_unopen` and added to `m_sound_datas_open`. * Open sounds are kept forever. * * Step 3.4: * We create the new `PlayingSound`. It has a `shared_ptr` to its open sound. * If the open sound is streaming, the playing sound needs to be stepped using * `PlayingSound::stepStream` for enqueuing buffers. For this purpose, the sound * is added to `m_sounds_streaming` (as `weak_ptr`). * If the sound is fading, it is added to `m_sounds_fading` for regular fade-stepping. * The sound is also added to `m_sounds_playing`, so that one can access it * via its sound handle. * * Step 4: * Streaming sounds are updated. For details see [Streaming of sounds]. * * Step 5: * At deinitialization, we can just let the destructors do their work. * Sound sources are deleted (and with this also stopped) by ~PlayingSound. * Buffers can't be deleted while sound sources using them exist, because * PlayingSound has a shared_ptr to its ISoundData. * * * Streaming of sounds: * -------------------- * * In each "bigstep", all streamed sounds are stepStream()ed. This means a * sound can be stepped at any point in time in the bigstep's interval. * * In the worst case, a sound is stepped at the start of one bigstep and in the * end of the next bigstep. So between two stepStream()-calls lie at most * 2 * STREAM_BIGSTEP_TIME seconds. * As there are always 2 sound buffers enqueued, at least one untouched full buffer * is still available after the first stepStream(). * If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence * not run into an empty queue. * * The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter, * other sounds that may have taken long to stepStream(), and sounds being played * faster due to Doppler effect. * */ namespace sound { // constants // in seconds constexpr f32 REMOVE_DEAD_SOUNDS_INTERVAL = 2.0f; // maximum length in seconds that a sound can have without being streamed constexpr f32 SOUND_DURATION_MAX_SINGLE = 3.0f; // minimum time in seconds of a single buffer in a streamed sound constexpr f32 MIN_STREAM_BUFFER_LENGTH = 1.0f; // duration in seconds of one bigstep constexpr f32 STREAM_BIGSTEP_TIME = 0.3f; // step duration for the OpenALSoundManager thread, in seconds constexpr f32 SOUNDTHREAD_DTIME = 0.016f; static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f, "See [Streaming of sounds]."); static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f, "There's no benefit in streaming if we can't queue more than 2 buffers."); } // namespace sound