mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:27:43 +00:00
Piano: Use a secondary thread to write the audio stream.
This frees up the main thread to draw the GUI. The secondary thread uses a pipe to trick the main thread's event loop to break out of select() and update() the PianoWidget. :^)
This commit is contained in:
parent
dcfa93e71f
commit
eec9666735
3 changed files with 88 additions and 54 deletions
|
@ -8,6 +8,8 @@ PianoWidget::PianoWidget()
|
||||||
{
|
{
|
||||||
memset(keys, 0, sizeof(keys));
|
memset(keys, 0, sizeof(keys));
|
||||||
m_bitmap = GraphicsBitmap::create(GraphicsBitmap::Format::RGB32, { m_width, m_height });
|
m_bitmap = GraphicsBitmap::create(GraphicsBitmap::Format::RGB32, { m_width, m_height });
|
||||||
|
m_front_buffer = new Sample[2048];
|
||||||
|
m_back_buffer = new Sample[2048];
|
||||||
}
|
}
|
||||||
|
|
||||||
PianoWidget::~PianoWidget()
|
PianoWidget::~PianoWidget()
|
||||||
|
@ -18,16 +20,44 @@ void PianoWidget::paint_event(GPaintEvent& event)
|
||||||
{
|
{
|
||||||
GPainter painter(*this);
|
GPainter painter(*this);
|
||||||
painter.add_clip_rect(event.rect());
|
painter.add_clip_rect(event.rect());
|
||||||
painter.blit({ 0, 0 }, *m_bitmap, m_bitmap->rect());
|
|
||||||
|
painter.fill_rect(event.rect(), Color::Black);
|
||||||
|
|
||||||
|
auto* samples = m_front_buffer;
|
||||||
|
Color wave_color;
|
||||||
|
if (m_wave_type == WaveType::Sine)
|
||||||
|
wave_color = Color(255, 192, 0);
|
||||||
|
else if (m_wave_type == WaveType::Saw)
|
||||||
|
wave_color = Color(240, 100, 128);
|
||||||
|
else if (m_wave_type == WaveType::Square)
|
||||||
|
wave_color = Color(128, 160, 255);
|
||||||
|
|
||||||
|
int prev_x = 0;
|
||||||
|
int prev_y = m_height / 2;
|
||||||
|
for (int x = 0; x < m_sample_count; ++x) {
|
||||||
|
double val = samples[x].left;
|
||||||
|
val /= 32768;
|
||||||
|
val *= m_height * 2;
|
||||||
|
int y = (m_height / 2) + val;
|
||||||
|
if (x == 0)
|
||||||
|
painter.set_pixel({ x, y }, wave_color);
|
||||||
|
else
|
||||||
|
painter.draw_line({ prev_x, prev_y }, { x, y }, wave_color);
|
||||||
|
prev_x = x;
|
||||||
|
prev_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_piano(painter);
|
||||||
|
render_knobs(painter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PianoWidget::fill_audio_buffer(uint8_t* stream, int len)
|
void PianoWidget::fill_audio_buffer(uint8_t* stream, int len)
|
||||||
{
|
{
|
||||||
size_t sample_count = len / sizeof(Sample);
|
m_sample_count = len / sizeof(Sample);
|
||||||
memset(stream, 0, len);
|
memset(stream, 0, len);
|
||||||
|
|
||||||
Sample* sst = (Sample*)stream;
|
auto* sst = (Sample*)stream;
|
||||||
for (size_t i = 0; i < sample_count; ++i) {
|
for (int i = 0; i < m_sample_count; ++i) {
|
||||||
static const double VOLUME = 3000;
|
static const double VOLUME = 3000;
|
||||||
for (size_t n = 0; n < (sizeof(m_note_on) / sizeof(bool)); ++n) {
|
for (size_t n = 0; n < (sizeof(m_note_on) / sizeof(bool)); ++n) {
|
||||||
if (!m_note_on[n])
|
if (!m_note_on[n])
|
||||||
|
@ -58,52 +88,24 @@ void PianoWidget::fill_audio_buffer(uint8_t* stream, int len)
|
||||||
static Queue<Sample*> delay_frames;
|
static Queue<Sample*> delay_frames;
|
||||||
static const int delay_length_in_frames = 50;
|
static const int delay_length_in_frames = 50;
|
||||||
|
|
||||||
//assert((int)sample_count == m_width);
|
|
||||||
|
|
||||||
if (m_delay_enabled) {
|
if (m_delay_enabled) {
|
||||||
if (delay_frames.size() >= delay_length_in_frames) {
|
if (delay_frames.size() >= delay_length_in_frames) {
|
||||||
auto* to_blend = delay_frames.dequeue();
|
auto* to_blend = delay_frames.dequeue();
|
||||||
for (size_t i = 0; i < sample_count; ++i) {
|
for (int i = 0; i < m_sample_count; ++i) {
|
||||||
sst[i].left += to_blend[i].left * 0.333333;
|
sst[i].left += to_blend[i].left * 0.333333;
|
||||||
sst[i].right += to_blend[i].right * 0.333333;
|
sst[i].right += to_blend[i].right * 0.333333;
|
||||||
}
|
}
|
||||||
delete[] to_blend;
|
delete[] to_blend;
|
||||||
}
|
}
|
||||||
Sample* frame = new Sample[sample_count];
|
Sample* frame = new Sample[m_sample_count];
|
||||||
memcpy(frame, sst, sample_count * sizeof(Sample));
|
memcpy(frame, sst, m_sample_count * sizeof(Sample));
|
||||||
|
|
||||||
delay_frames.enqueue(frame);
|
delay_frames.enqueue(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
GPainter painter(*m_bitmap);
|
ASSERT(len <= 2048 * (int)sizeof(Sample));
|
||||||
painter.fill_rect(m_bitmap->rect(), Color::Black);
|
memcpy(m_back_buffer, (Sample*)stream, len);
|
||||||
|
swap(m_front_buffer, m_back_buffer);
|
||||||
Sample* samples = (Sample*)stream;
|
|
||||||
Color wave_color;
|
|
||||||
if (m_wave_type == WaveType::Sine)
|
|
||||||
wave_color = Color(255, 192, 0);
|
|
||||||
else if (m_wave_type == WaveType::Saw)
|
|
||||||
wave_color = Color(240, 100, 128);
|
|
||||||
else if (m_wave_type == WaveType::Square)
|
|
||||||
wave_color = Color(128, 160, 255);
|
|
||||||
|
|
||||||
int prev_x = 0;
|
|
||||||
int prev_y = m_height / 2;
|
|
||||||
for (int x = 0; x < (int)sample_count; ++x) {
|
|
||||||
double val = samples[x].left;
|
|
||||||
val /= 32768;
|
|
||||||
val *= m_height * 2;
|
|
||||||
int y = (m_height / 2) + val;
|
|
||||||
if (x == 0)
|
|
||||||
painter.set_pixel({ x, y }, wave_color);
|
|
||||||
else
|
|
||||||
painter.draw_line({ prev_x, prev_y }, { x, y }, wave_color);
|
|
||||||
prev_x = x;
|
|
||||||
prev_y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_piano(painter);
|
|
||||||
render_knobs(painter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double PianoWidget::w_sine(size_t n)
|
double PianoWidget::w_sine(size_t n)
|
||||||
|
@ -351,3 +353,12 @@ void PianoWidget::render_knobs(GPainter& painter)
|
||||||
painter.draw_rect(wave_knob_rect, Color(r, g, b));
|
painter.draw_rect(wave_knob_rect, Color(r, g, b));
|
||||||
painter.draw_text(wave_knob_rect, wave_name, TextAlignment::Center, Color(r, g, b));
|
painter.draw_text(wave_knob_rect, wave_name, TextAlignment::Center, Color(r, g, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PianoWidget::event(CEvent& event)
|
||||||
|
{
|
||||||
|
if (event.type() == CEvent::Custom) {
|
||||||
|
dbg() << "Piano got custom event!";
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
GWidget::event(event);
|
||||||
|
}
|
||||||
|
|
|
@ -10,13 +10,15 @@ public:
|
||||||
PianoWidget();
|
PianoWidget();
|
||||||
virtual ~PianoWidget() override;
|
virtual ~PianoWidget() override;
|
||||||
|
|
||||||
virtual void paint_event(GPaintEvent&) override;
|
|
||||||
virtual void keydown_event(GKeyEvent&) override;
|
|
||||||
virtual void keyup_event(GKeyEvent&) override;
|
|
||||||
|
|
||||||
void fill_audio_buffer(uint8_t* stream, int len);
|
void fill_audio_buffer(uint8_t* stream, int len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual void paint_event(GPaintEvent&) override;
|
||||||
|
virtual void keydown_event(GKeyEvent&) override;
|
||||||
|
virtual void keyup_event(GKeyEvent&) override;
|
||||||
|
virtual void event(CEvent&) override;
|
||||||
|
|
||||||
double w_sine(size_t);
|
double w_sine(size_t);
|
||||||
double w_saw(size_t);
|
double w_saw(size_t);
|
||||||
double w_square(size_t);
|
double w_square(size_t);
|
||||||
|
@ -30,6 +32,10 @@ private:
|
||||||
void update_keys();
|
void update_keys();
|
||||||
int octave_base() const;
|
int octave_base() const;
|
||||||
|
|
||||||
|
int m_sample_count { 0 };
|
||||||
|
Sample* m_front_buffer { nullptr };
|
||||||
|
Sample* m_back_buffer { nullptr };
|
||||||
|
|
||||||
RefPtr<GraphicsBitmap> m_bitmap;
|
RefPtr<GraphicsBitmap> m_bitmap;
|
||||||
|
|
||||||
#define note_count sizeof(note_frequency) / sizeof(double)
|
#define note_count sizeof(note_frequency) / sizeof(double)
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
#include "Music.h"
|
#include "Music.h"
|
||||||
#include "PianoWidget.h"
|
#include "PianoWidget.h"
|
||||||
#include <LibCore/CFile.h>
|
#include <LibCore/CFile.h>
|
||||||
|
#include <LibCore/CNotifier.h>
|
||||||
#include <LibGUI/GApplication.h>
|
#include <LibGUI/GApplication.h>
|
||||||
#include <LibGUI/GEventLoop.h>
|
#include <LibGUI/GEventLoop.h>
|
||||||
#include <LibGUI/GWindow.h>
|
#include <LibGUI/GWindow.h>
|
||||||
|
|
||||||
|
static int s_pipefds[2];
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
CFile audio("/dev/audio");
|
|
||||||
if (!audio.open(CIODevice::WriteOnly)) {
|
|
||||||
dbgprintf("Can't open audio device: %s", audio.error_string());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
GApplication app(argc, argv);
|
GApplication app(argc, argv);
|
||||||
|
|
||||||
|
pipe(s_pipefds);
|
||||||
|
|
||||||
auto* window = new GWindow;
|
auto* window = new GWindow;
|
||||||
window->set_title("Piano");
|
window->set_title("Piano");
|
||||||
window->set_rect(100, 100, 512, 512);
|
window->set_rect(100, 100, 512, 512);
|
||||||
|
@ -24,12 +23,30 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
window->show();
|
window->show();
|
||||||
|
|
||||||
for (;;) {
|
CNotifier notifier(s_pipefds[0], CNotifier::Read);
|
||||||
GEventLoop::current().pump(GEventLoop::WaitMode::PollForEvents);
|
notifier.on_ready_to_read = [&] {
|
||||||
u8 buffer[4096];
|
char buffer[32];
|
||||||
piano_widget->fill_audio_buffer(buffer, sizeof(buffer));
|
read(s_pipefds[1], buffer, sizeof(buffer));
|
||||||
audio.write(buffer, sizeof(buffer));
|
piano_widget->update();
|
||||||
}
|
};
|
||||||
|
|
||||||
return 0;
|
create_thread([](void* context) -> int {
|
||||||
|
auto* piano_widget = (PianoWidget*)context;
|
||||||
|
|
||||||
|
CFile audio("/dev/audio");
|
||||||
|
if (!audio.open(CIODevice::WriteOnly)) {
|
||||||
|
dbgprintf("Can't open audio device: %s", audio.error_string());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
u8 buffer[4096];
|
||||||
|
piano_widget->fill_audio_buffer(buffer, sizeof(buffer));
|
||||||
|
audio.write(buffer, sizeof(buffer));
|
||||||
|
char ch = '!';
|
||||||
|
write(s_pipefds[0], &ch, 1);
|
||||||
|
}
|
||||||
|
}, piano_widget);
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue