|
- //
- // Lol Engine
- //
- // Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net>
- //
- // Lol Engine is free software. It comes without any warranty, to
- // the extent permitted by applicable law. You can redistribute it
- // and/or modify it under the terms of the Do What the Fuck You Want
- // to Public License, Version 2, as published by the WTFPL Task Force.
- // See http://www.wtfpl.net/ for more details.
- //
-
- #include <lol/engine-internal.h>
-
- #include <array>
- #include <unordered_set>
- #include <functional>
-
- #if LOL_USE_SDL_MIXER
- # if HAVE_SDL2_SDL_H
- # include <SDL2/SDL.h>
- # include <SDL2/SDL_mixer.h>
- # elif HAVE_SDL_H
- # include <SDL.h>
- # include <SDL_mixer.h>
- # endif
- #endif
-
- // Buffer size, in samples (https://wiki.libsdl.org/SDL_AudioSpec)
- // “[…] refers to the size of the audio buffer in sample frames. A sample frame
- // is a chunk of audio data of the size specified in format multiplied by the
- // number of channels.”
- #define LOL_AUDIO_DEFAULT_FRAMES 1024
-
- #define LOL_AUDIO_DEFAULT_TRACKS 8
- #define LOL_AUDIO_DEFAULT_CHANNELS 2
- #define LOL_AUDIO_DEFAULT_FORMAT AUDIO_S16
- #define LOL_AUDIO_DEFAULT_RATE 22050
-
- namespace lol
- {
-
- #if defined LOL_USE_SDL_MIXER
- struct audio_streamer
- {
- int m_track = -1;
- std::function<void(void *, int)> m_callback;
- // Buffer where the streaming client will write, and where the SDL
- // audio conversion may happen.
- std::vector<uint8_t> m_convert_buffer;
- // Remaining bytes in the conversion buffer.
- int m_extra_bytes = 0;
- // Buffer used to write the converted audio data for mixing.
- std::vector<uint8_t> m_output_buffer;
-
- SDL_AudioCVT m_convert;
- Mix_Chunk *m_chunk = nullptr;
- };
-
- static std::unordered_set<std::shared_ptr<audio_streamer>> g_streamers;
-
- // The global audio format
- static int g_frequency, g_channels;
- static audio::format g_format;
-
- static audio::format sdl2lol_format(Uint16 sdl_format)
- {
- switch (sdl_format)
- {
- case AUDIO_U8: return audio::format::uint8;
- case AUDIO_S8: return audio::format::sint8;
- case AUDIO_U16LSB: return audio::format::uint16le;
- case AUDIO_U16MSB: return audio::format::uint16be;
- case AUDIO_S16LSB: return audio::format::sint16le;
- case AUDIO_S16MSB: return audio::format::sint16be;
- case AUDIO_S32LSB: return audio::format::sint32le;
- case AUDIO_S32MSB: return audio::format::sint32be;
- case AUDIO_F32LSB: return audio::format::float32le;
- case AUDIO_F32MSB: return audio::format::float32be;
- default: return audio::format::unknown;
- }
- }
-
- static int lol2sdl_format(audio::format format)
- {
- switch (format)
- {
- case audio::format::uint8: return AUDIO_U8;
- case audio::format::sint8: return AUDIO_S8;
- case audio::format::uint16le: return AUDIO_U16LSB;
- case audio::format::uint16be: return AUDIO_U16MSB;
- case audio::format::sint16le: return AUDIO_S16LSB;
- case audio::format::sint16be: return AUDIO_S16MSB;
- case audio::format::sint32le: return AUDIO_S32LSB;
- case audio::format::sint32be: return AUDIO_S32MSB;
- case audio::format::float32le: return AUDIO_F32LSB;
- case audio::format::float32be: return AUDIO_F32MSB;
- default: return 0;
- }
- }
- #endif
-
- /*
- * Public audio class
- */
-
- #if defined LOL_USE_SDL_MIXER
- void audio::init()
- {
- if (Mix_OpenAudio(LOL_AUDIO_DEFAULT_RATE, LOL_AUDIO_DEFAULT_FORMAT, LOL_AUDIO_DEFAULT_CHANNELS, LOL_AUDIO_DEFAULT_FRAMES) < 0)
- {
- msg::error("error opening audio: %s\n", Mix_GetError());
- return;
- }
-
- set_tracks(LOL_AUDIO_DEFAULT_TRACKS);
-
- Uint16 sdl_format;
- if (Mix_QuerySpec(&g_frequency, &sdl_format, &g_channels) == 0)
- {
- msg::error("error querying audio: %s\n", Mix_GetError());
- return;
- }
-
- g_format = sdl2lol_format(sdl_format);
-
- char const *u = (SDL_AUDIO_ISFLOAT(sdl_format) || SDL_AUDIO_ISSIGNED(sdl_format)) ? "" : "u";
- char const *t = SDL_AUDIO_ISFLOAT(sdl_format) ? "float" : "int";
- int b = SDL_AUDIO_BITSIZE(sdl_format);
- char const *e = b <= 8 ? "" : SDL_AUDIO_ISLITTLEENDIAN(sdl_format) ? "le" : "be";
-
- msg::info("audio initialised: freq=%dHz format=%s%s%d%s channels=%d\n",
- g_frequency, u, t, b, e, g_channels);
- }
-
- void audio::shutdown()
- {
- }
-
- void audio::set_tracks(int tracks)
- {
- Mix_AllocateChannels(tracks);
- }
-
- void audio::set_volume(int track, int volume)
- {
- Mix_Volume(track, volume);
- }
-
- void audio::mute_all()
- {
- Mix_Volume(-1, 0);
- }
-
- void audio::unmute_all()
- {
- Mix_Volume(-1, MIX_MAX_VOLUME);
- }
-
- int audio::start_streaming(std::function<void(void *, int)> const &f,
- enum audio::format format /* = audio::format::sint16le */,
- int frequency /* = 22050 */,
- int channels /* = 2 */)
- {
- static auto trampoline = [](int, void *stream, int bytes, void *udata)
- {
- auto s = (audio_streamer *)udata;
-
- // If there were still bytes from a previous conversion, copy them.
- if (s->m_extra_bytes)
- {
- int tocopy = lol::min(bytes, s->m_extra_bytes);
- memcpy(stream, s->m_convert.buf + s->m_convert.len_cvt - s->m_extra_bytes, tocopy);
- s->m_extra_bytes -= tocopy;
- bytes -= tocopy;
- stream = (void *)((uint8_t *)stream + tocopy);
- }
-
- // Ask the callback for more bytes as long as we need them.
- while (bytes > 0)
- {
- s->m_callback(s->m_convert.buf, s->m_convert.len);
- if (s->m_convert.needed)
- SDL_ConvertAudio(&s->m_convert);
- int tocopy = lol::min(bytes, s->m_convert.len_cvt);
- s->m_extra_bytes = s->m_convert.len_cvt - tocopy;
- memcpy(stream, s->m_convert.buf, tocopy);
- bytes -= tocopy;
- stream = (void *)((uint8_t *)stream + tocopy);
- }
- };
-
- auto s = std::make_shared<audio_streamer>();
- g_streamers.insert(s);
-
- // Build an audio converter that converts from a given streaming format
- // to the format SDL was currently initialised with, if necessary.
- Uint16 sdl_format = lol2sdl_format(format);
- Uint16 sdl_g_format = lol2sdl_format(g_format);
- SDL_BuildAudioCVT(&s->m_convert, sdl_format, channels, frequency, sdl_g_format, g_channels, g_frequency);
-
- // This is how many bytes we will ask the streaming callback
- s->m_convert.len = LOL_AUDIO_DEFAULT_FRAMES * channels * SDL_AUDIO_BITSIZE(sdl_format) / 8;
- s->m_convert_buffer.resize(s->m_convert.len * s->m_convert.len_mult);
- s->m_convert.buf = s->m_convert_buffer.data();
-
- // This is how many bytes we will send to the SDL mixer
- Uint32 output_bytes = LOL_AUDIO_DEFAULT_FRAMES * g_channels * SDL_AUDIO_BITSIZE(sdl_g_format) / 8;
- s->m_output_buffer.resize(output_bytes);
- Uint8* audio_data = (Uint8*)s->m_output_buffer.data();
- s->m_chunk = Mix_QuickLoad_RAW(audio_data, output_bytes);
- s->m_track = Mix_PlayChannel(-1, s->m_chunk, -1);
- s->m_callback = f;
- Mix_RegisterEffect(s->m_track, trampoline, nullptr, s.get());
-
- return s->m_track;
- }
-
- void audio::stop_streaming(int track)
- {
- for (auto streamer : g_streamers)
- {
- if (streamer->m_track == track)
- {
- Mix_HaltChannel(track);
- g_streamers.erase(streamer);
- break;
- }
- }
- }
-
- #elif __NX__
- #else
- void audio::init() {}
- void audio::shutdown() {}
- void audio::set_tracks(int) {}
- void audio::set_volume(int, int) {}
- void audio::mute_all() {}
- void audio::unmute_all() {}
- int audio::start_streaming(std::function<void(void *, int)> const &,
- enum audio::format, int, int) { return -1; }
- void audio::stop_streaming(int) {}
- #endif
-
- } /* namespace lol */
|