CSL  5.2
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 
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 
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 ); }
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 
52  StringArray midiDevs = MidiInput::getDevices();
53  return (int) midiDevs.size();
54 }
55 
56 // Made available for flexibility
57 
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 
76  if( ! mIsInitialized ) {
77  mIsInitialized = true;
78  }
79  mIsOpen = false;
80  mMsg.message = kNone;
81 }
82 
84  mIsInitialized = false;
85 }
86 
87 void MIDIIO::open() {
88  open(MidiInput::getDefaultDeviceIndex());
89 }
90 
92  return mIsOpen;
93 }
94 
95 void MIDIIO::close() {
96  mIsOpen = false;
97  this->clear();
98 }
99 
100 // clear MIDI stream
101 
103  mMsg.message = kNone;
104  mBuffer.clear();
105 }
106 
107 // copy_MIDIMessage -- copies csl::CMIDIMessage <--> juce::MidiMessage
108 
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 
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 
195 // Pm_Terminate();
196  logMsg(kLogError, "An error occured in MIDIIO: %s", err->what());
197 }
198 
199 
200 // MIDIIn
201 
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) {
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());
227  gAudioDeviceManager->setMidiInputEnabled(mDevice->getName(), true);
228  mIsOpen = true;
229 }
230 
231 ///< start MIDI stream
232 
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 
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 
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 
337 }
338 
339 void MIDIOut::open(int deviceID) {
340  mDeviceID = deviceID;
341  mOut = MidiOutput::openDevice (deviceID);
342 }
343 
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 
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 
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 }