// // Lol Engine // // Copyright © 2010—2019 Sam Hocevar // // 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 #include #include #include #if LOL_USE_SDL_MIXER # if HAVE_SDL2_SDL_H # include # include # elif HAVE_SDL_H # include # include # 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 m_callback; // Buffer where the streaming client will write, and where the SDL // audio conversion may happen. std::vector 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 m_output_buffer; SDL_AudioCVT m_convert; Mix_Chunk *m_chunk = nullptr; }; static std::unordered_set> 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 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(); 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 const &, enum audio::format, int, int) { return -1; } void audio::stop_streaming(int) {} #endif } /* namespace lol */