From 7619caef3e1dc43c8f6fe63d3bbdd4b836a5a348 Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Mon, 10 Oct 2016 00:36:28 +0200 Subject: [PATCH] audio: minimal streaming API This will allow us to generate sounds on the fly on several dedicated channels. Until now we could only play fully loaded samples. --- .gitignore | 1 + doc/tutorial/09_sound.cpp | 114 ++++++++++++++++++++++++++++++++++++++ doc/tutorial/Makefile.am | 6 +- src/audio/audio.cpp | 62 +++++++++++++++++++-- src/lol/audio/audio.h | 5 +- 5 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 doc/tutorial/09_sound.cpp diff --git a/.gitignore b/.gitignore index ca6e3627..e45b6e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ doc/tutorial/05_easymesh doc/tutorial/06_sprite doc/tutorial/07_input doc/tutorial/08_fbo +doc/tutorial/09_sound doc/tutorial/11_fractal doc/tutorial/12_voronoi doc/tutorial/13_shader_builder diff --git a/doc/tutorial/09_sound.cpp b/doc/tutorial/09_sound.cpp new file mode 100644 index 00000000..ee85294f --- /dev/null +++ b/doc/tutorial/09_sound.cpp @@ -0,0 +1,114 @@ +// +// Lol Engine — Sound tutorial +// +// Copyright © 2011—2016 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. +// + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include + +using namespace lol; + +class sound_demo : public WorldEntity +{ +public: + sound_demo() + { + for (auto &val : m_streams) + val = -1; + + m_controller = new Controller("Default"); + m_profile << InputProfile::Keyboard(0, "Space") + << InputProfile::MouseKey(1, "Left"); + m_controller->Init(m_profile); + m_mouse = InputDevice::GetMouse(); + + m_text = new Text("SPACE for sine wave, Left Click for white noise", + "data/font/ascii.png"); + m_text->SetPos(vec3(5, 5, 1)); + Ticker::Ref(m_text); + } + + ~sound_demo() + { + Ticker::Unref(m_text); + } + + void synth(int mode, void *buf, int bytes) + { + uint16_t *stream = (uint16_t *)buf; + for (int i = 0; i < bytes / 2; ++i) + { + switch (mode) + { + case 0: // sine wave + stream[i] = 400 * lol::sin(12 * i * F_TAU / bytes); + break; + case 1: // white noise + stream[i] = lol::rand(-120, 120); + break; + } + } + } + + virtual void TickGame(float seconds) + { + WorldEntity::TickGame(seconds); + + for (int i = 0; i < 2; ++i) + { + if (!m_controller->WasKeyPressedThisFrame(i)) + continue; + + if (m_streams[i] < 0) + { + auto f = std::bind(&sound_demo::synth, this, i, + std::placeholders::_1, + std::placeholders::_2); + m_streams[i] = audio::start_streaming(f); + } + else + { + audio::stop_streaming(m_streams[i]); + m_streams[i] = -1; + } + } + } + + virtual void TickDraw(float seconds, Scene &scene) + { + WorldEntity::TickDraw(seconds, scene); + } + +private: + int m_streams[2]; + + InputDevice *m_mouse; + Controller *m_controller; + InputProfile m_profile; + + Text *m_text; +}; + +int main(int argc, char **argv) +{ + sys::init(argc, argv); + + Application app("Tutorial 9: Sound", ivec2(640, 480), 60.0f); + new sound_demo(); + app.Run(); + + return EXIT_SUCCESS; +} + diff --git a/doc/tutorial/Makefile.am b/doc/tutorial/Makefile.am index 78d33d9d..b5c5cee3 100644 --- a/doc/tutorial/Makefile.am +++ b/doc/tutorial/Makefile.am @@ -3,7 +3,7 @@ include $(top_srcdir)/build/autotools/common.am if BUILD_TUTORIAL noinst_PROGRAMS = 01_triangle 02_cube 03_noise 04_texture 05_easymesh \ - 06_sprite 07_input 08_fbo 11_fractal \ + 06_sprite 07_input 08_fbo 09_sound 11_fractal \ 12_voronoi 13_shader_builder 14_lol_lua endif @@ -43,6 +43,10 @@ endif 08_fbo_CPPFLAGS = $(AM_CPPFLAGS) 08_fbo_DEPENDENCIES = @LOL_DEPS@ +09_sound_SOURCES = 09_sound.cpp +09_sound_CPPFLAGS = $(AM_CPPFLAGS) +09_sound_DEPENDENCIES = @LOL_DEPS@ + 11_fractal_SOURCES = 11_fractal.cpp 11_fractal.lolfx 11_fractal_CPPFLAGS = $(AM_CPPFLAGS) 11_fractal_DEPENDENCIES = @LOL_DEPS@ diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 884950e9..df2206f5 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -28,14 +28,26 @@ namespace lol { -/* +struct audio_streamer +{ + int m_channel; + std::function m_callback; +#if defined LOL_USE_SDL_MIXER + Mix_Chunk *m_chunk; +#endif +}; + +array g_streamers; + + /* * Public audio class */ void audio::init() { #if defined LOL_USE_SDL_MIXER - Mix_OpenAudio(22050, AUDIO_S16, 8, 1024); + Mix_OpenAudio(22050, AUDIO_S16, 2, 1024); + set_channels(8); #endif } @@ -61,8 +73,6 @@ void audio::mute_all() { #if defined LOL_USE_SDL_MIXER Mix_Volume(-1,0); -#else - UNUSED(false); #endif } @@ -70,8 +80,50 @@ void audio::unmute_all() { #if defined LOL_USE_SDL_MIXER Mix_Volume(-1,MIX_MAX_VOLUME); +#endif +} + +int audio::start_streaming(std::function const &f) +{ +#if defined LOL_USE_SDL_MIXER + static auto trampoline = [](int, void *stream, int bytes, void *udata) + { + auto s = (audio_streamer *)udata; + s->m_callback(stream, bytes); + }; + + audio_streamer *s = new audio_streamer(); + g_streamers.push(s); + + array empty; + empty.resize(1024); + s->m_chunk = Mix_QuickLoad_RAW(empty.data(), empty.bytes()); + s->m_channel = Mix_PlayChannel(-1, s->m_chunk, -1); + s->m_callback = f; + Mix_RegisterEffect(s->m_channel, trampoline, nullptr, s); + + return s->m_channel; +#else + UNUSED(f); + return -1; +#endif +} + +void audio::stop_streaming(int channel) +{ +#if defined LOL_USE_SDL_MIXER + for (int i = 0; i < g_streamers.count(); ++i) + { + if (g_streamers[i]->m_channel == channel) + { + Mix_HaltChannel(channel); + delete g_streamers[i]; + g_streamers.remove(i); + return; + } + } #else - UNUSED(false); + UNUSED(channel); #endif } diff --git a/src/lol/audio/audio.h b/src/lol/audio/audio.h index 8ad9a40d..d176f1fa 100644 --- a/src/lol/audio/audio.h +++ b/src/lol/audio/audio.h @@ -18,7 +18,7 @@ // Helper functions to set up the audio device. // -#include +#include namespace lol { @@ -33,6 +33,9 @@ public: static void mute_all(); static void unmute_all(); + static int start_streaming(std::function const &f); + static void stop_streaming(int channel); + private: audio() {} };