| @@ -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: | |||