CSL 5.2

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

MIDIIOJ.cpp
Go to the documentation of this file.
1 //
2 // MIDIIOJ.cpp -- MIDI IO using JUCE
3 //
4 // See the copyright notice and acknowledgment of authors in the file COPYRIGHT
5 //
6
7 #include "MIDIIOJ.h"
8
9 extern AudioDeviceManager * gAudioDeviceManager; // global JUCE audio device mgr
10
11 using namespace csl;
12
13 // CSL_MIDIMessage
14
15 CMIDIMessage::CMIDIMessage()
16 : message(kNone), // init to no event
17 channel(0), data1(0), data2(0), time(0) { }
18
19 bool CMIDIMessage::isNoteOn() { return (command == kNoteOn); }
20 bool CMIDIMessage::isNoteOff() { return (command == kNoteOff); }
21 bool CMIDIMessage::isNoteOnOff() { return ((command == kNoteOff) || (command == kNoteOn)); }
22
23 bool CMIDIMessage::isPolyTouch() { return (command == kPolyTouch); }
24 bool CMIDIMessage::isControlChange() { return (command == kControlChange); }
25 bool CMIDIMessage::isProgramChange() { return (command == kProgramChange); }
26 bool CMIDIMessage::isAftertouch() { return (command == kAftertouch); }
27 bool CMIDIMessage::isPitchWheel() { return (command == kPitchWheel); }
28 bool CMIDIMessage::isSysEX() { return (command == kSysEX); }
29
30 unsigned CMIDIMessage::getCommand() { return (unsigned) command; }
31 unsigned CMIDIMessage::getNote() { return (unsigned) data1; }
32 unsigned CMIDIMessage::getVelocity() { return (unsigned) data2; }
33 unsigned CMIDIMessage::getPolyAftertouch() { return (unsigned) data2; }
34 unsigned CMIDIMessage::getControlFunction() { return (unsigned) data1; }
35 unsigned CMIDIMessage::getControlValue() { return (unsigned) data2; }
36 unsigned CMIDIMessage::getProgramNumber() { return (unsigned) data1; }
37 unsigned CMIDIMessage::getAftertouch() { return (unsigned) data1; }
38 unsigned CMIDIMessage::getPitchWheel() { return ((unsigned) (data2 << 7) + (unsigned) data1 ); }
39 float CMIDIMessage::getFrequency() { return keyToFreq(data1); }
40 float CMIDIMessage::getVelocityFloat() { return ((float) data2 / 127.0f); }
41
42
43 // MIDIIO
44
45 // static var. definition
46
47 bool MIDIIO::mIsInitialized = false;
48
49 // Made available for flexibility
50
51 int MIDIIO::countDevices() {
52 StringArray midiDevs = MidiInput::getDevices();
53 return (int) midiDevs.size();
54 }
55
56 // Made available for flexibility
57
58 void MIDIIO::dumpDevices() {
59 StringArray midiDevs = MidiInput::getDevices();
60 unsigned len = midiDevs.size();
61 logMsg("\n\tMIDI in devices");
62 for (unsigned i = 0; i < len; i++)
63 logMsg(" %d = %s", i, midiDevs[i].toUTF8());
64 logMsg(" Def: %d", MidiInput::getDefaultDeviceIndex());
65 midiDevs = MidiOutput::getDevices();
66 len = midiDevs.size();
67 logMsg("\n\tMIDI out devices");
68 for (unsigned i = 0; i < len; i++)
69 logMsg(" %d = %s", i, midiDevs[i].toUTF8());
70 logMsg(" Def: %d", MidiOutput::getDefaultDeviceIndex());
71 }
72
73 // Constructors
74
75 MIDIIO::MIDIIO() {
76 if( ! mIsInitialized ) {
77 mIsInitialized = true;
78 }
79 mIsOpen = false;
80 mMsg.message = kNone;
81 }
82
83 MIDIIO::~MIDIIO() {
84 mIsInitialized = false;
85 }
86
87 void MIDIIO::open() {
88 open(MidiInput::getDefaultDeviceIndex());
89 }
90
91 bool MIDIIO::isOpen() {
92 return mIsOpen;
93 }
94
95 void MIDIIO::close() {
96 mIsOpen = false;
97 this->clear();
98 }
99
100 // clear MIDI stream
101
102 void MIDIIO::clear() {
103 mMsg.message = kNone;
104 mBuffer.clear();
105 }
106
107 // copy_MIDIMessage -- copies csl::CMIDIMessage <--> juce::MidiMessage
108
109 void MIDIIO::copyMessage(CMIDIMessage& source, CMIDIMessage& dest) {
110 dest.message = source.message;
111 dest.command = source.command;
112 dest.channel = source.channel;
113 dest.data1 = source.data1;
114 dest.data2 = source.data2;
115 dest.time = source.time;
116 }
117
118 void MIDIIO::copyMessage(CMIDIMessage& source, MidiMessage* dest) {
119 dest = new MidiMessage((source.command | source.channel),
120 source.data1, source.data2, source.time);
121 }
122
123 #define CCOPY_MSG dest.channel = source.getChannel(); \
124 dest.data1 = source.getNoteNumber(); \
125 dest.data2 = source.getVelocity()
126
127 void MIDIIO::copyMessage(const MidiMessage& source, CMIDIMessage& dest) {
128 unsigned char * data = source.getRawData();
129 unsigned char cmd = (data[0] & 0xf0) >> 4;
130 dest.command = cmd;
131 switch(cmd) {
132 case kNoteOn:
133 if (source.getVelocity() > 0) {
134 dest.message = kNoteOn;
135 CCOPY_MSG;
136 break;
137 }
138 case kNoteOff:
139 dest.message = kNoteOff;
140 CCOPY_MSG;
141 break;
142 case kPolyTouch:
143 dest.message = kPolyTouch;
144 CCOPY_MSG;
145 case kControlChange:
146 dest.message = kControlChange;
147 dest.channel = source.getChannel();
148 dest.data1 = source.getControllerNumber();
149 dest.data2 = source.getControllerValue();
150 break;
151 case kProgramChange:
152 dest.message = kProgramChange;
153 CCOPY_MSG;
154 mMsg.data2 = 0;
155 break;
156 case kAftertouch:
157 dest.message = kAftertouch;
158 CCOPY_MSG;
159 break;
160 case kPitchWheel:
161 dest.message = kPitchWheel;
162 CCOPY_MSG;
163 dest.data2 = 0;
164 break;
165 case kSysEX:
166 dest.message = kSysEX;
167 dest.channel = 0;
168 dest.data1 = 0;
169 dest.data2 = 0;
170 break;
171 default:
172 logMsg(kLogError, "Unknown MIDI input: stat=%d chan=%d value1=%d value2=%d",
173 cmd, source.getChannel(), source.getNoteNumber(), source.getVelocity());
174 break;
175 }
176 dest.time = (Timestamp) source.getTimeStamp();
177 }
178
179 void MIDIIO::dumpBuffer() {
180 unsigned cmd, voice, pitch = 0, vel;
181 // PmMessage msg;
182 // msg = mBuffer[0].message;
183 // cmd = Pm_MessageStatus(msg) & 0xF0;
184 // voice = Pm_MessageStatus(msg) & 0x0F;
185 // pitch = Pm_MessageData1(msg);
186 // vel = Pm_MessageData2(msg);
187 // printf("\tGot message: time %.3f, cmd: %x midich: %d data1: %d data2: %d\n",
188 // (float) mBuffer[0].timestamp / 1000.0f, cmd, voice, pitch, vel);
189
190 }
191
192 // MIDIIO error handler
193
194 void MIDIIO::handleError(CException * err) {
195 // Pm_Terminate();
196 logMsg(kLogError, "An error occured in MIDIIO: %s", err->what());
197 }
198
199
200 // MIDIIn
201
202 MIDIIn::MIDIIn() : MIDIIO() {
203 mFilterFlag = 0;
204 mDeviceID = 0;
205 mDevice = 0;
206 mBufferSize = 100;
207 }
208
209 unsigned MIDIIn::bufferSize() {
210 return mBufferSize;
211 }
212
213 void MIDIIn::setBufferSize(unsigned bufferSize) {
214 mBufferSize = bufferSize;
215 }
216
217 // open and register me as the MidiInputCallback
218 void MIDIIn::open(int deviceID) {
219 mDeviceID = deviceID;
220 mDevice = MidiInput::openDevice(mDeviceID, this);
221 if ( ! mDevice) {
222 logMsg(kLogError, "Cannot open midi in %d", mDeviceID);
223 return;
224 }
225 logMsg("Open midi input %s", mDevice->getName().toUTF8());
226 if (gAudioDeviceManager)
227 gAudioDeviceManager->setMidiInputEnabled(mDevice->getName(), true);
228 mIsOpen = true;
229 }
230
231 ///< start MIDI stream
232
233 void MIDIIn::start() {
234 mStartTime = Time::getMillisecondCounter() / 1000.0;
235 if (mDevice)
236 mDevice->start();
237 }
238
239 ///< stop MIDI stream
240
241 void MIDIIn::stop() {
242 if (mDevice)
243 mDevice->stop();
244 }
245
246 /// implement inherited MidiInputCallback
247
248 void MIDIIn::handleIncomingMidiMessage(MidiInput * source, const MidiMessage & message) {
249 if (mMsg.message == kNone) {
250 copyMessage(message, mMsg); // copy the message
251 mMsg.time = message.getTimeStamp() - mStartTime;
252 this->changed((void *) &mMsg); // FLAG CHANGE TO OBSERVERS
253 } else { // paste msg to buffer
254 mBuffer.addEvent(message, timeNow());
255 copyMessage(message, mMsg2); // copy the message
256 mMsg2.time = message.getTimeStamp() - mStartTime;
257 this->changed((void *) &mMsg2); // FLAG CHANGE TO OBSERVERS
258 }
259 }
260
261 // quick poll
262
263 bool MIDIIn::poll() {
264 return (mMsg.message != kNone);
265 }
266
267 // step to next event or reset flag
268
269 void MIDIIn::nextEvent() {
270 if (mBuffer.isEmpty()) { // if empty, clear
271 mMsg.message = kNone;
272 } else { //else take first even from Q
273 int t0 = mBuffer.getFirstEventTime();
274 MidiBuffer::Iterator it(mBuffer);
275 it.getNextEvent(*mJMsg, t0);
276 copyMessage(*mJMsg, mMsg); // copy the message
277 mBuffer.clear(t0, 1); // remove it from the buffer
278 }
279 }
280
281 void MIDIIn::dumpMessage() {
282 printf("\tMIDIIn ");
283 switch(mMsg.message) {
284 case kNone:
285 printf("kNone");
286 break;
287 case kNoteOff:
288 printf("kNoteOff");
289 break;
290 case kNoteOn:
291 printf("kNoteOn");
292 break;
293 case kPolyTouch:
294 printf("kPolyTouch");
295 break;
296 case kControlChange:
297 printf("ControlChange");
298 break;
299 case kProgramChange:
300 printf("kProgramChange");
301 break;
302 case kAftertouch:
303 printf("kAftertouch");
304 break;
305 case kPitchWheel:
306 printf("kPitchWheel");
307 break;
308 case kSysEX:
309 printf("kSysEX");
310 break;
311 default:
312 printf("unknown");
313 }
314 printf(" \tch: %2d d1: %2d d2: %2d t: %5.3f\n", mMsg.channel, mMsg.data1, mMsg.data2, mMsg.time);
315 }
316
317 // evaluate answers the midi command
318
319 int MIDIIn::evaluate(void * arg) {
320 CMIDIMessage * msg = (CMIDIMessage *) arg;
321 return msg->message;
322 }
323
324 // MIDIOut
325
326 MIDIOut::MIDIOut() : MIDIIO(), mOut(0) {
327 mDeviceID = 0; // Pm_GetDefaultOutputDeviceID();
328 mBufferSize = 0;
329 mLatency = 0; // do I need to set this to > 0 value for synchronization?
330 MidiOutput::getDevices();
331 StringArray midiDevs = MidiInput::getDevices();
332
333 * mOut;
334 }
335
336 MIDIOut::~MIDIOut() {
337 }
338
339 void MIDIOut::open(int deviceID) {
340 mDeviceID = deviceID;
341 mOut = MidiOutput::openDevice (deviceID);
342 }
343
344 void MIDIOut::write(CMIDIMessage & msg) {
345 copyMessage(msg, mJMsg);
346 mOut->sendMessageNow (*mJMsg);
347 delete mJMsg;
348 }
349
350 void MIDIOut::writeSysEX(long when, unsigned char *msg ) {
351 }
352
353 void MIDIOut::writeNoteOn(unsigned channel, unsigned pitch, unsigned velocity ) {
354 }
355
356 void MIDIOut::writeNoteOn(unsigned channel, float frequency, float amplitude ) {
357 }
358
359 void MIDIOut::writeNoteOff(unsigned channel, unsigned pitch, unsigned velocity ) {
360 }
361
362 void MIDIOut::writeNoteOff(unsigned channel, float frequency, float amplitude ) {
363 }
364
365 void MIDIOut::writePolyTouch(unsigned channel, unsigned pitch, unsigned amount ) {
366 }
367
368 void MIDIOut::writeControlChange(unsigned channel, unsigned function, unsigned value ) {
369 }
370
371 void MIDIOut::writeProgramChange(unsigned channel, unsigned programNum ) {
372 }
373
374 void MIDIOut::writeAftertouch(unsigned channel, unsigned amount ) {
375 }
376
377 void MIDIOut::writePitchWheel(unsigned channel, unsigned amount ) {
378 }
379
380
381 // init a Midi player from a file name
382
383 void MIDIPlayer::init(String namS) {
384 File fil(namS);
385 mFile.readFrom(*(fil.createInputStream()));
386 mNumTrax = mFile.getNumTracks();
387 mTrak = (MidiMessageSequence *) mFile.getTrack(0);
388 mIsOn = false;
389 logMsg("MIDIPlayer %d trax", mNumTrax);
390 // dump all track events
391 for (int i = 0; i < mNumTrax; i++) {
392 mTrak = (MidiMessageSequence *) mFile.getTrack(i);
393 logMsg(" track %d : %d evts", i, mTrak->getNumEvents());
394 }
395 int tix = mFile.getTimeFormat();
396 MidiMessageSequence tempi;
397 mFile.findAllTempoEvents(tempi);
398 if (tempi.getNumEvents() == 0) {
399 mTempoScale = 120.0f;
400 } else {
401 MidiMessageSequence::MidiEventHolder * t1 = tempi.getEventPointer(0);
402 MidiMessage msg = t1->message; // and its event
403 double sex = msg.getTempoSecondsPerQuarterNote();
404 mTempoScale = sex / tix;
405 }
406 }
407
408 // MIDIPlayer constructor loads MIDI file
409
410 MIDIPlayer::MIDIPlayer(string nam, InstrumentLibrary * lib) {
411 String namS(nam.c_str());
412 init(namS);
413 mLibrary = lib;
414 }
415
416 // MIDIPlayer constructor loads MIDI file
417
418 MIDIPlayer::MIDIPlayer(string folder, string nam, InstrumentLibrary * lib) {
419 string abs(folder);
420 abs += nam;
421 String namS(abs.c_str());
422 init(namS);
423 mLibrary = lib;
424 }
425
426 // start method reads tracks
427
428 void MIDIPlayer::start(int index) {
429 if (index < 0)
430 mTrak = this->mergeTrax();
431 else
432 mTrak = (MidiMessageSequence *) mFile.getTrack(index); // get track
433 mTrak->deleteSysExMessages(); // clean up
434 mTrak->updateMatchedPairs(); // match note-on-off
435 float tim0 = (float) mTrak->getStartTime() * mTempoScale; // time 0
436 float timX = (float) mTrak->getEndTime() * mTempoScale; // time x
437 float timN = tim0; // time now
438 float startT = fTimeNow();
439 unsigned numEvts = mTrak->getNumEvents(); // # notes
440 mIsOn = true;
441 float pos; // stereo position
442 float * pPtr = & pos;
443 logMsg("MIDIPlayer playing %d evts %5.2f sec.", numEvts, timX - tim0);
444
445 while (mIsOn && (timN < timX)) { // schedule loop
446 int ind = mTrak->getNextIndexAtTime((double) (timN / mTempoScale)); // get ind of next event
447 float timE = mTrak->getEventTime(ind) * mTempoScale; // time of event
448 MidiMessageSequence::MidiEventHolder * evt // get the event holder
449 = mTrak->getEventPointer(ind);
450 if ( ! evt) continue;
451 MidiMessage msg = evt->message; // and its event
452 copyMessage(msg, mMsg); // make a CMIDIMessage
453 sleepSec(timE - timN); // sleep till then
454
455 if (mMsg.isNoteOn()) { // handle note-on events
456 float timD = mTrak->getTimeOfMatchingKeyUp(ind);
457 if (timD != 0)
458 timD = (timD * mTempoScale) - timE; // get note duration
459 else {
460 // logMsg(" NoteOff %d not found", ind);
461 timD = timX - timE;
462 }
463 if (mLibrary) { // get the instrument to play on
464 int which = 0;
465 InstrumentVector iv = (* mLibrary)[mMsg.channel];
466 if (iv.empty()) {
467 logMsg("MIDIPlayer empty iv");
468 }
469 Instrument * inst = iv[which]; // get the vector for this channel
470 if ( ! inst) {
471 logMsg("MIDIPlayer empty in");
472 }
473 while (inst->isActive()) { // if we're already playing
474 which++;
475 if (which == iv.size()) { // look for a free instrument
476 // iv.push_back(new Instrument(*inst));
477 logMsg("MIDIPlayer out of voices in instr %d", mMsg.channel);
478 goto next; // goto next note instead of voice-stealing
479 }
480 inst = iv[which];
481 }
482 pos = fRand1(); // pick a random position
483 inst->setParameter(set_position_f, 1, (void **) &pPtr, "f");
484 // play the MIDI note
485 // logMsg("NoteOn %5.2f - %5.2f \t%2d.%2d %2d %2d",
486 // timE, timD, mMsg.command, mMsg.channel, mMsg.data1, mMsg.data2);
487 inst->playMIDI(timD, mMsg.channel, mMsg.data1, mMsg.data2);
488 }
489 // } else if ( ! mMsg.isNoteOff()) {
490 // logMsg("Msg %5.2f %d - %d %d %d", timV * mTempoScale,
491 // mMsg.message, mMsg.channel, mMsg.data1, mMsg.data2);
492 }
493 next:
494 mTrak->deleteEvent(ind, mMsg.isNoteOn()); // delete event from list
495 timN = timE; // update clock
496 }
497 }
498
499 // set stop flag
500
501 void MIDIPlayer::stop() {
502 mIsOn = false;
503 }
504
505 // merge all the file's tracks into 1
506
507 MidiMessageSequence * MIDIPlayer::mergeTrax() {
508 MidiMessageSequence * tr = new MidiMessageSequence;
509 for (int i = 0; i < mNumTrax; i++)
510 tr->addSequence(*mFile.getTrack(i), 0, 0, mFile.getLastTimestamp());
511 return tr;
512 }