@@ -17,6 +17,7 @@ | |||
#include <lol/engine.h> | |||
#include <functional> | |||
#include <array> | |||
using namespace lol; | |||
@@ -25,8 +26,17 @@ class sound_demo : public WorldEntity | |||
public: | |||
sound_demo() | |||
{ | |||
for (auto &val : m_streams) | |||
val = -1; | |||
for (int i = 0; i < 2; ++i) | |||
{ | |||
auto f = std::bind(&sound_demo::synth, this, i, | |||
std::placeholders::_1, | |||
std::placeholders::_2); | |||
m_streams[i] = audio::start_streaming(f); | |||
} | |||
for (size_t i = 0; i < m_instrument.size(); ++i) | |||
m_instrument[i] = (int16_t)(i % 80 * (10000 - lol::abs(i - 10000)) * 40 / 10000); | |||
m_sample = sample::create(m_instrument.data(), 40000); | |||
m_text = new Text("SPACE for sine wave, Left Click for white noise", | |||
"data/font/ascii.png"); | |||
@@ -39,18 +49,25 @@ public: | |||
Ticker::Unref(m_text); | |||
} | |||
void synth(int mode, void *buf, int bytes) | |||
void synth(int channel, void *buf, int bytes) | |||
{ | |||
int mode = (1 << channel) & m_mask; | |||
int16_t *stream = (int16_t *)buf; | |||
for (int i = 0; i < bytes / 2; ++i) | |||
for (int i = 0; i < bytes / 2; i += 2) | |||
{ | |||
switch (mode) | |||
{ | |||
case 0: // sine wave | |||
stream[i] = lol::sin(4 * i * F_TAU / bytes) > 0 ? 800 : -800; | |||
case 2: // square / triangle signals | |||
stream[i] = 800 * (i % 128 > 64 ? -1 : 1); | |||
stream[i + 1] = (i % 128 - 64) * 15; | |||
break; | |||
case 1: // white noise | |||
stream[i] = lol::rand(-200, 200); | |||
stream[i] = lol::rand(-2000, 2000); | |||
stream[i + 1] = lol::rand(-1000, 1000); | |||
break; | |||
case 0: // inactive | |||
stream[i] = stream[i + 1] = 0; | |||
break; | |||
} | |||
} | |||
@@ -63,35 +80,28 @@ public: | |||
auto mouse = input::mouse(); | |||
auto keyboard = input::keyboard(); | |||
for (int i = 0; i < 2; ++i) | |||
{ | |||
if (i == 0 && !keyboard->key_pressed(input::key::SC_Space)) | |||
continue; | |||
if (i == 1 && !mouse->button_pressed(input::button::BTN_Left)) | |||
continue; | |||
if (keyboard->key_pressed(input::key::SC_Return)) | |||
m_sample->play(); | |||
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; | |||
} | |||
} | |||
if (keyboard->key_pressed(input::key::SC_Space)) | |||
m_mask ^= 2; | |||
if (mouse->button_pressed(input::button::BTN_Left)) | |||
m_mask ^= 1; | |||
} | |||
virtual void tick_draw(float seconds, Scene &scene) override | |||
virtual bool release_game() override | |||
{ | |||
WorldEntity::tick_draw(seconds, scene); | |||
for (int i = 0; i < 2; ++i) | |||
audio::stop_streaming(m_streams[i]); | |||
return true; | |||
} | |||
private: | |||
int m_streams[2]; | |||
int m_mask = 0; | |||
std::array<int16_t, 20000> m_instrument; | |||
sample* m_sample; | |||
Text *m_text; | |||
}; | |||
@@ -12,6 +12,7 @@ | |||
#include <lol/engine-internal.h> | |||
#include <array> | |||
#include <unordered_set> | |||
#include <functional> | |||
@@ -25,6 +26,13 @@ | |||
# 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_SAMPLE_FRAMES 1024 | |||
#define LOL_AUDIO_CHANNELS 2 | |||
namespace lol | |||
{ | |||
@@ -32,8 +40,8 @@ struct audio_streamer | |||
{ | |||
int m_channel; | |||
std::function<void(void *, int)> m_callback; | |||
std::array<uint16_t, LOL_AUDIO_CHANNELS * LOL_AUDIO_SAMPLE_FRAMES> m_empty; // SDL needs a reference to this | |||
#if defined LOL_USE_SDL_MIXER | |||
std::vector<uint8_t> m_empty; // SDL keeps a reference to this | |||
Mix_Chunk *m_chunk; | |||
#endif | |||
}; | |||
@@ -47,7 +55,7 @@ std::unordered_set<std::shared_ptr<audio_streamer>> audio::m_streamers; | |||
#if defined LOL_USE_SDL_MIXER | |||
void audio::init() | |||
{ | |||
Mix_OpenAudio(22050, AUDIO_S16, 2, 1024); | |||
Mix_OpenAudio(22050, AUDIO_S16, LOL_AUDIO_CHANNELS, LOL_AUDIO_SAMPLE_FRAMES); | |||
set_channels(8); | |||
} | |||
@@ -79,12 +87,13 @@ int audio::start_streaming(std::function<void(void *, int)> const &f) | |||
s->m_callback(stream, bytes); | |||
}; | |||
std::shared_ptr<audio_streamer> s = std::make_shared<audio_streamer>(); | |||
auto s = std::make_shared<audio_streamer>(); | |||
m_streamers.insert(s); | |||
s->m_empty.resize(1024); | |||
s->m_chunk = Mix_QuickLoad_RAW(s->m_empty.data(), | |||
(Uint32)(s->m_empty.size() * sizeof(uint8_t))); | |||
Uint8* audio_data = (Uint8*)s->m_empty.data(); | |||
Uint32 audio_size = (Uint32)(s->m_empty.size() * sizeof(s->m_empty[0])); | |||
memset(audio_data, 17, audio_size); | |||
s->m_chunk = Mix_QuickLoad_RAW(audio_data, audio_size); | |||
s->m_channel = Mix_PlayChannel(-1, s->m_chunk, -1); | |||
s->m_callback = f; | |||
Mix_RegisterEffect(s->m_channel, trampoline, nullptr, s.get()); | |||
@@ -58,6 +58,11 @@ sample *sample::create(std::string const &path) | |||
return ret ? ret : sample_cache.set(path, new sample(path)); | |||
} | |||
sample *sample::create(void const *samples, size_t len) | |||
{ | |||
return new sample(samples, len); | |||
} | |||
void sample::destroy(sample *s) | |||
{ | |||
// FIXME: decrement! | |||
@@ -65,7 +70,7 @@ void sample::destroy(sample *s) | |||
} | |||
sample::sample(std::string const &path) | |||
: data(new sample_data()) | |||
: data(std::make_unique<sample_data>()) | |||
{ | |||
data->m_name = std::string("<sample> ") + path; | |||
@@ -84,13 +89,23 @@ sample::sample(std::string const &path) | |||
#endif | |||
} | |||
sample::sample(void const *samples, size_t len) | |||
: data(std::make_unique<sample_data>()) | |||
{ | |||
data->m_name = std::string("<sample>"); | |||
#if defined LOL_USE_SDL_MIXER | |||
data->m_chunk = Mix_QuickLoad_RAW((Uint8 *)samples, (Uint32)len); | |||
data->m_channel = -1; | |||
#endif | |||
} | |||
sample::~sample() | |||
{ | |||
#if defined LOL_USE_SDL_MIXER | |||
if (data->m_chunk) | |||
Mix_FreeChunk(data->m_chunk); | |||
#endif | |||
delete data; | |||
} | |||
void sample::tick_game(float seconds) | |||
@@ -25,16 +25,16 @@ | |||
namespace lol | |||
{ | |||
class sample_data; | |||
class sample : public entity | |||
{ | |||
public: | |||
static sample *create(std::string const &path); | |||
static sample* create(void const* samples, size_t len); | |||
static void destroy(sample *s); | |||
protected: | |||
sample(std::string const &path); | |||
sample(void const *samples, size_t len); | |||
virtual ~sample(); | |||
/* Inherited from entity */ | |||
@@ -48,7 +48,7 @@ public: | |||
void stop(); | |||
private: | |||
sample_data *data; | |||
std::unique_ptr<class sample_data> data; | |||
}; | |||
} /* namespace lol */ | |||
@@ -197,8 +197,8 @@ void SdlInput::tick(float seconds) | |||
//case SDL_TEXTEDITING: //TODO: handle that? | |||
case SDL_TEXTINPUT: | |||
keyboard->internal_add_text(event.text.text); | |||
break; | |||
keyboard->internal_add_text(event.text.text); | |||
break; | |||
case SDL_MOUSEBUTTONDOWN: | |||
case SDL_MOUSEBUTTONUP: | |||