midi_obj.cpp

Go to the documentation of this file.
00001 /*
00002  * midi_obj.cpp
00003  *
00004  * Copyright 2002 Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  * Implements the midi object for messaging in a distributed object directory.
00008  */
00009 
00010 // includes --------------------------------------------------------------------
00011 #include "midi_obj.h"                   // always include our own header first!
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"                // event queue
00022 #include "common/handler.h"             // command handler macros
00023 
00024 #include "midi.h"                       // MIDI event parser
00025 
00026 // namespace
00027 namespace media
00028 {
00029 
00030 
00031 // statics
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         // must be last!
00051         STAT_ENTRY(Invalid,     NULL)
00052 };
00053 
00054 
00055 ////////////////////////////////////////////////////////////////////////////////
00056 //
00057 //      static helpers
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         // skip whitespace
00072         while (*str && isspace(*str)) { ++str; }
00073 
00074         // copy characters
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                 // name match?
00092                 if (!strcmp(status, p->name)) {
00093                         break;
00094                 }
00095 
00096                 // next record
00097                 ++p;
00098         }
00099 
00100         return p->status;
00101 }
00102 
00103 
00104 /*
00105  * class MidiImpl
00106  *
00107  * Class that implements the LocalObject interface for midi objects.
00108  */
00109 class MidiImpl : public objdir::LocalObject,
00110                  public MidiReceiver
00111 {
00112 public:
00113         // constructor, destructor ---------------------------------------------
00114         MidiImpl(void) throw();
00115         ~MidiImpl(void) throw();
00116 
00117         // public class methods ------------------------------------------------
00118         error_t Initialize(void) throw();
00119 
00120         // class objdir::LocalObject interface methods -------------------------
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         // media::MidiReceiver class interface methods -------------------------
00126         virtual error_t NotifyEvent(IN const MidiEvent& event) throw();
00127 
00128 private:
00129         // private typedefs ----------------------------------------------------
00130         struct trigger_info {
00131                 MidiEvent::eStatus      status;         // status
00132                 int                     channel;        // MIDI 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         // private methods -----------------------------------------------------
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         // command handlers ----------------------------------------------------
00146         HANDLER_DECL(MidiImpl, AddTrigger)
00147 
00148         // private member data -------------------------------------------------
00149         objdir::Directory      *m_directory;    // directory that owns us
00150         pthread_t               m_listen;       // listening for MIDI input
00151         MapTriggers             m_triggers;     // registered triggers
00152         int                     m_fdMIDI;       // file descriptor for MIDI i/o
00153 
00154         // private member data (non-DWORD aligned...) --------------------------
00155         bool                    m_shutdown;     // shutting down?
00156 
00157         // static private data -------------------------------------------------
00158         HANDLER_TABLE_DECL(MidiImpl,    ms_handlers)
00159 };
00160 
00161 
00162 
00163 ////////////////////////////////////////////////////////////////////////////////
00164 //
00165 //      MidiImpl -- static member definitions
00166 //
00167 ////////////////////////////////////////////////////////////////////////////////
00168 
00169 #define ENTRY(cmd, handler) HANDLER_ENTRY(MidiImpl, cmd, handler)
00170 
00171 HANDLER_TABLE_START(MidiImpl, ms_handlers)
00172         // command_string               handler_name
00173         ENTRY("add_trigger",            AddTrigger)
00174 HANDLER_TABLE_END()
00175 
00176 
00177 
00178 
00179 ////////////////////////////////////////////////////////////////////////////////
00180 //
00181 //      MidiImpl - constructor, destructor
00182 //
00183 ////////////////////////////////////////////////////////////////////////////////
00184 
00185 // c'tor
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 // d'tor
00197 MidiImpl::~MidiImpl(void) throw()
00198 {
00199         void *ret;
00200 
00201         DPRINTF("MidiImpl destructor...");
00202 
00203         // NOTE: close down midi thread first
00204         // tell thread we're shutting down, and wait for it
00205         if (m_listen) {
00206                 m_shutdown = true;
00207                 pthread_join(m_listen, &ret);
00208         }
00209 
00210         // close fd's
00211         if (m_fdMIDI > 0)
00212                 close(m_fdMIDI);
00213 }
00214 
00215 
00216 
00217 ////////////////////////////////////////////////////////////////////////////////
00218 //
00219 //      MidiImpl - public class methods
00220 //
00221 ////////////////////////////////////////////////////////////////////////////////
00222 
00223 error_t
00224 MidiImpl::Initialize(void)
00225 throw()
00226 {
00227         error_t error = 0;
00228 
00229         // open MIDI input
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         // create thread to listen for MIDI input
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 //      MidiImpl - objdir::LocalObject class interface methods
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 //      MidiImpl - MidiReceiver class interface methods
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         // loop through triggers, see if any should fire
00300         for (iter = m_triggers.begin(); iter != m_triggers.end(); ++iter) {
00301                 // get trigger pointer
00302                 trigger = iter->second;
00303 
00304                 // see if status + channel match
00305                 if (event.GetStatus() == trigger->status &&
00306                     event.GetChannel() == trigger->channel) {
00307                         // does byte0 match?
00308                         if (-1 == trigger->byte0 ||
00309                             event.GetByte0() == trigger->byte0) {
00310                                 // does byte1 match?
00311                                 if (-1 == trigger->byte1 ||
00312                                     event.GetByte1() == trigger->byte1) {
00313                                         // matches!
00314                                         this->Trigger(trigger->object.c_str(),
00315                                             trigger->message.c_str(), event);
00316                                 }
00317                         }
00318                 }
00319         }
00320 
00321 //out:
00322         return error;
00323 }
00324 
00325 
00326 
00327 ////////////////////////////////////////////////////////////////////////////////
00328 //
00329 //      MidiImpl -- command handlers
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         // allocate new trigger
00348         trigger = new trigger_info;
00349         ASSERT(trigger, "Should have valid info");
00350 
00351         // trigger name
00352         get_token(cmd, name);
00353 
00354         // status code
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         // midi channel
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         // byte0
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         // byte1
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         // destination object
00387         get_token(cmd, trigger->object);
00388 
00389         // message
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         // add to map
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 //      MidiImpl - private helper methods
00409 //
00410 ////////////////////////////////////////////////////////////////////////////////
00411 
00412 /*
00413  * Function:    MidiImpl::ThreadStart
00414  *
00415  * Description:
00416  *      This is the entry point for the MIDI listening thread.  Just waits for
00417  *      input.
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         // loop until we shut down
00437         // TODO: need a select() here.
00438         offset = 0;
00439         bytes = buff_size;
00440         while (!pThis->m_shutdown) {
00441                 // read any MIDI input
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                 // parse stream for midi events
00447                 CHECK_ERROR(ParseMidi(pThis, buffer, bytes + offset, &offset),
00448                     "Failed to parse midi buffer");
00449 
00450                 // shift data
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         // have to substitute format codes in message
00484         p = message;
00485         while (*p) {
00486                 if ('%' != *p) {
00487                         // not a control code--keep going
00488                         output += *p;
00489                         ++p;
00490                 } else {
00491                         // control code
00492                         ++p;
00493                         switch (*p) {
00494                         case '%':
00495                                 // just an escaped percent sign
00496                                 output += '%';
00497                                 ++p;
00498                                 break;
00499 
00500                         case 'c':
00501                                 // MIDI channel
00502                                 sprintf(buffer, "%d", event.GetChannel());
00503                                 output += buffer;
00504                                 ++p;
00505                                 break;
00506 
00507                         case 's':
00508                                 // status
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                                 // byte value
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         // send formatted output
00547         m_directory->SendMessage(object, output.c_str());
00548 }
00549 
00550 
00551 ////////////////////////////////////////////////////////////////////////////////
00552 //
00553 //      public factory methods
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         // create object
00568         local = new MidiImpl;
00569         ASSERT(local, "Should have midi object");
00570 
00571         // initialize
00572         CHECK_ERROR(local->Initialize(),
00573             "Failed to initialize MIDI media object");
00574 
00575         // hand over to caller
00576         midi = local;           // caller takes ownership
00577         ASSERT(2 == midi.get_ref_count(), "Bad ref count");
00578 
00579 out:
00580         return error;
00581 }
00582 
00583 };      // end of media namespace