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