-
Notifications
You must be signed in to change notification settings - Fork 1
/
GridStrument.h
278 lines (264 loc) · 9.74 KB
/
GridStrument.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// ======================================================================
// WinGridStrument - a Windows touchscreen musical instrument
// Copyright(C) 2020 Roger Allen
//
// This program is free software : you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// ======================================================================
#pragma once
#include <d2d1.h>
#include <mmsystem.h>
#include <algorithm>
#include <map>
#include <iostream>
#include <string>
#include <assert.h>
#include "GridPointer.h"
#include "GridMidi.h"
#include "GridSynth.h"
// ======================================================================
// color theme enums
//
enum class Theme { DEFAULT = 0, TUFTE, MAXIMUM };
const std::wstring ThemeNames[] = { L"LinnStrument", L"Tufte" };
// ======================================================================
// helper class for color themes
//
class GridColorTheme
{
Theme cur_theme_;
public:
GridColorTheme() {
cur_theme_ = Theme::DEFAULT;
}
Theme curTheme() { return cur_theme_; }
void curTheme(Theme t) {
cur_theme_ = static_cast<Theme>(std::clamp((int)t,0,(int)(Theme::MAXIMUM)-1));
}
D2D1_COLOR_F clearColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0xfffdff, 0.0f);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f);
}
}
D2D1_COLOR_F gridLineColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0x363435);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.75f, 0.75f, 0.75f);
}
}
D2D1_COLOR_F cNoteColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0xe9ccae);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.f, 0.f, 0.85f);
}
}
D2D1_COLOR_F noteColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0x363435);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.f, 0.85f, 0.f);
}
}
D2D1_COLOR_F highlightNoteColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0x969495);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.90f, 0.90f, 0.0f);
}
}
D2D1_COLOR_F guitarBackgroundColor() {
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0xa8937f);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.50f, 0.50f, 0.40f);
}
}
D2D1_COLOR_F touchColor() {
// 0.5 alpha for blending with this color
switch (cur_theme_) {
case Theme::TUFTE:
return D2D1::ColorF(0xd6d4d5, 0.5f);
case Theme::DEFAULT:
default:
return D2D1::ColorF(0.80f, 0.0f, 0.80f, 0.50f);
}
}
};
// ======================================================================
// helper class for all the brushes. Have to construct the brushes
// after we get the render target which isn't ready when GridStrument
// is first constructed.
//
struct GridBrushes
{
bool initialized_; // have we constructed the brushes yet?
GridColorTheme color_theme_;
ID2D1SolidColorBrush* grid_line_;
ID2D1SolidColorBrush* c_note_;
ID2D1SolidColorBrush* note_;
ID2D1SolidColorBrush* highlight_;
ID2D1SolidColorBrush* guitar_;
GridBrushes() {
initialized_ = false;
grid_line_ = nullptr;
c_note_ = nullptr;
note_ = nullptr;
highlight_ = nullptr;
guitar_ = nullptr;
}
Theme curTheme() { return color_theme_.curTheme(); }
void curTheme(Theme t) {
color_theme_.curTheme(t);
initialized_ = false;
}
void init(ID2D1HwndRenderTarget* d2dRenderTarget)
{
initialized_ = true;
HRESULT hr = d2dRenderTarget->CreateSolidColorBrush(color_theme_.gridLineColor(), &grid_line_);
assert(SUCCEEDED(hr));
hr = d2dRenderTarget->CreateSolidColorBrush(color_theme_.cNoteColor(), &c_note_);
assert(SUCCEEDED(hr));
hr = d2dRenderTarget->CreateSolidColorBrush(color_theme_.noteColor(), ¬e_);
assert(SUCCEEDED(hr));
hr = d2dRenderTarget->CreateSolidColorBrush(color_theme_.highlightNoteColor(), &highlight_);
assert(SUCCEEDED(hr));
hr = d2dRenderTarget->CreateSolidColorBrush(color_theme_.guitarBackgroundColor(), &guitar_);
assert(SUCCEEDED(hr));
}
};
class GridStrument
{
// preferences that control how GridStrument works
bool pref_guitar_mode_;
int pref_pitch_bend_range_;
int pref_pitch_bend_mask_;
int pref_modulation_controller_;
int pref_midi_channel_min_, pref_midi_channel_max_;
int pref_grid_size_;
bool pref_channel_per_row_mode_;
bool pref_hex_grid_mode_;
bool pref_play_midi_;
bool pref_play_soundfont_;
std::string pref_soundfont_path_;
// all of the current finger touches in one dictionary
std::map<int, GridPointer> grid_pointers_;
D2D1_SIZE_U size_; // size of the window
int num_grids_x_, num_grids_y_; // number of boxes for notes
GridMidi* midi_device_; // the current midi output
int midi_channel_; // next midi channel to use
GridBrushes brushes_; // all of our brushes
// Synth var
GridSynth* grid_synth_;
public:
GridStrument(HMIDIOUT midiDevice);
~GridStrument();
void midiDevice(HMIDIOUT midiDevice);
void resize(D2D1_SIZE_U size);
void draw(ID2D1HwndRenderTarget* d2dRenderTarget, IDWriteTextFormat* dwriteTextFormat);
void pointerDown(int id, RECT rect, POINT point, int pressure);
void pointerUpdate(int id, RECT rect, POINT point, int pressure);
void pointerUp(int id);
// get/set preferences
bool prefGuitarMode() { return pref_guitar_mode_; }
void prefGuitarMode(bool mode) { pref_guitar_mode_ = mode; }
int prefPitchBendRange() { return pref_pitch_bend_range_; }
void prefPitchBendRange(int value) {
pref_pitch_bend_range_ = std::clamp(value, 1, 12);
}
int prefPitchBendMask() { return pref_pitch_bend_mask_; }
void prefPitchBendMask(int value) {
pref_pitch_bend_mask_ = std::clamp(value, 0x2000, 0x3fff);
}
int prefModulationController() { return pref_modulation_controller_; }
void prefModulationController(int value) {
// https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2
pref_modulation_controller_ = std::clamp(value, 1, 119);
}
int prefMidiChannelMin() { return pref_midi_channel_min_; }
int prefMidiChannelMax() { return pref_midi_channel_max_; }
void prefMidiChannelRange(int min, int max) {
pref_midi_channel_min_ = std::clamp(min, 0, 15);
pref_midi_channel_max_ = std::clamp(max, 0, 15);
if (pref_midi_channel_min_ > pref_midi_channel_max_) {
std::wcout << "Forcing Midi Channel min == max == " << pref_midi_channel_max_ << std::endl;
pref_midi_channel_min_ = pref_midi_channel_max_;
}
}
int prefGridSize() { return pref_grid_size_; }
void prefGridSize(int value) {
value = std::clamp(value, 40, 400);
if (value != pref_grid_size_) {
pref_grid_size_ = value;
resize(size_);
}
}
bool prefChannelPerRowMode() { return pref_channel_per_row_mode_; }
void prefChannelPerRowMode(bool mode) { pref_channel_per_row_mode_ = mode; }
Theme prefColorTheme() { return brushes_.curTheme(); }
void prefColorTheme(Theme t) { brushes_.curTheme(t); }
bool prefHexGridMode() { return pref_hex_grid_mode_; }
void prefHexGridMode(bool mode) {
if (mode != pref_hex_grid_mode_) {
pref_hex_grid_mode_ = mode;
resize(size_);
}
}
bool prefPlayMidi() { return pref_play_midi_; }
void prefPlayMidi(bool mode) {
pref_play_midi_ = mode;
midi_device_->playMidi(pref_play_midi_);
}
bool prefPlaySoundfont() { return pref_play_soundfont_; }
void prefPlaySoundfont(bool mode) {
pref_play_soundfont_ = mode;
midi_device_->playSynth(pref_play_soundfont_);
}
std::string prefSoundfontPath() { return pref_soundfont_path_; }
void prefSoundfontPath(std::string s) {
// don't reload the soundfont unnecessarily
if (s != pref_soundfont_path_) {
pref_soundfont_path_ = s;
grid_synth_->loadSoundfont(pref_soundfont_path_);
}
}
private:
void drawPointers(ID2D1HwndRenderTarget* d2dRenderTarget);
void drawDots(ID2D1HwndRenderTarget* d2dRenderTarget);
void drawText(ID2D1HwndRenderTarget* d2dRenderTarget, IDWriteTextFormat* dwriteTextFormat);
void drawGuitar(ID2D1HwndRenderTarget* d2dRenderTarget);
void drawGrid(ID2D1HwndRenderTarget* d2dRenderTarget);
void nextMidiChannel();
int pointToGridColumn(POINT point);
int pointToGridRow(POINT point);
int pointToMidiNote(POINT point);
int gridLocToMidiNote(int x, int y);
int rectToMidiPressure(RECT rect);
int pointChangeToPitchBend(POINT delta);
int pointChangeToMidiModulation(POINT delta);
};