]> code.delx.au - virtualtones/blob - stringinstrument.cpp
9d0a9f918a0f08841b8529e71150473c15e9535b
[virtualtones] / stringinstrument.cpp
1 // stringinstrument.cpp - A stringed instrument simulator
2 // Written by James Bunton <james@delx.net.au>
3 // Licensed under the GPL, see COPYING.txt for more details
4
5
6 #include "stringinstrument.h"
7
8 using namespace Qt;
9
10 // Images from http://www.asinari.it/bassoeng.htm
11
12 ViolinInstrument::ViolinInstrument(QWidget *parent)
13 : StringInstrument(parent)
14 {
15 noteStart = 48;
16
17 background.load("violin.png");
18
19 setNotes(stringnote);
20 }
21
22 void ViolinInstrument::setNotes(int array[4])
23 {
24 array[0] = 28; // 'E' string
25 array[1] = 21; // 'A' string
26 array[2] = 14; // 'D' string
27 array[3] = 7; // 'G' string
28 }
29
30
31
32 ViolaInstrument::ViolaInstrument(QWidget *parent)
33 : StringInstrument(parent)
34 {
35 noteStart = 48;
36
37 background.load("viola.png");
38
39 setNotes(stringnote);
40 }
41
42 void ViolaInstrument::setNotes(int array[4])
43 {
44 array[0] = 21; // 'A' string below middle c
45 array[1] = 14; // 'D' string
46 array[2] = 7; // 'G' string
47 array[3] = 0; // 'C' string
48 }
49
50
51
52 CelloInstrument::CelloInstrument(QWidget *parent)
53 : StringInstrument(parent)
54 {
55 noteStart = 36;
56
57 background.load("cello.png");
58
59 setNotes(stringnote);
60 }
61
62 void CelloInstrument::setNotes(int array[4])
63 {
64 array[0] = 21; // 'A' string 2 below middle c
65 array[1] = 14; // 'D' string
66 array[2] = 7; // 'G' string
67 array[3] = 0; // 'C' string
68 }
69
70
71
72 ContrabassInstrument::ContrabassInstrument(QWidget *parent)
73 : StringInstrument(parent)
74 {
75 noteStart = 24;
76
77 background.load("contrabass.png");
78
79 setNotes(stringnote);
80 }
81
82 void ContrabassInstrument::setNotes(int array[4])
83 {
84 array[0] = 19; // 'G' string 2 below middle c
85 array[1] = 14; // 'D' string
86 array[2] = 9; // 'A' string
87 array[3] = 4; // 'E' string
88 }
89
90
91
92 /* StringInstrument */
93
94 /* For reference (Violin).. First line, note, second line key presses, third line, MIDI number
95 String E:
96 E F F# G G# A A# B
97 None 1 12 2 23 3 34 4
98 76 77 78 79 80 81 82 83
99
100 String A:
101 A A# B C C# D D# E
102 None TabQ Q W WE E ER R
103 69 70 71 72 73 74 75 76
104
105 String D:
106 D D# E F F# G G# A
107 None CapsA A S SD D DF F
108 62 63 64 65 66 67 68 69
109
110 String G:
111 G G# A A# B C C# D
112 None ShiftZ Z ZX X C CV V
113 55 56 57 58 59 60 61 62
114 */
115
116
117 StringInstrument::StringInstrument(QWidget *parent)
118 : Instrument(parent)
119 {
120 // Set all the keys to false
121 memset(down, 0, sizeof(down));
122 memset(downFudge, 0, sizeof(downFudge));
123 memset(bow, 0, sizeof(bow));
124 memset(oldVolume, 0, sizeof(oldVolume));
125 memset(oldNote, 0, sizeof(oldNote));
126 memset(note, 0, sizeof(note));
127 memset(volume, 0, sizeof(volume));
128
129 QTimer::singleShot(50, this, SLOT(emitSounds()));
130 }
131
132 StringInstrument::~StringInstrument()
133 {
134 }
135
136 QString StringInstrument::generateHelp()
137 {
138 QString help;
139 help +=
140
141 "<html>"
142
143 "Setting notes:"
144 "<ul>"
145 "<li>The row '1' '2' '3' '4' is the E string</li>"
146 "<li>The row 'q' 'w' 'e' 'r' is the A string</li>"
147 "<li>The row 'a' 's' 'd' 'f' is the D string</li>"
148 "<li>The row 'z' 'x' 'c' 'v' is the G string</li>"
149 "<li>The further to the right the key is, the higher the note</li>"
150 "<li>Try pressing multiple keys on the same string to get sharp notes. On the A,D,G strings try the Tab, Caps and Shift keys for sharp notes</li>"
151 "</ul>"
152
153 "Bowing:"
154 "<ul>"
155 "<li>Use the keys '7' '8' '9' '0' to bow the E string</li>"
156 "<li>Use the keys 'u' 'i' 'o' 'p' to bow the A string</li>"
157 "<li>Use the keys 'j' 'k' 'l' ';' to bow the D string</li>"
158 "<li>Use the keys 'm' ',' '.' '/' to bow the G string</li>"
159 "<li>The further right the key is, the quieter the note</li>"
160 "</ul>"
161
162 "</html>"
163 ;
164 return help;
165 }
166
167 void StringInstrument::paintEvent(QPaintEvent *) {
168 QPainter paint(this);
169
170 paint.drawPixmap(0, 0, background);
171
172 paint.setPen(Qt::black);
173
174 for(int i = 0; i < 4; i++) {
175 QString text;
176 text += "Playing note: ";
177 text += midi2string(note[i]);
178 text += " - Volume: " + QString::number(volume[i] / 30);
179 paint.drawText(45, (i+1)*20, text);
180 }
181 }
182
183 void StringInstrument::keyPressEvent(QKeyEvent *e)
184 {
185 if(e->isAutoRepeat() == true) {
186 e->ignore();
187 return;
188 }
189
190 switch(e->key()) {
191 /* Fudge keys */
192 // case Key_Tilde: // This is never used
193 // downFudge[0] = true;
194 // break;
195 case Key_Backtab:
196 case Key_Tab:
197 downFudge[1] = true;
198 break;
199 case Key_CapsLock:
200 downFudge[2] = true;
201 break;
202 case Key_Shift:
203 downFudge[3] = true;
204 break;
205
206 /* First string */
207 case Key_1:
208 down[0][0] = true;
209 break;
210 case Key_2:
211 down[0][1] = true;
212 break;
213 case Key_3:
214 down[0][2] = true;
215 break;
216 case Key_4:
217 down[0][3] = true;
218 break;
219
220 /* Second string */
221 case Key_Q:
222 down[1][0] = true;
223 break;
224 case Key_W:
225 down[1][1] = true;
226 break;
227 case Key_E:
228 down[1][2] = true;
229 break;
230 case Key_R:
231 down[1][3] = true;
232 break;
233
234 /* Third string */
235 case Key_A:
236 down[2][0] = true;
237 break;
238 case Key_S:
239 down[2][1] = true;
240 break;
241 case Key_D:
242 down[2][2] = true;
243 break;
244 case Key_F:
245 down[2][3] = true;
246 break;
247
248 /* Fourth string */
249 case Key_Z:
250 down[3][0] = true;
251 break;
252 case Key_X:
253 down[3][1] = true;
254 break;
255 case Key_C:
256 down[3][2] = true;
257 break;
258 case Key_V:
259 down[3][3] = true;
260 break;
261
262 // Bowing on volume 3 (max)
263 case Key_7:
264 bow[0][3] = true;
265 break;
266 case Key_U:
267 bow[1][3] = true;
268 break;
269 case Key_J:
270 bow[2][3] = true;
271 break;
272 case Key_M:
273 bow[3][3] = true;
274 break;
275
276 // Bowing on volume 2
277 case Key_8:
278 bow[0][2] = true;
279 break;
280 case Key_I:
281 bow[1][2] = true;
282 break;
283 case Key_K:
284 bow[2][2] = true;
285 break;
286 case Key_Comma:
287 bow[3][2] = true;
288 break;
289
290 // Bowing on volume 1
291 case Key_9:
292 bow[0][1] = true;
293 break;
294 case Key_O:
295 bow[1][1] = true;
296 break;
297 case Key_L:
298 bow[2][1] = true;
299 break;
300 case Key_Period:
301 bow[3][1] = true;
302 break;
303
304 // Bowing on volume 0 (min)
305 case Key_0:
306 bow[0][0] = true;
307 break;
308 case Key_P:
309 bow[1][0] = true;
310 break;
311 case Key_Semicolon:
312 bow[2][0] = true;
313 break;
314 case Key_Slash:
315 bow[3][0] = true;
316 break;
317
318 case Key_F12:
319 memset(down, 0, sizeof(down));
320 memset(bow, 0, sizeof(bow));
321 break;
322
323 default:
324 e->ignore();
325 return;
326 }
327 e->accept();
328 QTimer::singleShot(50, this, SLOT(emitSounds()));
329 }
330
331 void StringInstrument::keyReleaseEvent(QKeyEvent *e)
332 {
333 if(e->isAutoRepeat() == true) {
334 e->ignore();
335 return;
336 }
337
338 switch(e->key()) {
339 /* Fudge keys */
340 // case Key_Tilde: // This is never used
341 // downFudge[0] = true;
342 // break;
343 case Key_Backtab:
344 case Key_Tab:
345 downFudge[1] = false;
346 break;
347 case Key_CapsLock:
348 downFudge[2] = false;
349 break;
350 case Key_Shift:
351 downFudge[3] = false;
352 break;
353
354 /* First string */
355 case Key_1:
356 down[0][0] = false;
357 break;
358 case Key_2:
359 down[0][1] = false;
360 break;
361 case Key_3:
362 down[0][2] = false;
363 break;
364 case Key_4:
365 down[0][3] = false;
366 break;
367
368 /* Second string */
369 case Key_Q:
370 down[1][0] = false;
371 break;
372 case Key_W:
373 down[1][1] = false;
374 break;
375 case Key_E:
376 down[1][2] = false;
377 break;
378 case Key_R:
379 down[1][3] = false;
380 break;
381
382 /* Third string */
383 case Key_A:
384 down[2][0] = false;
385 break;
386 case Key_S:
387 down[2][1] = false;
388 break;
389 case Key_D:
390 down[2][2] = false;
391 break;
392 case Key_F:
393 down[2][3] = false;
394 break;
395
396 /* Fourth string */
397 case Key_Z:
398 down[3][0] = false;
399 break;
400 case Key_X:
401 down[3][1] = false;
402 break;
403 case Key_C:
404 down[3][2] = false;
405 break;
406 case Key_V:
407 down[3][3] = false;
408 break;
409
410 // Bowing on volume 3 (max)
411 case Key_7:
412 bow[0][3] = false;
413 break;
414 case Key_U:
415 bow[1][3] = false;
416 break;
417 case Key_J:
418 bow[2][3] = false;
419 break;
420 case Key_M:
421 bow[3][3] = false;
422 break;
423
424 // Bowing on volume 2
425 case Key_8:
426 bow[0][2] = false;
427 break;
428 case Key_I:
429 bow[1][2] = false;
430 break;
431 case Key_K:
432 bow[2][2] = false;
433 break;
434 case Key_Comma:
435 bow[3][2] = false;
436 break;
437
438 // Bowing on volume 1
439 case Key_9:
440 bow[0][1] = false;
441 break;
442 case Key_O:
443 bow[1][1] = false;
444 break;
445 case Key_L:
446 bow[2][1] = false;
447 break;
448 case Key_Period:
449 bow[3][1] = false;
450 break;
451
452 // Bowing on volume 0 (min)
453 case Key_0:
454 bow[0][0] = false;
455 break;
456 case Key_P:
457 bow[1][0] = false;
458 break;
459 case Key_Semicolon:
460 bow[2][0] = false;
461 break;
462 case Key_Slash:
463 bow[3][0] = false;
464 break;
465
466 case Key_F12:
467 memset(down, 0, sizeof(down));
468 memset(bow, 0, sizeof(bow));
469 break;
470
471 default:
472 e->ignore();
473 return;
474 }
475 e->accept();
476 QTimer::singleShot(50, this, SLOT(emitSounds()));
477 }
478
479 void StringInstrument::emitSounds()
480 {
481 memcpy(oldVolume, volume, sizeof(oldVolume));
482 memcpy(oldNote, note, sizeof(oldNote));
483
484 memset(volume, 0, sizeof(volume));
485 memcpy(note, stringnote, sizeof(note)); // Fudge for setNotes(note);
486
487 // Need to find the differences in oldVolume, volume and oldNote, note
488 // Then stop changed notes, and start new ones
489
490
491 // Get the volumes, 'i' is the string, 'j' is the volume
492 int i;
493 for(i = 0; i < 4; i++) {
494 for(int j = 0; j < 4; j++) {
495 if(bow[i][j] == true) {
496 volume[i] = (j + 1) * 30;
497 }
498 }
499 if(volume[i] != oldVolume[i]) { // The volume changed, stop that string
500 emit stopNote(oldNote[i] + noteStart);
501 oldNote[i] = -1; // Flag to restart, just in case the pitch didn't change
502 }
503 }
504
505 // Get the notes, 'i' is the string, 'j' is the volume
506 for(i = 0; i < 4; i++) {
507 for(int j = 0; j < 4; j++) {
508 if(down[i][j] == false) {
509 continue;
510 }
511 note[i] = stringnote[i] + 2 * (j + 1);
512 for(int k = stringnote[i]; k <= note[i]; k++) {
513 if(checkSharp(k) == true) {
514 note[i] -= 1;
515 }
516 }
517 if(j > 0 && down[i][j] == true && down[i][j-1] == true) {
518 note[i] -= 1;
519 }
520 else if(j == 0 && downFudge[i] == true) {
521 note[i] -= 1;
522 }
523 }
524 if(note[i] != oldNote[i]) { // The pitch changed, restart the string
525 // Stop the old one
526 if(oldNote[i] != -1) {
527 emit stopNote(oldNote[i] + noteStart);
528 }
529 // Start the new
530 emit playNote(note[i] + noteStart, volume[i], 0);
531 }
532 }
533 repaint();
534 }