| @@ -17,6 +17,7 @@ | |||||
| #include <lol/engine.h> | #include <lol/engine.h> | ||||
| #include <functional> | #include <functional> | ||||
| #include <array> | |||||
| using namespace lol; | using namespace lol; | ||||
| @@ -25,8 +26,17 @@ class sound_demo : public WorldEntity | |||||
| public: | public: | ||||
| sound_demo() | 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", | m_text = new Text("SPACE for sine wave, Left Click for white noise", | ||||
| "data/font/ascii.png"); | "data/font/ascii.png"); | ||||
| @@ -39,18 +49,25 @@ public: | |||||
| Ticker::Unref(m_text); | 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; | 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) | 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; | break; | ||||
| case 1: // white noise | 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; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -63,35 +80,28 @@ public: | |||||
| auto mouse = input::mouse(); | auto mouse = input::mouse(); | ||||
| auto keyboard = input::keyboard(); | 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: | private: | ||||
| int m_streams[2]; | int m_streams[2]; | ||||
| int m_mask = 0; | |||||
| std::array<int16_t, 20000> m_instrument; | |||||
| sample* m_sample; | |||||
| Text *m_text; | Text *m_text; | ||||
| }; | }; | ||||
| @@ -12,6 +12,7 @@ | |||||
| #include <lol/engine-internal.h> | #include <lol/engine-internal.h> | ||||
| #include <array> | |||||
| #include <unordered_set> | #include <unordered_set> | ||||
| #include <functional> | #include <functional> | ||||
| @@ -25,6 +26,13 @@ | |||||
| # endif | # endif | ||||
| #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 | namespace lol | ||||
| { | { | ||||
| @@ -32,8 +40,8 @@ struct audio_streamer | |||||
| { | { | ||||
| int m_channel; | int m_channel; | ||||
| std::function<void(void *, int)> m_callback; | 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 | #if defined LOL_USE_SDL_MIXER | ||||
| std::vector<uint8_t> m_empty; // SDL keeps a reference to this | |||||
| Mix_Chunk *m_chunk; | Mix_Chunk *m_chunk; | ||||
| #endif | #endif | ||||
| }; | }; | ||||
| @@ -47,7 +55,7 @@ std::unordered_set<std::shared_ptr<audio_streamer>> audio::m_streamers; | |||||
| #if defined LOL_USE_SDL_MIXER | #if defined LOL_USE_SDL_MIXER | ||||
| void audio::init() | 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); | set_channels(8); | ||||
| } | } | ||||
| @@ -79,12 +87,13 @@ int audio::start_streaming(std::function<void(void *, int)> const &f) | |||||
| s->m_callback(stream, bytes); | 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); | 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_channel = Mix_PlayChannel(-1, s->m_chunk, -1); | ||||
| s->m_callback = f; | s->m_callback = f; | ||||
| Mix_RegisterEffect(s->m_channel, trampoline, nullptr, s.get()); | 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)); | 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) | void sample::destroy(sample *s) | ||||
| { | { | ||||
| // FIXME: decrement! | // FIXME: decrement! | ||||
| @@ -65,7 +70,7 @@ void sample::destroy(sample *s) | |||||
| } | } | ||||
| sample::sample(std::string const &path) | sample::sample(std::string const &path) | ||||
| : data(new sample_data()) | |||||
| : data(std::make_unique<sample_data>()) | |||||
| { | { | ||||
| data->m_name = std::string("<sample> ") + path; | data->m_name = std::string("<sample> ") + path; | ||||
| @@ -84,13 +89,23 @@ sample::sample(std::string const &path) | |||||
| #endif | #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() | sample::~sample() | ||||
| { | { | ||||
| #if defined LOL_USE_SDL_MIXER | #if defined LOL_USE_SDL_MIXER | ||||
| if (data->m_chunk) | if (data->m_chunk) | ||||
| Mix_FreeChunk(data->m_chunk); | Mix_FreeChunk(data->m_chunk); | ||||
| #endif | #endif | ||||
| delete data; | |||||
| } | } | ||||
| void sample::tick_game(float seconds) | void sample::tick_game(float seconds) | ||||
| @@ -25,16 +25,16 @@ | |||||
| namespace lol | namespace lol | ||||
| { | { | ||||
| class sample_data; | |||||
| class sample : public entity | class sample : public entity | ||||
| { | { | ||||
| public: | public: | ||||
| static sample *create(std::string const &path); | static sample *create(std::string const &path); | ||||
| static sample* create(void const* samples, size_t len); | |||||
| static void destroy(sample *s); | static void destroy(sample *s); | ||||
| protected: | protected: | ||||
| sample(std::string const &path); | sample(std::string const &path); | ||||
| sample(void const *samples, size_t len); | |||||
| virtual ~sample(); | virtual ~sample(); | ||||
| /* Inherited from entity */ | /* Inherited from entity */ | ||||
| @@ -48,7 +48,7 @@ public: | |||||
| void stop(); | void stop(); | ||||
| private: | private: | ||||
| sample_data *data; | |||||
| std::unique_ptr<class sample_data> data; | |||||
| }; | }; | ||||
| } /* namespace lol */ | } /* namespace lol */ | ||||
| @@ -197,8 +197,8 @@ void SdlInput::tick(float seconds) | |||||
| //case SDL_TEXTEDITING: //TODO: handle that? | //case SDL_TEXTEDITING: //TODO: handle that? | ||||
| case SDL_TEXTINPUT: | case SDL_TEXTINPUT: | ||||
| keyboard->internal_add_text(event.text.text); | |||||
| break; | |||||
| keyboard->internal_add_text(event.text.text); | |||||
| break; | |||||
| case SDL_MOUSEBUTTONDOWN: | case SDL_MOUSEBUTTONDOWN: | ||||
| case SDL_MOUSEBUTTONUP: | case SDL_MOUSEBUTTONUP: | ||||