/*
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; ifnot, 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
