summaryrefslogtreecommitdiff
path: root/Userland/Applications
diff options
context:
space:
mode:
authorkleines Filmröllchen <malu.bertsch@gmail.com>2021-07-05 14:48:59 +0200
committerAndreas Kling <kling@serenityos.org>2021-07-05 19:33:55 +0200
commit9f1f3c6f3763c52376837085c6b27a0f280e80c0 (patch)
tree8665678c424e7133782011b114d323dbef683355 /Userland/Applications
parent9d00db618da3d10abc93ade84774be32170f232c (diff)
downloadserenity-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.h2
-rw-r--r--Userland/Applications/Piano/main.cpp98
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();