Serenity Operating System
at hosted 317 lines 8.3 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2019-2020, William McPherson <willmcpherson2@gmail.com> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright notice, this 10 * list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include "KeysWidget.h" 29#include "AudioEngine.h" 30#include <LibGUI/Painter.h> 31 32KeysWidget::KeysWidget(AudioEngine& audio_engine) 33 : m_audio_engine(audio_engine) 34{ 35 set_fill_with_background_color(true); 36} 37 38KeysWidget::~KeysWidget() 39{ 40} 41 42int KeysWidget::mouse_note() const 43{ 44 if (m_mouse_down && m_mouse_note + m_audio_engine.octave_base() < note_count) 45 return m_mouse_note; // Can be -1. 46 else 47 return -1; 48} 49 50void KeysWidget::set_key(int key, Switch switch_key) 51{ 52 if (key == -1 || key + m_audio_engine.octave_base() >= note_count) 53 return; 54 55 if (switch_key == On) { 56 ++m_key_on[key]; 57 } else { 58 if (m_key_on[key] >= 1) 59 --m_key_on[key]; 60 } 61 ASSERT(m_key_on[key] <= 2); 62 63 m_audio_engine.set_note_current_octave(key, switch_key); 64} 65 66int KeysWidget::key_code_to_key(int key_code) const 67{ 68 switch (key_code) { 69 case Key_A: 70 return 0; 71 case Key_W: 72 return 1; 73 case Key_S: 74 return 2; 75 case Key_E: 76 return 3; 77 case Key_D: 78 return 4; 79 case Key_F: 80 return 5; 81 case Key_T: 82 return 6; 83 case Key_G: 84 return 7; 85 case Key_Y: 86 return 8; 87 case Key_H: 88 return 9; 89 case Key_U: 90 return 10; 91 case Key_J: 92 return 11; 93 case Key_K: 94 return 12; 95 case Key_O: 96 return 13; 97 case Key_L: 98 return 14; 99 case Key_P: 100 return 15; 101 case Key_Semicolon: 102 return 16; 103 case Key_Apostrophe: 104 return 17; 105 case Key_RightBracket: 106 return 18; 107 case Key_Return: 108 return 19; 109 default: 110 return -1; 111 } 112} 113 114constexpr int white_key_width = 24; 115constexpr int black_key_width = 16; 116constexpr int black_key_x_offset = black_key_width / 2; 117constexpr int black_key_height = 60; 118 119constexpr char white_key_labels[] = { 120 'A', 121 'S', 122 'D', 123 'F', 124 'G', 125 'H', 126 'J', 127 'K', 128 'L', 129 ';', 130 '\'', 131 'r', 132}; 133constexpr int white_key_labels_count = sizeof(white_key_labels) / sizeof(char); 134 135constexpr char black_key_labels[] = { 136 'W', 137 'E', 138 'T', 139 'Y', 140 'U', 141 'O', 142 'P', 143 ']', 144}; 145constexpr int black_key_labels_count = sizeof(black_key_labels) / sizeof(char); 146 147constexpr int black_key_offsets[] = { 148 white_key_width, 149 white_key_width * 2, 150 white_key_width, 151 white_key_width, 152 white_key_width * 2, 153}; 154 155constexpr int white_key_note_accumulator[] = { 156 2, 157 2, 158 1, 159 2, 160 2, 161 2, 162 1, 163}; 164 165constexpr int black_key_note_accumulator[] = { 166 2, 167 3, 168 2, 169 2, 170 3, 171}; 172 173void KeysWidget::paint_event(GUI::PaintEvent& event) 174{ 175 GUI::Painter painter(*this); 176 painter.translate(frame_thickness(), frame_thickness()); 177 178 int note = 0; 179 int x = 0; 180 int i = 0; 181 for (;;) { 182 Gfx::Rect rect(x, 0, white_key_width, frame_inner_rect().height()); 183 painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::White); 184 painter.draw_rect(rect, Color::Black); 185 if (i < white_key_labels_count) { 186 rect.set_height(rect.height() * 1.5); 187 painter.draw_text(rect, StringView(&white_key_labels[i], 1), Gfx::TextAlignment::Center, Color::Black); 188 } 189 190 note += white_key_note_accumulator[i % white_keys_per_octave]; 191 x += white_key_width; 192 ++i; 193 194 if (note + m_audio_engine.octave_base() >= note_count) 195 break; 196 if (x >= frame_inner_rect().width()) 197 break; 198 } 199 200 note = 1; 201 x = white_key_width - black_key_x_offset; 202 i = 0; 203 for (;;) { 204 Gfx::Rect rect(x, 0, black_key_width, black_key_height); 205 painter.fill_rect(rect, m_key_on[note] ? note_pressed_color : Color::Black); 206 painter.draw_rect(rect, Color::Black); 207 if (i < black_key_labels_count) { 208 rect.set_height(rect.height() * 1.5); 209 painter.draw_text(rect, StringView(&black_key_labels[i], 1), Gfx::TextAlignment::Center, Color::White); 210 } 211 212 note += black_key_note_accumulator[i % black_keys_per_octave]; 213 x += black_key_offsets[i % black_keys_per_octave]; 214 ++i; 215 216 if (note + m_audio_engine.octave_base() >= note_count) 217 break; 218 if (x >= frame_inner_rect().width()) 219 break; 220 } 221 222 GUI::Frame::paint_event(event); 223} 224 225constexpr int notes_per_white_key[] = { 226 1, 227 3, 228 5, 229 6, 230 8, 231 10, 232 12, 233}; 234 235// Keep in mind that in any of these functions a note value can be out of 236// bounds. Bounds checking is done in set_key(). 237 238static inline int note_from_white_keys(int white_keys) 239{ 240 int octaves = white_keys / white_keys_per_octave; 241 int remainder = white_keys % white_keys_per_octave; 242 int notes_from_octaves = octaves * notes_per_octave; 243 int notes_from_remainder = notes_per_white_key[remainder]; 244 int note = (notes_from_octaves + notes_from_remainder) - 1; 245 return note; 246} 247 248int KeysWidget::note_for_event_position(const Gfx::Point& a_point) const 249{ 250 if (!frame_inner_rect().contains(a_point)) 251 return -1; 252 253 auto point = a_point; 254 point.move_by(-frame_thickness(), -frame_thickness()); 255 256 int white_keys = point.x() / white_key_width; 257 int note = note_from_white_keys(white_keys); 258 259 bool black_key_on_left = note != 0 && key_pattern[(note - 1) % notes_per_octave] == Black; 260 if (black_key_on_left) { 261 int black_key_x = (white_keys * white_key_width) - black_key_x_offset; 262 Gfx::Rect black_key(black_key_x, 0, black_key_width, black_key_height); 263 if (black_key.contains(point)) 264 return note - 1; 265 } 266 267 bool black_key_on_right = key_pattern[(note + 1) % notes_per_octave] == Black; 268 if (black_key_on_right) { 269 int black_key_x = ((white_keys + 1) * white_key_width) - black_key_x_offset; 270 Gfx::Rect black_key(black_key_x, 0, black_key_width, black_key_height); 271 if (black_key.contains(point)) 272 return note + 1; 273 } 274 275 return note; 276} 277 278void KeysWidget::mousedown_event(GUI::MouseEvent& event) 279{ 280 if (event.button() != GUI::MouseButton::Left) 281 return; 282 283 m_mouse_down = true; 284 285 m_mouse_note = note_for_event_position(event.position()); 286 287 set_key(m_mouse_note, On); 288 update(); 289} 290 291void KeysWidget::mouseup_event(GUI::MouseEvent& event) 292{ 293 if (event.button() != GUI::MouseButton::Left) 294 return; 295 296 m_mouse_down = false; 297 298 set_key(m_mouse_note, Off); 299 update(); 300} 301 302void KeysWidget::mousemove_event(GUI::MouseEvent& event) 303{ 304 if (!m_mouse_down) 305 return; 306 307 int new_mouse_note = note_for_event_position(event.position()); 308 309 if (m_mouse_note == new_mouse_note) 310 return; 311 312 set_key(m_mouse_note, Off); 313 set_key(new_mouse_note, On); 314 update(); 315 316 m_mouse_note = new_mouse_note; 317}