-
Notifications
You must be signed in to change notification settings - Fork 0
/
encoder.cpp
288 lines (257 loc) · 8.36 KB
/
encoder.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
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
278
279
280
281
282
#include "encoder.h"
#include <cstdlib>
#include <ctime>
#include "RtMidi.h"
#include "wiringPi.h"
// Deus in adjutorium meum intende
// Soli Deo Gloria
constexpr unsigned int base_key = 36; // C2
constexpr unsigned int higher_key = 62; // D4
std::ofstream error_file {"/home/pi/error_file",std::ofstream::app};
// security
void write_time() {
/* write current time on error_file
*/
std::time_t result = std::time(nullptr);
error_file << std::asctime(std::localtime(&result));
error_file.flush();
}
void check_valid_pin(const uint_fast8_t pin) {
/* Check that pin exists and can be used
*/
if ((pin > 7 && pin < 21 )|| pin > 28) {
error_file << "Invalid pin: " << pin << "\n";
std::cerr << "Invalid pin: " << pin << "\n";
throw_and_flush();
}
}
void throw_and_flush() {
/* Write date and time,
* flush the error file
* throw
*/
write_time();
error_file.flush();
throw 1;
}
// setup
std::tuple<array12,array3> get_pins() {
/* Get the pins and return arrays containing their numbers
*/
#if defined DEBUG && defined MOCK
return {
{7,0,2, 3,21,22, 23,24,25, 1,4,5 },
{6, 26, 27}
};
#endif
std::ifstream pins_file ("/home/pi/pins");
// check file
if (pins_file.fail()){
error_file << "Invalid file\n";
std::cerr << "Invalid file\n";
throw_and_flush();
}
char pin_nb[3];
array12 lines;
for (int i=0;i<11;++i) {
pins_file.getline(pin_nb,3,' ');
lines[i] = std::atoi(pin_nb);
#ifdef DEBUG
std::cout << i << " " << pin_nb << "\n";
#endif
}
// we need to add the last line
pins_file.getline(pin_nb,3,'\n');
lines[11] = std::atoi(pin_nb);
array3 columns;
for (int i=0;i<3;++i) {
pins_file.getline(pin_nb,3,' ');
columns[i] = std::atoi(pin_nb);
#ifdef DEBUG
std::cout << i << " " << pin_nb << "\n";
#endif
}
#ifdef DEBUG
std::cout << "Pins lines: " ;
for (const auto& elt : lines)
std::cout << static_cast<int>(elt) << " ";
std::cout << "\n";
std::cout << "Pins columns: ";
for (const auto& elt : columns)
std::cout << static_cast<int>(elt) << " ";
std::cout << "\n";
#endif
// check validity of pins
for (const auto& elt : lines)
check_valid_pin(elt);
for (const auto& elt : columns)
check_valid_pin(elt);
// check no duplicate pins
for (const auto& lelt : lines) {
for (const auto& celt : columns) {
if (lelt == celt) {
error_file << "Please check the pins: " << celt << " is used as line and column at the same time\n";
std::cerr << "Please check the pins: " << celt << " is used as line and column at the same time\n";
throw_and_flush();
}
}
}
check_duplicates(lines);
check_duplicates(columns);
return {lines, columns};
}
uint_fast8_t get_channel() {
/* Return the channel selected by user
* in case of error, channel selected is zero
*/
#if defined DEBUG && defined MOCK
return 1;
#endif
std::ifstream channel_file("/home/pi/channel");
uint_fast8_t channel;
channel_file >> channel;
// no need to check if channel is under 15 since channel is unsigned
if (channel_file.fail() || channel > 15) {
error_file << "Channel can not be set correctly from file. Please check that the 'channel' file contains an integer between 0 and 15.\nDefault channel will be used: 0.";
write_time();
return 0;
}
#ifdef DEBUG
std::cout << "Channel selected : " << channel << "\n";
#endif
return channel;
}
void set_output_pins(const array12& lines) {
// set lines as outputs
for (const auto& line : lines)
pinMode(line,OUTPUT);
}
void set_input_pins(const array3& columns) {
/* set lines as inputs
* (although this is the default value,
* we do that by security)
* and set the pull down resistor
* to avoid bizarre results
*/
for (const auto& c : columns) {
pinMode(c,INPUT);
pullUpDnControl(c,PUD_DOWN);
}
}
void light_on () {
/* light on the LED
*/
constexpr int LED_PIN = 29;
pinMode(LED_PIN,OUTPUT);
digitalWrite(LED_PIN,HIGH);
}
status_array get_notes_status_array() {
/* Return the notes status array
*/
// TODO if necessary, make this function constexpr
constexpr int lines{12}, columns{3};
constexpr size_t notes_nb { 27 };
status_array array;
array.reserve(notes_nb);
unsigned int current_key = base_key;
unsigned int sub_base_key = base_key;
for (int i=0;i<lines;++i) {
current_key = sub_base_key;
for (int j=0;j<columns && current_key <= higher_key;++j) {
array.push_back({current_key,false});
current_key += lines;
}
++sub_base_key;
}
#ifdef DEBUG
std::cout << "ID | Key | Value\n";
for (size_t i=0; i < array.size(); ++i) {
const auto&elt = array.at(i);
std::cout << i << " | " << elt.key << " | " << std::boolalpha << elt.status << "\n";
}
#endif
return array;
}
// main loop
void send_message(uint_fast8_t value, uint_fast8_t key) {
/* Send a midi message
* value must be the note on or off followed by the channel
*/
static RtMidiOut midiout;
constexpr int is_on = 0b0001'0000;
// the message is required to be a vector of uchar. It is set to 3 elements with value zero
static std::vector<unsigned char> message (3,0);
static bool check_done { false };
if (! check_done) {
// checks
if (midiout.getPortCount() == 0) {
error_file << "No port available.\n";
throw_and_flush();
}
midiout.openPort(1); // it seems that it's always the good one: port 0 is MIDI Through
check_done = true;
}
message[0] = value;
message[1] = key;
message[2] = (value & is_on) ? 127 : 0;
midiout.sendMessage(&message);
}
void loop( const array12& lines, const array3& columns, const uint_fast8_t channel, status_array& key_array) {
constexpr int NOTE_ON_BASE = 0b1001'0000;
constexpr int NOTE_OFF_BASE = 0b1000'0000;
const unsigned char NOTE_ON = NOTE_ON_BASE + channel;
const unsigned char NOTE_OFF = NOTE_OFF_BASE + channel;
#ifndef LOOP_NB
#define LOOP_NB true
#endif
do {
unsigned int current_key = base_key;
unsigned int sub_base_key = base_key;
for (size_t i=0, idx=0; i<lines.size(); ++i) {
current_key = sub_base_key;
digitalWrite(lines[i],HIGH);
for (size_t j=0; j < columns.size() && current_key <= higher_key; ++j,++idx) {
const bool status = digitalRead(columns[j]);
auto& key {key_array[idx]};
#ifdef DEBUG
#if 1
std::cout << "Test key: " << key.key << " Status: " << std::boolalpha << key.status << "\n";
std::cout << "Current column: " << static_cast<int>(columns[j]) << " Current status: " << std::boolalpha << status << "\n";
#endif
#endif
if (status != key.status) {
key.status = ! key.status;
const auto signal = (status?NOTE_ON:NOTE_OFF);
send_message(signal,key.key);
#ifdef DEBUG
std::cout << "Sent message: " << (signal==NOTE_ON?"Note_On":"Note_Off") << ":"<<key.key<<"\n";
std::cout << "Current column: " << static_cast<int>(columns[j]) << " Current line: " << static_cast<int>(lines[i]);
#endif
}
current_key += lines.size();
}
++sub_base_key;
digitalWrite(lines[i],LOW);
/* This delay is absolutely mandatory,
* even if I don't understand why that works.
* Without it, pressing a key of the first octave
* (and in some cases, in others too)
* results in the key and one of the following
* considered pressed by the program.
* For example, pressing the first C
* sends 36 to the decoder (that's expected)
* but also 37 (C#) and in some cases 38, 39, etc.
* Pressing B sends 47 (that's expected) but also...
* 36! It seems that the line is considered active
* by the program when it's too fast. Only 1 delay is sufficient
* and not noticeable by the user.
* I must add that pullUpDnControl seems to do nothing
*/
delay(1);
}
#ifdef DEBUG
std::cout << std::endl;
#endif
} while (LOOP_NB);
#undef LOOP_NB
}