]> code.delx.au - virtualtones/blob - midiengine.cpp
Initial commit
[virtualtones] / midiengine.cpp
1 // midiengine.cpp - Class to play to a sequencer or write to a MIDI file
2 // Written by James Bunton <james@delx.cjb.net>
3 // Licensed under the GPL, see COPYING.txt for more details
4
5
6 #include "midiengine.h"
7
8
9 /* |---------------------------| */
10 /* | Code for class MidiEngine | */
11 /* |---------------------------| */
12
13
14 bool MidiEngine::initSuccess()
15 {
16 return !error;
17 }
18
19 QString MidiEngine::getError()
20 {
21 return errorMessage;
22 }
23
24 bool MidiEngine::stopNote(int num)
25 {
26 return playNote(num, 0, 0);
27 }
28
29 void MidiEngine::stopAll()
30 {
31 // For all notes that have and ending, end them now
32 for(int i = 0; i <= 127; i++) {
33 if(noteEndings[i] != 0)
34 stopNote(i);
35 }
36 }
37
38 void MidiEngine::timerEvent(QTimerEvent *)
39 {
40 // Increment the timer
41 timer += 10;
42
43 // For all notes that expired after the current time, or on the current time:
44 // send a stop note
45 for(int i = 0; i <= 127; i++) {
46 if(noteEndings[i] <= timer && noteEndings[i] != 0) {
47 playNote(i, 0, 0);
48 }
49 }
50 }
51
52
53
54
55
56 /* |-------------------------| */
57 /* | Code for class MidiReal | */
58 /* |-------------------------| */
59
60
61 /* Linux code */
62
63 #ifdef Q_WS_X11
64
65 #include <linux/soundcard.h>
66 #include <sys/ioctl.h>
67 #include <fcntl.h>
68 #include <unistd.h>
69
70
71 class MidiReal::Private {
72 public:
73 // For setting up the midi output
74 int seqfd;
75 QString seqDevice;
76 int midiDevice;
77 unsigned char outpacket[12];
78 };
79
80
81 MidiReal::MidiReal()
82 {
83 // Use to set the sequencer and device
84 QString dev = "/dev/sequencer";
85 int devicenum = 0;
86
87 d = new Private();
88
89 d->seqfd = -1;
90 d->seqDevice = dev;
91 d->midiDevice = devicenum;
92 error = true;
93 timer = 0;
94
95 // Open the sequencer file
96 d->seqfd = open(d->seqDevice.latin1(), O_WRONLY, 0);
97 if (d->seqfd < 0) {
98 errorMessage = "Cannot open sequencer: " + d->seqDevice;
99 return;
100 }
101
102 // Check if we can access the midi devices
103 int maxMidi = 0;
104 if(ioctl(d->seqfd, SNDCTL_SEQ_NRMIDIS, &maxMidi) != 0) {
105 errorMessage = "Cannot access MIDI devices on soundcard.";
106 return;
107 }
108 if(d->midiDevice >= maxMidi) {
109 errorMessage = "Invalid MIDI device. Valid devices are 0-" + QString::number(maxMidi - 1);
110 return;
111 }
112
113
114 // Good, no errors so far
115 error = false;
116
117
118 // Set all the note endings to zero
119 for(int i = 0; i <= 127; i++) {
120 noteEndings[i] = 0;
121 }
122
123 // Setup the outpacket
124 // The note on command
125 d->outpacket[0] = SEQ_MIDIPUTC;
126 d->outpacket[1] = 0x90;
127 d->outpacket[2] = d->midiDevice;
128 d->outpacket[3] = 0;
129
130 // Specify the note
131 d->outpacket[4] = SEQ_MIDIPUTC;
132 d->outpacket[5] = 0; // note number
133 d->outpacket[6] = d->midiDevice;
134 d->outpacket[7] = 0;
135
136 // Specify the volume
137 d->outpacket[8] = SEQ_MIDIPUTC;
138 d->outpacket[9] = 0; // volume
139 d->outpacket[10] = d->midiDevice;
140 d->outpacket[11] = 0;
141
142 // Start the timer so that we can end the notes
143 startTimer(10);
144 }
145
146 MidiReal::~MidiReal()
147 {
148 // Stop all the notes
149 stopAll();
150
151 // Close the sequencer
152 if(d->seqfd > 0) {
153 close(d->seqfd);
154 }
155
156 delete d;
157 }
158
159
160 void MidiReal::setInstrument(int num)
161 {
162 // Setup the MIDI packet
163 unsigned char outpacket[4];
164 outpacket[0] = SEQ_MIDIPUTC;
165 outpacket[2] = d->midiDevice;
166 outpacket[3] = 0;
167
168 // Write the "Change Patch" packet
169 outpacket[1] = 0xc0;
170 write(d->seqfd, outpacket, 4);
171
172 // Write the patch number packet
173 outpacket[1] = num & 0xff;
174 write(d->seqfd, outpacket, 4);
175 }
176
177 bool MidiReal::playNote(int num, int vel, int len)
178 {
179 // Check the note and volume are in range
180 if(num > 127 || num < 0)
181 return false;
182
183 if(vel > 127 || vel < 0)
184 return false;
185
186 // Specify the note
187 d->outpacket[5] = num;
188
189 // Specify the volume
190 d->outpacket[9] = vel;
191
192 // Send it on it's way
193 write(d->seqfd, d->outpacket, 12);
194
195 // Set it up to turn off
196 if(len != 0 && vel != 0) {
197 noteEndings[num] = timer + len;
198 }
199
200 // if(vel != 0)
201 // printf("Playing note: %d\n", num);
202
203 return true;
204 }
205
206 #endif
207
208
209 /* Windows code */
210
211 #ifdef Q_WS_WIN
212
213 #include <windows.h>
214 #include <mmsystem.h>
215 #include <mmreg.h>
216
217
218 class MidiReal::Private {
219 public:
220 HMIDIOUT handle;
221
222 union {
223 DWORD dwData;
224 UCHAR bData[4];
225 } u;
226 };
227
228 MidiReal::MidiReal()
229 {
230 d = new Private();
231
232 error = false;
233 timer = 0;
234
235 /* Open default MIDI Out device */
236 unsigned long err;
237 if(err = midiOutOpen(&(d->handle), (UINT)-1, 0, 0, CALLBACK_NULL)) {
238 error = true;
239 errorMessage = "Error opening MIDI: " + QString::number(err);
240 return;
241 }
242
243 // Set all the note endings to zero
244 for(int i = 0; i <= 127; i++) {
245 noteEndings[i] = 0;
246 }
247
248 // Start the timer so that we can end the notes
249 startTimer(10);
250 }
251
252 MidiReal::~MidiReal()
253 {
254 // Stop all the notes
255 stopAll();
256
257 /* Close the MIDI device */
258 midiOutClose(d->handle);
259
260 delete d;
261 }
262
263
264 void MidiReal::setInstrument(int num)
265 {
266 // Setup the MIDI packet
267 d->u.bData[0] = 0xc0;
268 d->u.bData[1] = num & 0xff;
269 d->u.bData[2] = 0;
270 d->u.bData[3] = 0;
271
272 // Write it
273 midiOutShortMsg(d->handle, d->u.dwData);
274 }
275
276 bool MidiReal::playNote(int num, int vel, int len)
277 {
278 // Check the note and volume are in range
279 if(num > 127 || num < 0)
280 return false;
281
282 if(vel > 127 || vel < 0)
283 return false;
284
285 // Setup the MIDI packet
286 d->u.bData[0] = 0x90;
287 d->u.bData[1] = num;
288 d->u.bData[2] = vel;
289 d->u.bData[3] = 0;
290
291 // Write it
292 midiOutShortMsg(d->handle, d->u.dwData);
293
294 // Set it up to turn off
295 if(len != 0 && vel != 0) {
296 noteEndings[num] = timer + len;
297 }
298
299 // if(vel != 0)
300 // printf("Playing note: %d\n", num);
301
302 return true;
303 }
304
305 #endif
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 /* |-------------------------| */
322 /* | Code for class MidiFile | */
323 /* |-------------------------| */
324
325
326 #include <stdio.h>
327
328 #include <qvaluelist.h>
329
330
331
332
333 class MidiFile::Private {
334 public:
335 /* This information found at http://www.borg.com/~jglatt/tech/midifile.htm */
336
337 struct MThd_Chunk
338 {
339 /* Here's the 8 byte header that all chunks must have */
340 char ID[4]; /* This will be 'M','T','h','d' */
341 unsigned long Length; /* This will be 6 */
342
343 /* Here are the 6 bytes */
344 unsigned short Format;
345 unsigned short NumTracks;
346 unsigned short Division;
347 };
348
349 struct MTrk_Chunk
350 {
351 /* Here's the 8 byte header that all chunks must have */
352 char ID[4]; /* This will be 'M','T','r','k' */
353 unsigned long Length; /* This will be the actual size of Data[] */
354
355 /* Here are the data bytes */
356 unsigned char *Data; /* Its actual size is Data[Length] */
357 };
358
359 struct MTrk_Packet
360 {
361 unsigned long deltaTime; /* The delta time - 4 bytes*/
362
363 /* The midi packet data */
364 unsigned char midiData[7];
365
366 int lastGood;
367 };
368
369
370 /* A place to store the MTrk_Packets as we wait for them */
371 QValueList<struct MTrk_Packet> packets;
372
373 /* The time the last note was played, relative to MidiEngine::timer */
374 long int prevNoteTime;
375
376 /* Where we save the file to */
377 QString filename;
378 FILE *file;
379
380
381 /* Functions */
382 int twistVarLen(register unsigned long value, unsigned char str[4]); // 4 bytes
383 void convertLong2Array(unsigned long value, unsigned char str[4]);
384 void convertShort2Array(unsigned short value, unsigned char str[2]);
385 void writeBytes(unsigned char *str, int num);
386 void writeBytes(char *str, int num);
387 void writeMIDI();
388 };
389
390 int MidiFile::Private::twistVarLen(register unsigned long value, unsigned char str[4])
391 {
392 register unsigned long buffer;
393 int i = 0;
394 buffer = value & 0x7F;
395
396 for(i = 0; i < 4; i++) str[i] = 0;
397
398 while ( (value >>= 7) )
399 {
400 buffer <<= 8;
401 buffer |= ((value & 0x7F) | 0x80);
402 }
403
404 for(i = 0;; i++)
405 {
406 str[i] = (unsigned char)buffer;
407 if (buffer & 0x80)
408 buffer >>= 8;
409 else
410 return i;
411 }
412 }
413
414 void MidiFile::Private::convertLong2Array(unsigned long value, unsigned char str[4])
415 {
416 union {
417 char c[4];
418 unsigned long num;
419 } u;
420 u.num = value;
421 for(int i = 0, j = 3; i < 4; i++, j--)
422 str[i] = u.c[j];
423 }
424
425 void MidiFile::Private::convertShort2Array(unsigned short value, unsigned char str[2])
426 {
427 union {
428 char c[2];
429 unsigned short num;
430 } u;
431 u.num = value;
432 str[1] = u.c[0];
433 str[0] = u.c[1];
434 }
435
436 void MidiFile::Private::writeBytes(unsigned char *str, int num)
437 {
438 for(int i = 0; i < num; i++)
439 fputc(str[i], file);
440 }
441
442 void MidiFile::Private::writeBytes(char *str, int num)
443 {
444 for(int i = 0; i < num; i++)
445 fputc(str[i], file);
446 }
447
448 void MidiFile::Private::writeMIDI()
449 {
450 if(file == 0)
451 return;
452
453 unsigned char str[4];
454 int i = 0;
455 int j = 0;
456
457 // Create the MThd header
458 struct MThd_Chunk h;
459 h.ID[0] = 'M';
460 h.ID[1] = 'T';
461 h.ID[2] = 'h';
462 h.ID[3] = 'd';
463 h.Length = 6;
464 h.Format = 0;
465 h.NumTracks = 1;
466 h.Division = 500;
467
468 // Create the MTrk chunk and allocate space for the MIDI packets
469 struct MTrk_Chunk t;
470 t.ID[0] = 'M';
471 t.ID[1] = 'T';
472 t.ID[2] = 'r';
473 t.ID[3] = 'k';
474 t.Length = packets.count() * sizeof(struct MTrk_Packet); // Only an approximation
475 t.Data = new unsigned char[t.Length];
476
477
478 // Store the MIDI packets in the MTrk chunk
479 QValueList<struct MTrk_Packet>::Iterator it;
480 int pos = 0;
481 for(it = packets.begin(); it != packets.end(); ++it) {
482 // Twist the deltaTime, then copy it
483 j = twistVarLen((*it).deltaTime, str);
484 // Copy now..
485 for(i = 0; i <= j; i++) t.Data[pos + i] = str[i];
486
487 pos = pos + i; // New position
488
489
490 // Copy the MIDI bytes also
491 for(i = 0; i <= (*it).lastGood; i++)
492 t.Data[pos + i] = (*it).midiData[i];
493
494 pos = pos + i;
495 }
496 t.Length = pos;
497
498
499
500 /* Write all the MIDI packets to disk */
501
502 // Writing the header
503 // Write h.ID
504 writeBytes(h.ID, 4);
505 // Write h.Length
506 convertLong2Array(h.Length, str);
507 writeBytes(str, 4);
508 // Write the format
509 convertShort2Array(h.Format, str);
510 writeBytes(str, 2);
511 // Write the NumTracks
512 convertShort2Array(h.NumTracks, str);
513 writeBytes(str, 2);
514 // Hack hack, write the Division
515 convertShort2Array(h.Division, str);
516 writeBytes(str, 2);
517 // fputc(-25, file);
518 // fputc(40, file);
519
520
521 // Now writing the track
522 // Write t.ID
523 writeBytes(t.ID, 4);
524 // Write the length
525 convertLong2Array(t.Length, str);
526 writeBytes(str, 4);
527 // Copy the track data
528 writeBytes(t.Data, t.Length);
529
530
531 // Free it again
532 delete [] t.Data;
533 }
534
535
536 MidiFile::MidiFile(QString file)
537 {
538 d = new Private();
539
540 d->filename = file;
541
542 error = false;
543 timer = 0;
544 d->prevNoteTime = -1;
545
546 // Open the MIDI file to send output to
547 d->file = fopen(d->filename.latin1(), "wb");
548 if (d->file == 0) {
549 errorMessage = "Cannot open output MIDI file: " + d->filename;
550 return;
551 }
552
553 // Set all the note endings to zero
554 for(int i = 0; i <= 127; i++) {
555 noteEndings[i] = 0;
556 }
557
558 // Start the timer so that we can end the notes
559 startTimer(10);
560 }
561
562 MidiFile::~MidiFile()
563 {
564 // Stop all the notes
565 stopAll();
566
567 // End track note
568 struct Private::MTrk_Packet pak;
569 pak.deltaTime = 0;
570 pak.midiData[0] = 0xff;
571 pak.midiData[1] = 0x2f;
572 pak.midiData[2] = 0x00;
573 pak.lastGood = 2;
574 d->packets.append(pak);
575
576 // Write the MIDI
577 d->writeMIDI();
578
579 // Close the MIDI file
580 if(d->file != 0) {
581 fclose(d->file);
582 }
583
584 delete d;
585 }
586
587
588 void MidiFile::setInstrument(int num)
589 {
590 // Setup the MIDI packet
591 struct Private::MTrk_Packet pak;
592
593 // Set the deltaTime
594 if(d->prevNoteTime == -1) // If we're the first note, start from 0, but don't let us count (only a patch change)
595 pak.deltaTime = 0;
596 else
597 d->prevNoteTime = timer;
598
599 pak.midiData[0] = 0xc0;
600 pak.midiData[1] = num & 0xff;
601 pak.lastGood = 1; // Last byte with data
602
603 d->packets.append(pak);
604 }
605
606 bool MidiFile::playNote(int num, int vel, int len)
607 {
608 // Check the note and volume are in range
609 if(num > 127 || num < 0)
610 return false;
611
612 if(vel > 127 || vel < 0)
613 return false;
614
615
616 // Setup the MIDI packet
617 struct Private::MTrk_Packet pak;
618
619 // Set the deltaTime
620 if(d->prevNoteTime == -1 && vel != 0) // If we're the first note, start from 0
621 d->prevNoteTime = timer;
622 pak.deltaTime = timer - d->prevNoteTime;
623 d->prevNoteTime = timer;
624
625 if(vel > 0) {
626 // Setup the MIDI packet
627 pak.midiData[0] = 0x90;
628 pak.midiData[1] = num;
629 pak.midiData[2] = vel;
630 pak.lastGood = 2; // Last byte with good data
631 }
632 else {
633 // Setup the MIDI packet
634 pak.midiData[0] = 0x80;
635 pak.midiData[1] = num;
636 pak.midiData[2] = 64; // How quickly the note goes away
637 pak.lastGood = 2; // Last byte with good data
638 }
639
640 // Set it up to turn off
641 if(len != 0 && vel != 0) {
642 noteEndings[num] = timer + len;
643 }
644
645 // if(vel != 0)
646 // printf("Appending note: %d with deltaTime: %d\n", num, pak.deltaTime);
647
648 d->packets.append(pak);
649
650 return true;
651 }
652
653