This will allow us to generate sounds on the fly on several dedicated channels. Until now we could only play fully loaded samples.legacy
@@ -95,6 +95,7 @@ doc/tutorial/05_easymesh | |||||
doc/tutorial/06_sprite | doc/tutorial/06_sprite | ||||
doc/tutorial/07_input | doc/tutorial/07_input | ||||
doc/tutorial/08_fbo | doc/tutorial/08_fbo | ||||
doc/tutorial/09_sound | |||||
doc/tutorial/11_fractal | doc/tutorial/11_fractal | ||||
doc/tutorial/12_voronoi | doc/tutorial/12_voronoi | ||||
doc/tutorial/13_shader_builder | doc/tutorial/13_shader_builder | ||||
@@ -0,0 +1,114 @@ | |||||
// | |||||
// Lol Engine — Sound tutorial | |||||
// | |||||
// Copyright © 2011—2016 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. | |||||
// | |||||
#if HAVE_CONFIG_H | |||||
# include "config.h" | |||||
#endif | |||||
#include <lol/engine.h> | |||||
#include <functional> | |||||
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; | |||||
} | |||||
@@ -3,7 +3,7 @@ include $(top_srcdir)/build/autotools/common.am | |||||
if BUILD_TUTORIAL | if BUILD_TUTORIAL | ||||
noinst_PROGRAMS = 01_triangle 02_cube 03_noise 04_texture 05_easymesh \ | 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 | 12_voronoi 13_shader_builder 14_lol_lua | ||||
endif | endif | ||||
@@ -43,6 +43,10 @@ endif | |||||
08_fbo_CPPFLAGS = $(AM_CPPFLAGS) | 08_fbo_CPPFLAGS = $(AM_CPPFLAGS) | ||||
08_fbo_DEPENDENCIES = @LOL_DEPS@ | 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_SOURCES = 11_fractal.cpp 11_fractal.lolfx | ||||
11_fractal_CPPFLAGS = $(AM_CPPFLAGS) | 11_fractal_CPPFLAGS = $(AM_CPPFLAGS) | ||||
11_fractal_DEPENDENCIES = @LOL_DEPS@ | 11_fractal_DEPENDENCIES = @LOL_DEPS@ | ||||
@@ -28,14 +28,26 @@ | |||||
namespace lol | namespace lol | ||||
{ | { | ||||
/* | |||||
struct audio_streamer | |||||
{ | |||||
int m_channel; | |||||
std::function<void(void *, int)> m_callback; | |||||
#if defined LOL_USE_SDL_MIXER | |||||
Mix_Chunk *m_chunk; | |||||
#endif | |||||
}; | |||||
array<audio_streamer *> g_streamers; | |||||
/* | |||||
* Public audio class | * Public audio class | ||||
*/ | */ | ||||
void audio::init() | void audio::init() | ||||
{ | { | ||||
#if defined LOL_USE_SDL_MIXER | #if defined LOL_USE_SDL_MIXER | ||||
Mix_OpenAudio(22050, AUDIO_S16, 8, 1024); | |||||
Mix_OpenAudio(22050, AUDIO_S16, 2, 1024); | |||||
set_channels(8); | |||||
#endif | #endif | ||||
} | } | ||||
@@ -61,8 +73,6 @@ void audio::mute_all() | |||||
{ | { | ||||
#if defined LOL_USE_SDL_MIXER | #if defined LOL_USE_SDL_MIXER | ||||
Mix_Volume(-1,0); | Mix_Volume(-1,0); | ||||
#else | |||||
UNUSED(false); | |||||
#endif | #endif | ||||
} | } | ||||
@@ -70,8 +80,50 @@ void audio::unmute_all() | |||||
{ | { | ||||
#if defined LOL_USE_SDL_MIXER | #if defined LOL_USE_SDL_MIXER | ||||
Mix_Volume(-1,MIX_MAX_VOLUME); | Mix_Volume(-1,MIX_MAX_VOLUME); | ||||
#endif | |||||
} | |||||
int audio::start_streaming(std::function<void(void *, int)> 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<uint8_t> 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 | #else | ||||
UNUSED(false); | |||||
UNUSED(channel); | |||||
#endif | #endif | ||||
} | } | ||||
@@ -18,7 +18,7 @@ | |||||
// Helper functions to set up the audio device. | // Helper functions to set up the audio device. | ||||
// | // | ||||
#include <stdint.h> | |||||
#include <functional> | |||||
namespace lol | namespace lol | ||||
{ | { | ||||
@@ -33,6 +33,9 @@ public: | |||||
static void mute_all(); | static void mute_all(); | ||||
static void unmute_all(); | static void unmute_all(); | ||||
static int start_streaming(std::function<void(void *, int)> const &f); | |||||
static void stop_streaming(int channel); | |||||
private: | private: | ||||
audio() {} | audio() {} | ||||
}; | }; | ||||