-
Notifications
You must be signed in to change notification settings - Fork 0
/
Piano.cpp
247 lines (196 loc) · 7.35 KB
/
Piano.cpp
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
//
// Created by babbaj on 5/2/2019.
//
#include "Piano.h"
#include "Keys.h"
#include "ShiftGuard.h"
#include <memory>
#include <vector>
#include <iostream>
#include <regex>
#include <cctype>
#include <algorithm>
#include <chrono>
std::vector<std::string> groups(const std::string& str, const std::regex& regex) { // this is pretty bad
std::sregex_token_iterator non_matching_iter(str.begin(), str.end(), regex, -1);
std::sregex_token_iterator non_matching_end;
std::sregex_iterator matching_iter(str.begin(), str.end(), regex);
std::sregex_iterator matching_end;
std::vector<std::string> out;
const auto run_matching = [&]{
if (matching_iter != matching_end) {
out.push_back(matching_iter->str());
matching_iter++;
}
};
const auto run_not_matching = [&]{
if (non_matching_iter != non_matching_end) {
if (!non_matching_iter->str().empty()) {
out.push_back(*non_matching_iter);
}
non_matching_iter++;
}
};
const auto matchingFirst = [&]{
while(matching_iter != matching_end || non_matching_iter != non_matching_end) {
run_matching();
run_not_matching();
}
};
const auto nonMatchingFirst = [&]{
while(matching_iter != matching_end || non_matching_iter != non_matching_end) {
run_not_matching();
run_matching();
}
};
if (matching_iter != matching_end && matching_iter->position(0) == 0) {
non_matching_iter++; // skip first empty string
// add matching first
matchingFirst();
} else {
// add non matching first
nonMatchingFirst();
}
return out;
}
bool matches(const std::string& regex_token, const std::regex& regex) { // TODO: i shouldnt need this
return std::regex_match(regex_token, regex);
}
const std::vector<Note> parseNoteFile(std::ifstream &stream) {
static const std::regex multi_regex(R"RE(\[[^\[\]]+\])RE");
const std::string str((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
const auto vec = groups(str, multi_regex);
std::vector<Note> out;
for (const auto& token : vec) {
if (matches(token, multi_regex)) {
out.push_back(Note::multiNote(token.begin() + 1, token.end() - 1)); // TODO: filter invalid keys
} else {
for (char c : token) {
if (c == ' ' || PAUSE_CHARS.find(c) != PAUSE_CHARS.end()) {
out.push_back(Note::silentNote(c == ' ' ? NOTE_LENGTH : NOTE_LENGTH * 4));
} else if (VALID_KEYS.find(c) != VALID_KEYS.end()) {
out.push_back(Note::singleNote(c));
} else {
// ignored
}
}
}
}
return out;
}
void sendKey(const Piano &piano, char key) {
SendMessage(piano.hWindowHandle, WM_KEYDOWN, key, 0x1);
Sleep(NO_DELAY);
SendMessage(piano.hWindowHandle, WM_KEYUP, key, 0x1);
}
void playKey(const Piano &piano, char key, std::unique_ptr<ShiftGuard> &shift_ptr) {
if (isBlackKey(key)) {
if (!shift_ptr) shift_ptr = std::make_unique<ShiftGuard>(piano); // ensure we have a shift guard
sendKey(piano, BLACK_KEYS.at(key));
} else {
shift_ptr.reset(); // destroy any existing shift guard
// uppercase letters must be sent
const char upper = (key >= 'a' && key <= 'z') ? key - 32 : key;
sendKey(piano, upper);
}
}
void playNote(const Piano& piano, const Note ¬e, std::unique_ptr<ShiftGuard> &shift_ptr) {
for (const auto key : note.keys) {
playKey(piano, key, shift_ptr);
if (note.type != NoteType::MULTI) break; // dont unnecessarily sleep
Sleep(key == ' ' ? FAST_DELAY : NO_DELAY); // a space in a multinote is a very short delay
}
}
void Piano::loadText(const char* file) noexcept(false) {
std::ifstream stream(file);
stream.exceptions(std::ifstream::failbit | std::ifstream::badbit);
auto notes = parseNoteFile(stream);
stream.close();
this->loaded_song = std::move(notes);
}
void removePercussionNotes(smf::MidiFile& midi) {
for (int i = 0; i < midi.getTrackCount(); i++) {
for (int j = 0; j < midi[i].getEventCount(); j++) {
if (midi[i][j].isNote()) {
int channel = midi[i][j].getChannelNibble();
if (channel == 9) {
midi[i][j].clear();
}
}
}
}
midi.removeEmpties();
}
void Piano::loadMidi(const char* file) {
smf::MidiFile midi;
midi.read(file);
if (!midi.status()) {
std::cerr << "Problem reading MIDI file\n";
return;
}
removePercussionNotes(midi); // should be good enough
midi.doTimeAnalysis();
midi.linkNotePairs();
midi.joinTracks();
this->loaded_song = std::move(midi);
}
void playText(const Piano&, const std::vector<Note>&);
void playMidi(const Piano&, const smf::MidiFile&);
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
void Piano::play() {
if (this->loaded_song.valueless_by_exception()) {
std::cerr << "No loaded song\n";
return;
}
std::visit(overloaded {
[this](const std::vector<Note>& notes) {
playText(*this, notes);
},
[this](const smf::MidiFile& midi) {
playMidi(*this, midi);
}
}, this->loaded_song);
}
void playText(const Piano& piano, const std::vector<Note>& notes) {
{
std::unique_ptr<ShiftGuard> optShift;
for (auto ¬e : notes) {
playNote(piano, note, optShift);
Sleep(note.delay); // usually NOTE_LENGTH
}
}
}
#define duration(a) std::chrono::duration_cast<std::chrono::milliseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()
void playMidi(const Piano& piano, const smf::MidiFile& midi) {
const auto midiTo61 = [](int midiKey) -> int { // may be out of range
return midiKey - 35;
};
const auto& track = midi[0];
std::unique_ptr<ShiftGuard> shift_guard;
for (int eventIdx = 0; eventIdx < track.size(); eventIdx++) {
const auto& event = track[eventIdx];
if (event.isNoteOn()) {
const int keyNum = std::clamp(midiTo61(event.getKeyNumber()), 1, 61);
const char key = KEYNUM_TO_KEY.at(keyNum);
const auto now = timeNow();
playKey(piano, key, shift_guard);
const auto dur = duration(timeNow() - now); // measure how long it takes to send a key input
if (eventIdx < track.size() - 1) { // if not last note
const auto& opt_next = [&]() -> std::optional<smf::MidiEvent> {
for (int i = eventIdx + 1; i < track.size(); i++) {
if (track[i].isNoteOn()) return track[i];
}
return std::nullopt;
}();
if (opt_next) {
const unsigned int timeDiffMillis = (opt_next->seconds - event.seconds) * 1000.0;
// we will count the time it took to send the previous input as sleep time
const auto sleep_time = dur < timeDiffMillis ? timeDiffMillis - dur : 0;
Sleep(sleep_time);
}
}
}
}
}