diff options
author | kleines Filmröllchen <malu.bertsch@gmail.com> | 2021-07-05 14:48:59 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-07-05 19:33:55 +0200 |
commit | 9f1f3c6f3763c52376837085c6b27a0f280e80c0 (patch) | |
tree | 8665678c424e7133782011b114d323dbef683355 /Userland/Applications | |
parent | 9d00db618da3d10abc93ade84774be32170f232c (diff) | |
download | serenity-9f1f3c6f3763c52376837085c6b27a0f280e80c0.zip |
Piano: Use AudioServer instead of /dev/audio for audio
Piano is an old application that predates AudioServer. For this reason,
it was architected to directly talk to the soundcard via the /dev/audio
device. This caused multiple problems including simultaneous playback
issues, no ability to change volume/mute for Piano and more.
This change moves Piano to use AudioServer like any well-behaved audio
application :^) The track processing and IPC communication is moved to
the main thread because IPC doesn't like multi-threading. For this, the
new AudioPlayerLoop class is utilized that should evolve into the
DSP->AudioServer interface in the future.
Because Piano's CPU utilization has gotten so low (about 3-6%), the UI
update loop is switched back to render at exactly 60fps.
This is an important commit on the road to #6528.
Diffstat (limited to 'Userland/Applications')
-rw-r--r-- | Userland/Applications/Piano/Music.h | 2 | ||||
-rw-r--r-- | Userland/Applications/Piano/main.cpp | 98 |
2 files changed, 67 insertions, 33 deletions
diff --git a/Userland/Applications/Piano/Music.h b/Userland/Applications/Piano/Music.h index b80f4b759e..55dc329704 100644 --- a/Userland/Applications/Piano/Music.h +++ b/Userland/Applications/Piano/Music.h @@ -23,7 +23,7 @@ struct Sample { i16 right; }; -constexpr int sample_count = 1024; +constexpr int sample_count = 1 << 9; constexpr int buffer_size = sample_count * sizeof(Sample); diff --git a/Userland/Applications/Piano/main.cpp b/Userland/Applications/Piano/main.cpp index f31540579a..b9e0608f68 100644 --- a/Userland/Applications/Piano/main.cpp +++ b/Userland/Applications/Piano/main.cpp @@ -9,10 +9,13 @@ #include "MainWidget.h" #include "TrackManager.h" #include <AK/Array.h> +#include <AK/Queue.h> +#include <LibAudio/Buffer.h> #include <LibAudio/ClientConnection.h> #include <LibAudio/WavWriter.h> #include <LibCore/EventLoop.h> #include <LibCore/File.h> +#include <LibCore/Object.h> #include <LibGUI/Action.h> #include <LibGUI/Application.h> #include <LibGUI/FilePicker.h> @@ -23,6 +26,65 @@ #include <LibGfx/Bitmap.h> #include <LibThreading/Thread.h> +// Converts Piano-internal data to an Audio::Buffer that AudioServer receives +static NonnullRefPtr<Audio::Buffer> music_samples_to_buffer(Array<Sample, sample_count> samples) +{ + Vector<Audio::Frame, sample_count> frames; + frames.ensure_capacity(sample_count); + for (auto sample : samples) { + Audio::Frame frame = { sample.left / (double)NumericLimits<i16>::max(), sample.right / (double)NumericLimits<i16>::max() }; + frames.unchecked_append(frame); + } + return Audio::Buffer::create_with_samples(frames); +} + +// Wrapper class accepting custom events to advance the track playing and forward audio data to the system. +// This does not run on a separate thread, preventing IPC multithreading madness. +class AudioPlayerLoop : public Core::Object { + C_OBJECT(AudioPlayerLoop) +public: + AudioPlayerLoop(TrackManager& track_manager, bool& need_to_write_wav, Audio::WavWriter& wav_writer) + : m_track_manager(track_manager) + , m_need_to_write_wav(need_to_write_wav) + , m_wav_writer(wav_writer) + { + m_audio_client = Audio::ClientConnection::construct(); + m_audio_client->on_finish_playing_buffer = [this](int buffer_id) { + (void)buffer_id; + enqueue_audio(); + }; + } + + void enqueue_audio() + { + m_track_manager.fill_buffer(m_buffer); + NonnullRefPtr<Audio::Buffer> audio_buffer = music_samples_to_buffer(m_buffer); + m_audio_client->async_enqueue(audio_buffer); + + // FIXME: This should be done somewhere else. + if (m_need_to_write_wav) { + m_need_to_write_wav = false; + m_track_manager.reset(); + m_track_manager.set_should_loop(false); + do { + m_track_manager.fill_buffer(m_buffer); + m_wav_writer.write_samples(reinterpret_cast<u8*>(m_buffer.data()), buffer_size); + } while (m_track_manager.time()); + m_track_manager.reset(); + m_track_manager.set_should_loop(true); + m_wav_writer.finalize(); + } + } + +private: + TrackManager& m_track_manager; + Array<Sample, sample_count> m_buffer; + RefPtr<Audio::ClientConnection> m_audio_client; + + bool& m_need_to_write_wav; + Audio::WavWriter& m_wav_writer; +}; + int main(int argc, char** argv) { if (pledge("stdio thread rpath cpath wpath recvfd sendfd unix", nullptr) < 0) { @@ -32,8 +94,6 @@ int main(int argc, char** argv) auto app = GUI::Application::construct(argc, argv); - auto audio_client = Audio::ClientConnection::construct(); - TrackManager track_manager; auto app_icon = GUI::Icon::default_icon("app-piano"); @@ -48,37 +108,11 @@ int main(int argc, char** argv) Optional<String> save_path; bool need_to_write_wav = false; - auto audio_thread = Threading::Thread::construct([&] { - auto audio = Core::File::construct("/dev/audio"); - if (!audio->open(Core::OpenMode::WriteOnly)) { - dbgln("Can't open audio device: {}", audio->error_string()); - return 1; - } - - Array<Sample, sample_count> buffer; - while (!Core::EventLoop::current().was_exit_requested()) { - track_manager.fill_buffer(buffer); - audio->write(reinterpret_cast<u8*>(buffer.data()), buffer_size); - Core::EventLoop::wake(); - - if (need_to_write_wav) { - need_to_write_wav = false; - track_manager.reset(); - track_manager.set_should_loop(false); - do { - track_manager.fill_buffer(buffer); - wav_writer.write_samples(reinterpret_cast<u8*>(buffer.data()), buffer_size); - } while (track_manager.time()); - track_manager.reset(); - track_manager.set_should_loop(true); - wav_writer.finalize(); - } - } - return 0; - }); - audio_thread->start(); + auto audio_loop = AudioPlayerLoop::construct(track_manager, need_to_write_wav, wav_writer); + audio_loop->enqueue_audio(); + audio_loop->enqueue_audio(); - auto main_widget_updater = Core::Timer::construct(150, [&] { + auto main_widget_updater = Core::Timer::construct(static_cast<int>((1 / 60.0) * 1000), [&] { Core::EventLoop::current().post_event(main_widget, make<Core::CustomEvent>(0)); }); main_widget_updater->start(); |