Vital
Loading...
Searching...
No Matches
midi_keyboard.cpp
Go to the documentation of this file.
1#include "midi_keyboard.h"
2
3const float MidiKeyboard::kBlackKeyOffsets[kNumBlackKeysPerOctave] = {
4 1.0f - 0.6f * kBlackKeyWidthRatio,
5 2.0f - 0.4f * kBlackKeyWidthRatio,
6 4.0f - 0.7f * kBlackKeyWidthRatio,
7 5.0f - 0.5f * kBlackKeyWidthRatio,
8 6.0f - 0.3f * kBlackKeyWidthRatio,
9};
10
12 true, false, true, false, true, true, false, true, false, true, false, true
13};
14
15namespace {
16 int getBlackKeyOctaveOffset(int black_key_index) {
17 for (int i = 0; i < vital::kNotesPerOctave; ++i) {
19 if (black_key_index == 0)
20 return i;
21
22 black_key_index--;
23 }
24 }
25 return -1;
26 }
27
28 int getWhiteKeyOctaveOffset(int white_key_index) {
29 for (int i = 0; i < vital::kNotesPerOctave; ++i) {
31 if (white_key_index == 0)
32 return i;
33
34 white_key_index--;
35 }
36 }
37 return -1;
38 }
39
40 int getBlackKeyIndexFromOffset(int note_offset) {
41 int black_key_index = 0;
42 for (int i = 0; i < note_offset; ++i) {
44 black_key_index++;
45 }
46 return black_key_index;
47 }
48
49 int getWhiteKeyIndexFromOffset(int note_offset) {
50 int white_key_index = 0;
51 for (int i = 0; i < note_offset; ++i) {
53 white_key_index++;
54 }
55 return white_key_index;
56 }
57}
58
59MidiKeyboard::MidiKeyboard(MidiKeyboardState& state) :
60 OpenGlComponent("keyboard"), state_(state), midi_channel_(1), hover_note_(-1),
61 black_notes_(kNumBlackKeys, Shaders::kRoundedRectangleFragment),
62 white_pressed_notes_(kNumWhiteKeys, Shaders::kRoundedRectangleFragment),
63 black_pressed_notes_(kNumBlackKeys, Shaders::kRoundedRectangleFragment),
64 hover_note_quad_(Shaders::kRoundedRectangleFragment) {
65 black_notes_.setTargetComponent(this);
66 white_pressed_notes_.setTargetComponent(this);
67 black_pressed_notes_.setTargetComponent(this);
68 hover_note_quad_.setTargetComponent(this);
69 hover_note_quad_.setQuad(0, -2.0f, -2.0f, 0.0f, 0.0f);
70
71 int num_children = getNumChildComponents();
72
73 for (int i = 0; i < num_children; ++i) {
74 Component* child = getChildComponent(i);
75 child->setWantsKeyboardFocus(false);
76 }
77}
78
80 float width = getWidth();
81 int height = getHeight();
82 g.setColour(black_key_color_);
83 for (int i = 1; i < kNumWhiteKeys; ++i) {
84 int x = i * width / kNumWhiteKeys;
85 g.fillRect(x, 0, 1, height);
86 }
87}
88
92
94 if (findParentComponentOfClass<SynthGuiInterface>() == nullptr)
95 return;
96
97 key_press_color_ = findColour(Skin::kWidgetPrimary1, true);
98 hover_color_ = findColour(Skin::kWidgetAccent2, true);
99 white_key_color_ = findColour(Skin::kWidgetSecondary1, true);
100 black_key_color_ = findColour(Skin::kWidgetSecondary2, true);
101}
102
105 setColors();
106
107 float width = getWidth();
108 float height = getHeight();
109 float black_key_height = 2.0f * ((int)(height * kBlackKeyHeightRatio)) / height;
110 float black_key_y = 1.0f - black_key_height;
111 float white_key_width = 2.0f / kNumWhiteKeys;
112 float black_key_width = (((int)(kBlackKeyWidthRatio * white_key_width * width / 4.0f)) * 4.0f + 2.0f) / width;
113 float octave_width = kNumWhiteKeysPerOctave * white_key_width;
114
115 for (int i = 0; i < kNumBlackKeys; ++i) {
116 int octave = i / kNumBlackKeysPerOctave;
117 int index = i % kNumBlackKeysPerOctave;
118
119 float x = -1.0f + octave_width * octave + kBlackKeyOffsets[index] * white_key_width;
120 x = ((int)((x + 1.0f) * width / 2.0f)) * 2.0f / width - 1.0f;
121 black_notes_.setQuad(i, x, black_key_y, black_key_width, black_key_height + 0.5f);
122 }
123
124 float widget_rounding = findValue(Skin::kWidgetRoundedCorner);
125 black_notes_.setRounding(widget_rounding);
126 hover_note_quad_.setRounding(widget_rounding);
127 black_pressed_notes_.setRounding(widget_rounding);
128}
129
130int MidiKeyboard::getNoteAtPosition(Point<float> position) {
131 float white_key_position = kNumWhiteKeys * position.x / getWidth();
132 int octave = white_key_position / kNumWhiteKeysPerOctave;
133 float white_key_in_octave = white_key_position - octave * kNumWhiteKeysPerOctave;
134
135 // Check if click might be on a black key area first
136 if (isBlackKeyHeight(position)) {
137 for (int i = 0; i < kNumBlackKeysPerOctave; ++i) {
138 float note_offset = white_key_in_octave - kBlackKeyOffsets[i];
139 if (note_offset <= kBlackKeyWidthRatio && note_offset >= 0.0f) {
140 int note = octave * vital::kNotesPerOctave + getBlackKeyOctaveOffset(i);
141 return std::min(vital::kMidiSize - 1, std::max(note, 0));
142 }
143 }
144 }
145
146 // Otherwise determine which white key
147 int white_key_index = std::min<int>(kNumWhiteKeysPerOctave - 1, white_key_in_octave);
148 int note = octave * vital::kNotesPerOctave + getWhiteKeyOctaveOffset(white_key_index);
149 return std::min(vital::kMidiSize - 1, std::max(note, 0));
150}
151
152float MidiKeyboard::getVelocityForNote(int midi, Point<float> position) {
153 static constexpr float kMinVelocity = 1.0f / (vital::kMidiSize - 1);
154
155 float velocity = position.y / getHeight();
156 if (!isWhiteKey(midi))
157 velocity = position.y / (kBlackKeyHeightRatio * getHeight());
158
159 return std::max(kMinVelocity, std::min(1.0f, velocity));
160}
161
162void MidiKeyboard::render(OpenGlWrapper& open_gl, bool animate) {
164
165 hover_note_quad_.setColor(hover_color_);
166 int hover_note = hover_note_;
167
168 // Draw hover key if any
169 if (hover_note >= 0) {
170 int octave = hover_note / vital::kNotesPerOctave;
171 int note_offset = hover_note - octave * vital::kNotesPerOctave;
172 if (isWhiteKey(hover_note)) {
173 int index = octave * kNumWhiteKeysPerOctave + getWhiteKeyIndexFromOffset(note_offset);
174 setWhiteKeyQuad(&hover_note_quad_, 0, index);
175 hover_note_quad_.render(open_gl, animate);
176 }
177 else {
178 int index = octave * kNumBlackKeysPerOctave + getBlackKeyIndexFromOffset(note_offset);
179 setBlackKeyQuad(&hover_note_quad_, 0, index);
180 }
181 }
182
183 white_pressed_notes_.setColor(key_press_color_);
184 white_pressed_notes_.render(open_gl, animate);
185
186 black_notes_.setColor(black_key_color_);
187 black_notes_.render(open_gl, animate);
188
189 if (hover_note >= 0 && !isWhiteKey(hover_note))
190 hover_note_quad_.render(open_gl, animate);
191
192 black_pressed_notes_.setColor(key_press_color_);
193 black_pressed_notes_.render(open_gl, animate);
194}
195
197 int num_pressed_white_keys = 0;
198 int num_pressed_black_keys = 0;
199 int white_key_index = 0;
200 int black_key_index = 0;
201 for (int i = 0; i < vital::kMidiSize; ++i) {
202 bool white_key = isWhiteKey(i);
203 if (state_.isNoteOnForChannels(0xffff, i)) {
204 if (white_key) {
205 setWhiteKeyQuad(&white_pressed_notes_, num_pressed_white_keys, white_key_index);
206 num_pressed_white_keys++;
207 }
208 else {
209 setBlackKeyQuad(&black_pressed_notes_, num_pressed_black_keys, black_key_index);
210 num_pressed_black_keys++;
211 }
212 }
213
214 if (white_key)
215 white_key_index++;
216 else
217 black_key_index++;
218 }
219
220 white_pressed_notes_.setNumQuads(num_pressed_white_keys);
221 black_pressed_notes_.setNumQuads(num_pressed_black_keys);
222}
223
224void MidiKeyboard::setWhiteKeyQuad(OpenGlMultiQuad* quads, int quad_index, int white_key_index) {
225 float full_width = getWidth();
226 int start_x = white_key_index * full_width / kNumWhiteKeys + 1;
227 int end_x = (white_key_index + 1) * full_width / kNumWhiteKeys;
228 float x = 2.0f * start_x / full_width - 1.0f;
229 float width = 2.0f * (end_x - start_x) / full_width;
230
231 quads->setQuad(quad_index, x, -2.0f, width, 4.0f);
232}
233
234void MidiKeyboard::setBlackKeyQuad(OpenGlMultiQuad* quads, int quad_index, int black_key_index) {
235 float x = black_notes_.getQuadX(black_key_index);
236 float y = black_notes_.getQuadY(black_key_index);
237 float width = black_notes_.getQuadWidth(black_key_index);
238 float height = black_notes_.getQuadHeight(black_key_index);
239 float border = 2.0f / getWidth();
240 float y_adjust = 2.0f / getHeight();
241
242 quads->setQuad(quad_index, x + border, y + y_adjust, width - 2.0f * border, height);
243}
static const float kBlackKeyOffsets[]
Horizontal offsets for black keys relative to white keys, per octave.
Definition midi_keyboard.h:3
void parentHierarchyChanged() override
Called when the parent hierarchy changes.
Definition midi_keyboard.cpp:89
float getVelocityForNote(int midi, Point< float > position)
Calculates the note velocity based on vertical mouse click position.
Definition midi_keyboard.cpp:152
static constexpr int kNumBlackKeysPerOctave
Number of black keys per octave.
Definition midi_keyboard.h:41
static const bool kWhiteKeys[]
Array indicating which notes (semitones) in an octave are white keys.
Definition midi_keyboard.h:11
int getNoteAtPosition(Point< float > position)
Determines the MIDI note at a given mouse position.
Definition midi_keyboard.cpp:130
static force_inline bool isWhiteKey(int midi)
Determines if a given MIDI note number corresponds to a white key.
Definition midi_keyboard.h:53
void paintBackground(Graphics &g) override
Paints any background elements of the keyboard, typically key boundaries.
Definition midi_keyboard.cpp:79
void setPressedKeyPositions()
Updates which keys are displayed as pressed according to the current MidiKeyboardState.
Definition midi_keyboard.cpp:196
static constexpr int kNumWhiteKeys
Total number of white keys across the entire MIDI range.
Definition midi_keyboard.h:35
void setColors()
Updates the color scheme of the keys.
Definition midi_keyboard.cpp:93
void resized() override
Called when the component is resized.
Definition midi_keyboard.cpp:103
static constexpr int kNumWhiteKeysPerOctave
Number of white keys per octave.
Definition midi_keyboard.h:37
bool isBlackKeyHeight(Point< float > position)
Checks if a given position falls within the vertical range of a black key.
Definition midi_keyboard.h:95
void render(OpenGlWrapper &open_gl, bool animate) override
Renders the keyboard and its keys using OpenGL.
Definition midi_keyboard.cpp:162
static constexpr float kBlackKeyWidthRatio
Horizontal ratio of the black key width relative to white keys.
Definition midi_keyboard.h:46
static constexpr float kBlackKeyHeightRatio
Ratio of keyboard height covered by black keys.
Definition midi_keyboard.h:44
static constexpr int kNumBlackKeys
Total number of black keys across the entire MIDI range.
Definition midi_keyboard.h:39
MidiKeyboard(MidiKeyboardState &state)
Constructs the MidiKeyboard component.
Definition midi_keyboard.cpp:59
A base component class that integrates JUCE's Component with OpenGL rendering.
Definition open_gl_component.h:20
virtual void resized() override
Called when the component is resized.
Definition open_gl_component.cpp:121
float findValue(Skin::ValueId value_id)
Finds a float value from the skin associated with this component's parent.
Definition open_gl_component.cpp:173
A component for rendering multiple quads using OpenGL, with customizable colors, rounding,...
Definition open_gl_multi_quad.h:16
void setQuad(int i, float x, float y, float w, float h)
Sets the position and size of a quad in normalized device space.
Definition open_gl_multi_quad.h:313
force_inline float getQuadHeight(int i) const
Gets the height of the specified quad.
Definition open_gl_multi_quad.h:199
void setTargetComponent(Component *target_component)
Sets a target component to help position the quads.
Definition open_gl_multi_quad.h:358
void setNumQuads(int num_quads)
Sets how many quads will actually be drawn (up to max_quads).
Definition open_gl_multi_quad.h:92
virtual void render(OpenGlWrapper &open_gl, bool animate) override
Renders the quads using OpenGL.
Definition open_gl_multi_quad.cpp:92
force_inline void setColor(Colour color)
Sets the base color for the quads.
Definition open_gl_multi_quad.h:102
force_inline float getQuadWidth(int i) const
Gets the width of the specified quad.
Definition open_gl_multi_quad.h:191
void setRounding(float rounding)
Sets the rounding radius of the quads.
Definition open_gl_multi_quad.h:347
force_inline float getQuadY(int i) const
Gets the y-position of a specified quad.
Definition open_gl_multi_quad.h:183
force_inline float getQuadX(int i) const
Gets the x-position of a specified quad.
Definition open_gl_multi_quad.h:175
Manages and provides access to vertex and fragment shaders used by the OpenGL rendering pipeline.
Definition shaders.h:19
@ kWidgetRoundedCorner
Definition skin.h:104
@ kWidgetPrimary1
Definition skin.h:165
@ kWidgetSecondary1
Definition skin.h:168
@ kWidgetSecondary2
Definition skin.h:169
@ kWidgetAccent2
Definition skin.h:172
constexpr int kNotesPerOctave
Number of semitones per octave.
Definition common.h:51
constexpr int kMidiSize
MIDI note count (0-127).
Definition common.h:44
A helper struct containing references to OpenGL context, shaders, and display scale.
Definition shaders.h:174