1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 03:47:35 +00:00

Piano: Add piano roll

m_roll_notes[] is an array of 2 bools, pressed and playing. A roll note
is highlighted if it is pressed or in the currently playing column, but
the note is only actually played if the roll note is both pressed and
playing.

We change columns every m_tick which is currently 10 frames. The delay
is synchronised with this. change_roll_column() tracks the previous
column and current column to be played. Each roll note in the previous
column is set to "not playing" and the underlying audio note is turned
off if it was pressed. For the current column, each roll note is set to
playing and the underlying audio note is turned on if pressed.
This commit is contained in:
William McPherson 2019-12-23 20:46:15 +11:00 committed by Andreas Kling
parent ebaadda6bd
commit 7c8bbea995
2 changed files with 116 additions and 6 deletions

View file

@ -42,8 +42,8 @@ void PianoWidget::paint_event(GPaintEvent& event)
for (int x = 0; x < m_sample_count; ++x) { for (int x = 0; x < m_sample_count; ++x) {
double val = samples[x].left; double val = samples[x].left;
val /= 32768; val /= 32768;
val *= m_height * 2; val *= m_height;
int y = (m_height / 2) + val; int y = ((m_height / 8) - 8) + val;
if (x == 0) if (x == 0)
painter.set_pixel({ x, y }, wave_color); painter.set_pixel({ x, y }, wave_color);
else else
@ -54,10 +54,16 @@ void PianoWidget::paint_event(GPaintEvent& event)
render_piano(painter); render_piano(painter);
render_knobs(painter); render_knobs(painter);
render_roll(painter);
} }
void PianoWidget::fill_audio_buffer(uint8_t* stream, int len) void PianoWidget::fill_audio_buffer(uint8_t* stream, int len)
{ {
if (++m_time == m_tick) {
m_time = 0;
change_roll_column();
}
m_sample_count = len / sizeof(Sample); m_sample_count = len / sizeof(Sample);
memset(stream, 0, len); memset(stream, 0, len);
@ -92,7 +98,7 @@ 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 = m_tick * 4;
if (m_delay_enabled) { if (m_delay_enabled) {
if (delay_frames.size() >= delay_length_in_frames) { if (delay_frames.size() >= delay_length_in_frames) {
@ -281,12 +287,16 @@ void PianoWidget::mousedown_event(GMouseEvent& event)
m_mouse_pressed = true; m_mouse_pressed = true;
m_piano_key_under_mouse = find_key_for_relative_position(event.x() - x(), event.y() - y()); m_piano_key_under_mouse = find_key_for_relative_position(event.x() - x(), event.y() - y());
if (m_piano_key_under_mouse) {
if (!m_piano_key_under_mouse)
return;
note(m_piano_key_under_mouse, On); note(m_piano_key_under_mouse, On);
update(); update();
return;
}
RollNote* roll_note_under_mouse = find_roll_note_for_relative_position(event.x() - x(), event.y() - y());
if (roll_note_under_mouse)
roll_note_under_mouse->pressed = !roll_note_under_mouse->pressed;
update();
} }
void PianoWidget::mouseup_event(GMouseEvent&) void PianoWidget::mouseup_event(GMouseEvent&)
@ -459,6 +469,90 @@ void PianoWidget::render_knobs(GPainter& painter)
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));
} }
static int roll_columns = 32;
static int roll_rows = 20;
static int roll_note_size = 512 / roll_columns;
static int roll_height = roll_note_size * roll_rows;
static int roll_y = 512 - white_key_height - roll_height - 16;
Rect PianoWidget::define_roll_note_rect(int column, int row) const
{
Rect rect;
rect.set_width(roll_note_size);
rect.set_height(roll_note_size);
rect.set_x(column * roll_note_size);
rect.set_y(roll_y + (row * roll_note_size));
return rect;
}
PianoWidget::RollNote* PianoWidget::find_roll_note_for_relative_position(int x, int y)
{
for (int row = 0; row < roll_rows; ++row) {
for (int column = 0; column < roll_columns; ++column) {
auto rect = define_roll_note_rect(column, row);
if (rect.contains(x, y))
return &m_roll_notes[row][column];
}
}
return nullptr;
}
void PianoWidget::render_roll_note(GPainter& painter, int column, int row, PianoKey key)
{
Color color;
auto roll_note = m_roll_notes[row][column];
if (roll_note.pressed) {
if (roll_note.playing)
color = Color(24, 24, 255);
else
color = Color(64, 64, 255);
} else {
if (roll_note.playing)
color = Color(104, 104, 255);
else
color = is_white(key) ? Color::White : Color::MidGray;
}
auto rect = define_roll_note_rect(column, row);
painter.fill_rect(rect, color);
painter.draw_rect(rect, Color::Black);
}
void PianoWidget::render_roll(GPainter& painter)
{
for (int row = 0; row < roll_rows; ++row) {
PianoKey key = (PianoKey)(roll_rows - row);
for (int column = 0; column < roll_columns; ++column)
render_roll_note(painter, column, row, key);
}
}
void PianoWidget::change_roll_column()
{
static int current_column = 0;
static int previous_column = roll_columns - 1;
for (int row = 0; row < roll_rows; ++row) {
m_roll_notes[row][previous_column].playing = false;
if (m_roll_notes[row][previous_column].pressed)
note((PianoKey)(roll_rows - row), Off);
m_roll_notes[row][current_column].playing = true;
if (m_roll_notes[row][current_column].pressed)
note((PianoKey)(roll_rows - row), On);
}
if (++current_column == roll_columns)
current_column = 0;
if (++previous_column == roll_columns)
previous_column = 0;
update();
}
void PianoWidget::custom_event(CCustomEvent&) void PianoWidget::custom_event(CCustomEvent&)
{ {
update(); update();

View file

@ -28,13 +28,24 @@ private:
double w_triangle(size_t); double w_triangle(size_t);
double w_noise(); double w_noise();
struct RollNote {
bool pressed;
bool playing;
};
Rect define_piano_key_rect(int index, PianoKey) const; Rect define_piano_key_rect(int index, PianoKey) const;
PianoKey find_key_for_relative_position(int x, int y) const; PianoKey find_key_for_relative_position(int x, int y) const;
Rect define_roll_note_rect(int column, int row) const;
RollNote* find_roll_note_for_relative_position(int x, int y);
void render_piano_key(GPainter&, int index, PianoKey, const StringView&); void render_piano_key(GPainter&, int index, PianoKey, const StringView&);
void render_piano(GPainter&); void render_piano(GPainter&);
void render_knobs(GPainter&); void render_knobs(GPainter&);
void render_knob(GPainter&, const Rect&, bool state, const StringView&); void render_knob(GPainter&, const Rect&, bool state, const StringView&);
void render_roll_note(GPainter&, int column, int row, PianoKey);
void render_roll(GPainter&);
void change_roll_column();
enum SwitchNote { enum SwitchNote {
Off, Off,
@ -73,4 +84,9 @@ private:
PianoKey m_piano_key_under_mouse { K_None }; PianoKey m_piano_key_under_mouse { K_None };
bool m_mouse_pressed { false }; bool m_mouse_pressed { false };
RollNote m_roll_notes[20][32] { { false, false } };
int m_time { 0 };
int m_tick { 10 };
}; };