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