00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include "midi_obj.h"
00012
00013 #include <sys/types.h>
00014 #include <sys/stat.h>
00015
00016 #include <unistd.h>
00017 #include <fcntl.h>
00018
00019 #include <map>
00020
00021 #include "util/eventq.h"
00022 #include "common/handler.h"
00023
00024 #include "midi.h"
00025
00026
00027 namespace media
00028 {
00029
00030
00031
00032 static const char * s_MidiIn = "/dev/midi";
00033
00034 struct status_record {
00035 MidiEvent::eStatus status;
00036 const char *name;
00037 };
00038
00039 #define STAT_ENTRY(enum, name) { MidiEvent::e ##enum , name },
00040 static const status_record s_StatusTable[] =
00041 {
00042 STAT_ENTRY(NoteOff, "note_off")
00043 STAT_ENTRY(NoteOn, "note_on")
00044 STAT_ENTRY(NoteAfter, "note_after")
00045 STAT_ENTRY(Control, "control")
00046 STAT_ENTRY(Program, "program")
00047 STAT_ENTRY(ChannelAfter,"channel_after")
00048 STAT_ENTRY(Pitch, "pitch")
00049
00050
00051 STAT_ENTRY(Invalid, NULL)
00052 };
00053
00054
00055
00056
00057
00058
00059
00060
00061 void
00062 get_token
00063 (
00064 IN const char *& str,
00065 OUT std::string& token
00066 )
00067 throw()
00068 {
00069 ASSERT(str, "NULL string");
00070
00071
00072 while (*str && isspace(*str)) { ++str; }
00073
00074
00075 token = "";
00076 while (*str && !isspace(*str)) {
00077 token += *str;
00078 ++str;
00079 }
00080 }
00081
00082
00083 MidiEvent::eStatus
00084 get_status
00085 (
00086 IN const char * status
00087 )
00088 {
00089 const status_record * p = s_StatusTable;
00090 while (p->name) {
00091
00092 if (!strcmp(status, p->name)) {
00093 break;
00094 }
00095
00096
00097 ++p;
00098 }
00099
00100 return p->status;
00101 }
00102
00103
00104
00105
00106
00107
00108
00109 class MidiImpl : public objdir::LocalObject,
00110 public MidiReceiver
00111 {
00112 public:
00113
00114 MidiImpl(void) throw();
00115 ~MidiImpl(void) throw();
00116
00117
00118 error_t Initialize(void) throw();
00119
00120
00121 virtual void NotifyDirectory(IN objdir::Directory * directory) throw();
00122 virtual error_t ReceiveMessage(IN const char * from_object,
00123 IN const char * message) throw();
00124
00125
00126 virtual error_t NotifyEvent(IN const MidiEvent& event) throw();
00127
00128 private:
00129
00130 struct trigger_info {
00131 MidiEvent::eStatus status;
00132 int channel;
00133 int byte0;
00134 int byte1;
00135 std::string object;
00136 std::string message;
00137 };
00138 typedef std::map<std::string, smart_ptr<trigger_info> > MapTriggers;
00139
00140
00141 static void * ThreadStart(IN void * context) throw();
00142 void Trigger(IN const char * object, IN const char * message,
00143 IN const MidiEvent& event) throw();
00144
00145
00146 HANDLER_DECL(MidiImpl, AddTrigger)
00147
00148
00149 objdir::Directory *m_directory;
00150 pthread_t m_listen;
00151 MapTriggers m_triggers;
00152 int m_fdMIDI;
00153
00154
00155 bool m_shutdown;
00156
00157
00158 HANDLER_TABLE_DECL(MidiImpl, ms_handlers)
00159 };
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169 #define ENTRY(cmd, handler) HANDLER_ENTRY(MidiImpl, cmd, handler)
00170
00171 HANDLER_TABLE_START(MidiImpl, ms_handlers)
00172
00173 ENTRY("add_trigger", AddTrigger)
00174 HANDLER_TABLE_END()
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186 MidiImpl::MidiImpl(void) throw() :
00187 m_directory(NULL),
00188 m_listen(0),
00189 m_fdMIDI(-1),
00190 m_shutdown(false)
00191 {
00192 DPRINTF("MidiImpl constructor...");
00193 }
00194
00195
00196
00197 MidiImpl::~MidiImpl(void) throw()
00198 {
00199 void *ret;
00200
00201 DPRINTF("MidiImpl destructor...");
00202
00203
00204
00205 if (m_listen) {
00206 m_shutdown = true;
00207 pthread_join(m_listen, &ret);
00208 }
00209
00210
00211 if (m_fdMIDI > 0)
00212 close(m_fdMIDI);
00213 }
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223 error_t
00224 MidiImpl::Initialize(void)
00225 throw()
00226 {
00227 error_t error = 0;
00228
00229
00230 ASSERT(m_fdMIDI < 0, "Already have a MIDI in descriptor");
00231 m_fdMIDI = open(s_MidiIn, O_RDWR);
00232 if (-1 == m_fdMIDI)
00233 CHECK_ERROR(errno, "Failed to open %s for read/write: %s",
00234 s_MidiIn, strerror(errno));
00235 ASSERT(m_fdMIDI > 0, "Bad MIDI in file descriptor");
00236
00237
00238 ASSERT(!m_listen, "Already have a listening thread");
00239 CHECK_ERROR(pthread_create(&m_listen, NULL, ThreadStart, this),
00240 "Failed to create MIDI input thread");
00241 ASSERT(m_listen, "Should have MIDI input thread handle");
00242
00243 out:
00244 return error;
00245 }
00246
00247
00248
00249
00250
00251
00252
00253
00254 void
00255 MidiImpl::NotifyDirectory
00256 (
00257 IN objdir::Directory * directory
00258 )
00259 throw()
00260 {
00261 ASSERT(directory, "NULL directory");
00262 ASSERT(!m_directory, "Already have a directory pointer");
00263
00264 m_directory = directory;
00265 }
00266
00267
00268
00269 error_t
00270 MidiImpl::ReceiveMessage
00271 (
00272 IN const char * from_object,
00273 IN const char * message
00274 )
00275 throw()
00276 {
00277 return DISPATCH_COMMAND(MidiImpl, this, message, ms_handlers);
00278 }
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288 error_t
00289 MidiImpl::NotifyEvent
00290 (
00291 IN const MidiEvent& event
00292 )
00293 throw()
00294 {
00295 error_t error = 0;
00296 MapTriggers::iterator iter;
00297 trigger_info *trigger;
00298
00299
00300 for (iter = m_triggers.begin(); iter != m_triggers.end(); ++iter) {
00301
00302 trigger = iter->second;
00303
00304
00305 if (event.GetStatus() == trigger->status &&
00306 event.GetChannel() == trigger->channel) {
00307
00308 if (-1 == trigger->byte0 ||
00309 event.GetByte0() == trigger->byte0) {
00310
00311 if (-1 == trigger->byte1 ||
00312 event.GetByte1() == trigger->byte1) {
00313
00314 this->Trigger(trigger->object.c_str(),
00315 trigger->message.c_str(), event);
00316 }
00317 }
00318 }
00319 }
00320
00321
00322 return error;
00323 }
00324
00325
00326
00327
00328
00329
00330
00331
00332
00333 error_t
00334 MidiImpl::Handle_AddTrigger
00335 (
00336 IN const char * cmd
00337 )
00338 throw()
00339 {
00340 error_t error = 0;
00341 smart_ptr<trigger_info> trigger;
00342 std::string status, name;
00343
00344 DPRINTF("In AddTrigger()...");
00345 DPRINTF(" cmd = %s", cmd);
00346
00347
00348 trigger = new trigger_info;
00349 ASSERT(trigger, "Should have valid info");
00350
00351
00352 get_token(cmd, name);
00353
00354
00355 get_token(cmd, status);
00356 trigger->status = get_status(status.c_str());
00357 if (MidiEvent::eInvalid == trigger->status)
00358 CHECK_ERROR(EINVAL, "Midi event status '%s' is not valid",
00359 status.c_str());
00360
00361
00362 while (*cmd && isspace(*cmd)) { ++cmd; }
00363 trigger->channel = atoi(cmd);
00364 if (trigger->channel < 0 ||
00365 trigger->channel > 15)
00366 CHECK_ERROR(EINVAL, "Midi channel %d invalid: should be 0-15",
00367 trigger->channel);
00368 while (*cmd && !isspace(*cmd)) { ++cmd; }
00369
00370
00371 while (*cmd && isspace(*cmd)) { ++cmd; }
00372 if ('*' == *cmd)
00373 trigger->byte0 = -1;
00374 else
00375 trigger->byte0 = atoi(cmd);
00376 while (*cmd && !isspace(*cmd)) { ++cmd; }
00377
00378
00379 while (*cmd && isspace(*cmd)) { ++cmd; }
00380 if ('*' == *cmd)
00381 trigger->byte1 = -1;
00382 else
00383 trigger->byte1 = atoi(cmd);
00384 while (*cmd && !isspace(*cmd)) { ++cmd; }
00385
00386
00387 get_token(cmd, trigger->object);
00388
00389
00390 while (*cmd && isspace(*cmd)) { ++cmd; }
00391 trigger->message = cmd;
00392
00393 DPRINTF("Adding trigger: %s --> %s",
00394 trigger->object.c_str(), trigger->message.c_str());
00395
00396
00397 m_triggers.insert(MapTriggers::value_type(name, trigger));
00398 ASSERT(!trigger, "map should take ownership");
00399
00400 out:
00401 return error;
00402 }
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419 void *
00420 MidiImpl::ThreadStart
00421 (
00422 IN void * context
00423 )
00424 throw()
00425 {
00426 void * ret = NULL;
00427 error_t error = 0;
00428 const int buff_size = 5;
00429 byte_t buffer[buff_size];
00430 long i, bytes, offset;
00431 MidiImpl * pThis = (MidiImpl *) context;
00432
00433 ASSERT(pThis, "NULL context");
00434 ASSERT(pThis->m_fdMIDI > 0, "Bad MIDI fd");
00435
00436
00437
00438 offset = 0;
00439 bytes = buff_size;
00440 while (!pThis->m_shutdown) {
00441
00442 bytes = read(pThis->m_fdMIDI, buffer + offset, bytes);
00443 if (-1 == bytes)
00444 CHECK_ERROR(errno, "Could not read from MIDI in");
00445
00446
00447 CHECK_ERROR(ParseMidi(pThis, buffer, bytes + offset, &offset),
00448 "Failed to parse midi buffer");
00449
00450
00451 for (i = 0; i < offset; i++) {
00452 buffer[i] = buffer[buff_size - offset + i];
00453 }
00454 bytes = buff_size - offset;
00455 if (bytes < 1)
00456 CHECK_ERROR(EINVAL, "Bad data stream?");
00457 }
00458
00459 out:
00460 return ret;
00461 }
00462
00463
00464 void
00465 MidiImpl::Trigger
00466 (
00467 IN const char * object,
00468 IN const char * message,
00469 IN const MidiEvent& event
00470 )
00471 throw()
00472 {
00473 std::string output;
00474 const char * p;
00475 char buffer[8];
00476 const status_record *rec;
00477 int i;
00478
00479 ASSERT(object, "NULL object");
00480 ASSERT(message, "NULL message");
00481 ASSERT(m_directory, "Should have a parent directory object");
00482
00483
00484 p = message;
00485 while (*p) {
00486 if ('%' != *p) {
00487
00488 output += *p;
00489 ++p;
00490 } else {
00491
00492 ++p;
00493 switch (*p) {
00494 case '%':
00495
00496 output += '%';
00497 ++p;
00498 break;
00499
00500 case 'c':
00501
00502 sprintf(buffer, "%d", event.GetChannel());
00503 output += buffer;
00504 ++p;
00505 break;
00506
00507 case 's':
00508
00509 rec = s_StatusTable;
00510 while (rec->name) {
00511 if (rec->status == event.GetStatus()) {
00512 output += rec->name;
00513 break;
00514 }
00515 }
00516 ++p;
00517 break;
00518
00519 case 'b':
00520
00521 ++p;
00522 if ('0' == *p)
00523 i = event.GetByte0();
00524 else if ('1' == *p)
00525 i = event.GetByte1();
00526 else {
00527 DPRINTF("Unknown byte escape: %%b%c",
00528 *p);
00529 return;
00530 }
00531 sprintf(buffer, "%d", i);
00532 output += buffer;
00533 ++p;
00534 break;
00535
00536 default:
00537 DPRINTF("Unknown escape code in trigger "
00538 "message: %%%c", *p);
00539 return;
00540 }
00541 }
00542 }
00543
00544 DPRINTF("message '%s' --> '%s'", message, output.c_str());
00545
00546
00547 m_directory->SendMessage(object, output.c_str());
00548 }
00549
00550
00551
00552
00553
00554
00555
00556
00557 error_t
00558 CreateMidiObject
00559 (
00560 OUT smart_ptr<objdir::LocalObject>& midi
00561 )
00562 throw()
00563 {
00564 error_t error = 0;
00565 smart_ptr<MidiImpl> local;
00566
00567
00568 local = new MidiImpl;
00569 ASSERT(local, "Should have midi object");
00570
00571
00572 CHECK_ERROR(local->Initialize(),
00573 "Failed to initialize MIDI media object");
00574
00575
00576 midi = local;
00577 ASSERT(2 == midi.get_ref_count(), "Bad ref count");
00578
00579 out:
00580 return error;
00581 }
00582
00583 };