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));
|
||||
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()
|
||||
|
@ -18,16 +20,44 @@ void PianoWidget::paint_event(GPaintEvent& event)
|
|||
{
|
||||
GPainter painter(*this);
|
||||
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)
|
||||
{
|
||||
size_t sample_count = len / sizeof(Sample);
|
||||
m_sample_count = len / sizeof(Sample);
|
||||
memset(stream, 0, len);
|
||||
|
||||
Sample* sst = (Sample*)stream;
|
||||
for (size_t i = 0; i < sample_count; ++i) {
|
||||
auto* sst = (Sample*)stream;
|
||||
for (int i = 0; i < m_sample_count; ++i) {
|
||||
static const double VOLUME = 3000;
|
||||
for (size_t n = 0; n < (sizeof(m_note_on) / sizeof(bool)); ++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 const int delay_length_in_frames = 50;
|
||||
|
||||
//assert((int)sample_count == m_width);
|
||||
|
||||
if (m_delay_enabled) {
|
||||
if (delay_frames.size() >= delay_length_in_frames) {
|
||||
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].right += to_blend[i].right * 0.333333;
|
||||
}
|
||||
delete[] to_blend;
|
||||
}
|
||||
Sample* frame = new Sample[sample_count];
|
||||
memcpy(frame, sst, sample_count * sizeof(Sample));
|
||||
Sample* frame = new Sample[m_sample_count];
|
||||
memcpy(frame, sst, m_sample_count * sizeof(Sample));
|
||||
|
||||
delay_frames.enqueue(frame);
|
||||
}
|
||||
|
||||
GPainter painter(*m_bitmap);
|
||||
painter.fill_rect(m_bitmap->rect(), Color::Black);
|
||||
|
||||
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);
|
||||
ASSERT(len <= 2048 * (int)sizeof(Sample));
|
||||
memcpy(m_back_buffer, (Sample*)stream, len);
|
||||
swap(m_front_buffer, m_back_buffer);
|
||||
}
|
||||
|
||||
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_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();
|
||||
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);
|
||||
|
||||
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_saw(size_t);
|
||||
double w_square(size_t);
|
||||
|
@ -30,6 +32,10 @@ private:
|
|||
void update_keys();
|
||||
int octave_base() const;
|
||||
|
||||
int m_sample_count { 0 };
|
||||
Sample* m_front_buffer { nullptr };
|
||||
Sample* m_back_buffer { nullptr };
|
||||
|
||||
RefPtr<GraphicsBitmap> m_bitmap;
|
||||
|
||||
#define note_count sizeof(note_frequency) / sizeof(double)
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
#include "Music.h"
|
||||
#include "PianoWidget.h"
|
||||
#include <LibCore/CFile.h>
|
||||
#include <LibCore/CNotifier.h>
|
||||
#include <LibGUI/GApplication.h>
|
||||
#include <LibGUI/GEventLoop.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
|
||||
static int s_pipefds[2];
|
||||
|
||||
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);
|
||||
|
||||
pipe(s_pipefds);
|
||||
|
||||
auto* window = new GWindow;
|
||||
window->set_title("Piano");
|
||||
window->set_rect(100, 100, 512, 512);
|
||||
|
@ -24,12 +23,30 @@ int main(int argc, char** argv)
|
|||
|
||||
window->show();
|
||||
|
||||
CNotifier notifier(s_pipefds[0], CNotifier::Read);
|
||||
notifier.on_ready_to_read = [&] {
|
||||
char buffer[32];
|
||||
read(s_pipefds[1], buffer, sizeof(buffer));
|
||||
piano_widget->update();
|
||||
};
|
||||
|
||||
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 (;;) {
|
||||
GEventLoop::current().pump(GEventLoop::WaitMode::PollForEvents);
|
||||
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 0;
|
||||
return app.exec();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue