| chris@162 | 1 /** | 
| chris@162 | 2  * Copyright (c) 2014, 2015, Enzien Audio Ltd. | 
| chris@162 | 3  * | 
| chris@162 | 4  * Permission to use, copy, modify, and/or distribute this software for any | 
| chris@162 | 5  * purpose with or without fee is hereby granted, provided that the above | 
| chris@162 | 6  * copyright notice and this permission notice appear in all copies. | 
| chris@162 | 7  * | 
| chris@162 | 8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | 
| chris@162 | 9  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | 
| chris@162 | 10  * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | 
| chris@162 | 11  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | 
| chris@162 | 12  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | 
| chris@162 | 13  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | 
| chris@162 | 14  * PERFORMANCE OF THIS SOFTWARE. | 
| chris@162 | 15  */ | 
| chris@162 | 16 | 
| chris@162 | 17 #include "HvMessage.h" | 
| chris@162 | 18 | 
| chris@162 | 19 HvMessage *msg_init(HvMessage *m, hv_size_t numElements, hv_uint32_t timestamp) { | 
| chris@162 | 20   m->timestamp = timestamp; | 
| chris@162 | 21   m->numElements = (hv_uint16_t) numElements; | 
| chris@162 | 22   m->numBytes = (hv_uint16_t) msg_getByteSize(numElements); | 
| chris@162 | 23   return m; | 
| chris@162 | 24 } | 
| chris@162 | 25 | 
| chris@162 | 26 HvMessage *msg_initWithFloat(HvMessage *m, hv_uint32_t timestamp, float f) { | 
| chris@162 | 27   m->timestamp = timestamp; | 
| chris@162 | 28   m->numElements = 1; | 
| chris@162 | 29   m->numBytes = sizeof(HvMessage); | 
| chris@162 | 30   msg_setFloat(m, 0, f); | 
| chris@162 | 31   return m; | 
| chris@162 | 32 } | 
| chris@162 | 33 | 
| chris@162 | 34 HvMessage *msg_initWithBang(HvMessage *m, hv_uint32_t timestamp) { | 
| chris@162 | 35   m->timestamp = timestamp; | 
| chris@162 | 36   m->numElements = 1; | 
| chris@162 | 37   m->numBytes = sizeof(HvMessage); | 
| chris@162 | 38   msg_setBang(m, 0); | 
| chris@162 | 39   return m; | 
| chris@162 | 40 } | 
| chris@162 | 41 | 
| chris@162 | 42 HvMessage *msg_initWithSymbol(HvMessage *m, hv_uint32_t timestamp, char *s) { | 
| chris@162 | 43   m->timestamp = timestamp; | 
| chris@162 | 44   m->numElements = 1; | 
| chris@162 | 45   m->numBytes = sizeof(HvMessage); | 
| chris@162 | 46   msg_setSymbol(m, 0, s); | 
| chris@162 | 47   return m; | 
| chris@162 | 48 } | 
| chris@162 | 49 | 
| chris@162 | 50 HvMessage *msg_initWithHash(HvMessage *m, hv_uint32_t timestamp, hv_uint32_t h) { | 
| chris@162 | 51   m->timestamp = timestamp; | 
| chris@162 | 52   m->numElements = 1; | 
| chris@162 | 53   m->numBytes = sizeof(HvMessage); | 
| chris@162 | 54   msg_setHash(m, 0, h); | 
| chris@162 | 55   return m; | 
| chris@162 | 56 } | 
| chris@162 | 57 | 
| chris@162 | 58 HvMessage *msg_initV(HvMessage *const m, const hv_uint32_t timestamp, const char *format, ...) { | 
| chris@162 | 59   va_list ap; | 
| chris@162 | 60   va_start(ap, format); | 
| chris@162 | 61 | 
| chris@162 | 62   const int numElem = (int) hv_strlen(format); | 
| chris@162 | 63   msg_init(m, numElem, timestamp); | 
| chris@162 | 64   for (int i = 0; i < numElem; i++) { | 
| chris@162 | 65     switch (format[i]) { | 
| chris@162 | 66       case 'b': msg_setBang(m,i); break; | 
| chris@162 | 67       case 'f': msg_setFloat(m, i, (float) va_arg(ap, double)); break; | 
| chris@162 | 68       case 's': msg_setSymbol(m, i, (char *) va_arg(ap, char *)); break; | 
| chris@162 | 69       case 'h': // hash not supported | 
| chris@162 | 70       default: break; | 
| chris@162 | 71     } | 
| chris@162 | 72   } | 
| chris@162 | 73   va_end(ap); | 
| chris@162 | 74 | 
| chris@162 | 75   return m; | 
| chris@162 | 76 } | 
| chris@162 | 77 | 
| chris@162 | 78 hv_size_t msg_getNumHeapBytes(const HvMessage *m) { | 
| chris@162 | 79   // get the size of all symbol elements | 
| chris@162 | 80   hv_size_t rsizeofsym = 0; | 
| chris@162 | 81   for (int i = 0; i < msg_getNumElements(m); ++i) { | 
| chris@162 | 82     if (msg_isSymbol(m,i)) { | 
| chris@162 | 83       rsizeofsym += (hv_size_t) hv_strlen(msg_getSymbol(m,i)) + 1; // +1 to allow for trailing '\0' | 
| chris@162 | 84     } | 
| chris@162 | 85   } | 
| chris@162 | 86 | 
| chris@162 | 87   // the total byte size on the heap | 
| chris@162 | 88   return (msg_getByteSize(msg_getNumElements(m)) + rsizeofsym); | 
| chris@162 | 89 } | 
| chris@162 | 90 | 
| chris@162 | 91 void msg_copyToBuffer(const HvMessage *m, char *buffer, hv_size_t len) { | 
| chris@162 | 92   HvMessage *r = (HvMessage *) buffer; | 
| chris@162 | 93 | 
| chris@162 | 94   // assert that the message is not already larger than the length of the buffer | 
| chris@162 | 95   hv_assert(msg_getNumBytes(m) <= len); | 
| chris@162 | 96 | 
| chris@162 | 97   // copy the basic message to the buffer | 
| chris@162 | 98   hv_memcpy(r, m, msg_getNumBytes(m)); | 
| chris@162 | 99 | 
| chris@162 | 100   hv_size_t len_r = msg_getNumBytes(m); | 
| chris@162 | 101 | 
| chris@162 | 102   char *p = buffer + msg_getByteSize(msg_getNumElements(m)); // points to the end of the base message | 
| chris@162 | 103   for (int i = 0; i < msg_getNumElements(m); ++i) { | 
| chris@162 | 104     if (msg_isSymbol(m,i)) { | 
| chris@162 | 105       const hv_size_t symLen = (hv_size_t) hv_strlen(msg_getSymbol(m,i)) + 1; // include the trailing null char | 
| chris@162 | 106       hv_assert(len_r + symLen <= len); // stay safe! | 
| chris@162 | 107       hv_strncpy(p, msg_getSymbol(m,i), symLen); | 
| chris@162 | 108       msg_setSymbol(r, i, p); | 
| chris@162 | 109       p += symLen; | 
| chris@162 | 110       len_r += symLen; | 
| chris@162 | 111     } | 
| chris@162 | 112   } | 
| chris@162 | 113 | 
| chris@162 | 114   r->numBytes = (hv_uint16_t) len_r; // update the message size in memory | 
| chris@162 | 115 } | 
| chris@162 | 116 | 
| chris@162 | 117 // the message is serialised such that all symbol elements are placed in order at the end of the buffer | 
| chris@162 | 118 HvMessage *msg_copy(const HvMessage *m) { | 
| chris@162 | 119   const hv_size_t heapSize = msg_getNumHeapBytes(m); | 
| chris@162 | 120   char *r = (char *) hv_malloc(heapSize); | 
| chris@162 | 121   msg_copyToBuffer(m, r, heapSize); | 
| chris@162 | 122   return (HvMessage *) r; | 
| chris@162 | 123 } | 
| chris@162 | 124 | 
| chris@162 | 125 void msg_free(HvMessage *m) { | 
| chris@162 | 126   hv_free(m); // because heap messages are serialised in memory, a simple call to free releases the message | 
| chris@162 | 127 } | 
| chris@162 | 128 | 
| chris@162 | 129 bool msg_hasFormat(const HvMessage *m, const char *fmt) { | 
| chris@162 | 130   if (fmt == NULL) return false; | 
| chris@162 | 131   if (msg_getNumElements(m) != hv_strlen(fmt)) return false; | 
| chris@162 | 132   for (int i = 0; i < msg_getNumElements(m); i++) { | 
| chris@162 | 133     switch (fmt[i]) { | 
| chris@162 | 134       case 'b': if (!msg_isBang(m, i)) return false; break; | 
| chris@162 | 135       case 'f': if (!msg_isFloat(m, i)) return false; break; | 
| chris@162 | 136       case 's': if (!msg_isSymbol(m, i)) return false; break; | 
| chris@162 | 137       case 'h': if (!msg_isHash(m, i)) return false; break; | 
| chris@162 | 138       default: return false; | 
| chris@162 | 139     } | 
| chris@162 | 140   } | 
| chris@162 | 141   return true; | 
| chris@162 | 142 } | 
| chris@162 | 143 | 
| chris@162 | 144 bool msg_compareSymbol(const HvMessage *m, int i, const char *s) { | 
| chris@162 | 145   switch (msg_getType(m,i)) { | 
| chris@162 | 146     case SYMBOL: return !hv_strcmp(msg_getSymbol(m, i), s); | 
| chris@162 | 147     case HASH: return (msg_getHash(m,i) == msg_symbolToHash(s)); | 
| chris@162 | 148     default: return false; | 
| chris@162 | 149   } | 
| chris@162 | 150 } | 
| chris@162 | 151 | 
| chris@162 | 152 bool msg_equalsElement(const HvMessage *m, int i_m, const HvMessage *n, int i_n) { | 
| chris@162 | 153   if (i_m < msg_getNumElements(m) && i_n < msg_getNumElements(n)) { | 
| chris@162 | 154     if (msg_getType(m, i_m) == msg_getType(n, i_n)) { | 
| chris@162 | 155       switch (msg_getType(m, i_m)) { | 
| chris@162 | 156         case BANG: return true; | 
| chris@162 | 157         case FLOAT: return (msg_getFloat(m, i_m) == msg_getFloat(n, i_n)); | 
| chris@162 | 158         case SYMBOL: return msg_compareSymbol(m, i_m, msg_getSymbol(n, i_n)); | 
| chris@162 | 159         case HASH: return msg_getHash(m,i_m) == msg_getHash(n,i_n); | 
| chris@162 | 160         default: break; | 
| chris@162 | 161       } | 
| chris@162 | 162     } | 
| chris@162 | 163   } | 
| chris@162 | 164   return false; | 
| chris@162 | 165 } | 
| chris@162 | 166 | 
| chris@162 | 167 void msg_setElementToFrom(HvMessage *n, int i_n, const HvMessage *const m, int i_m) { | 
| chris@162 | 168   switch (msg_getType(m, i_m)) { | 
| chris@162 | 169     case BANG: msg_setBang(n, i_n); break; | 
| chris@162 | 170     case FLOAT: msg_setFloat(n, i_n, msg_getFloat(m, i_m)); break; | 
| chris@162 | 171     case SYMBOL: msg_setSymbol(n, i_n, msg_getSymbol(m, i_m)); break; | 
| chris@162 | 172     case HASH: msg_setHash(n, i_n, msg_getHash(m, i_m)); | 
| chris@162 | 173     default: break; | 
| chris@162 | 174   } | 
| chris@162 | 175 } | 
| chris@162 | 176 | 
| chris@162 | 177 hv_uint32_t msg_symbolToHash(const char *s) { | 
| chris@162 | 178   // this hash is based MurmurHash2 | 
| chris@162 | 179   // http://en.wikipedia.org/wiki/MurmurHash | 
| chris@162 | 180   // https://sites.google.com/site/murmurhash/ | 
| chris@162 | 181   static const unsigned int n = 0x5bd1e995; | 
| chris@162 | 182   static const int r = 24; | 
| chris@162 | 183 | 
| chris@162 | 184   int len = (int) hv_strlen(s); | 
| chris@162 | 185   hv_uint32_t x = (hv_uint32_t) (len); // seed (0) ^ len | 
| chris@162 | 186 | 
| chris@162 | 187   while (len >= 4) { | 
| chris@162 | 188     hv_uint32_t k = *((hv_uint32_t *)s); | 
| chris@162 | 189     k *= n; | 
| chris@162 | 190     k ^= k >> r; | 
| chris@162 | 191     k *= n; | 
| chris@162 | 192     x *= n; | 
| chris@162 | 193     x ^= k; | 
| chris@162 | 194     s += 4; len -= 4; | 
| chris@162 | 195   } | 
| chris@162 | 196 | 
| chris@162 | 197   switch(len) { | 
| chris@162 | 198     case 3: x ^= s[2] << 16; | 
| chris@162 | 199     case 2: x ^= s[1] << 8; | 
| chris@162 | 200     case 1: x ^= s[0]; x *= n; | 
| chris@162 | 201     default: break; | 
| chris@162 | 202   } | 
| chris@162 | 203 | 
| chris@162 | 204   x ^= x >> 13; | 
| chris@162 | 205   x *= n; | 
| chris@162 | 206   x ^= x >> 15; | 
| chris@162 | 207 | 
| chris@162 | 208   return x; | 
| chris@162 | 209 } | 
| chris@162 | 210 | 
| chris@162 | 211 hv_uint32_t msg_getHash(const HvMessage *const m, int i) { | 
| chris@162 | 212   hv_assert(i < msg_getNumElements(m)); // invalid index | 
| chris@162 | 213   switch (msg_getType(m,i)) { | 
| chris@162 | 214     case BANG: return 0xFFFFFFFF; | 
| chris@162 | 215     case FLOAT: { | 
| chris@162 | 216       float f = msg_getFloat(m,i); | 
| chris@162 | 217       return *((hv_uint32_t *) &f); | 
| chris@162 | 218     } | 
| chris@162 | 219     case SYMBOL: return msg_symbolToHash(msg_getSymbol(m,i)); | 
| chris@162 | 220     case HASH: return (&(m->elem)+i)->data.h; | 
| chris@162 | 221     default: return 0; | 
| chris@162 | 222   } | 
| chris@162 | 223 } | 
| chris@162 | 224 | 
| chris@162 | 225 char *msg_toString(const HvMessage *m) { | 
| chris@162 | 226   hv_assert(msg_getNumElements(m) > 0); | 
| chris@162 | 227   int *len = (int *) hv_alloca(msg_getNumElements(m)*sizeof(int)); | 
| chris@162 | 228   int size = 0; // the total length of our final buffer | 
| chris@162 | 229 | 
| chris@162 | 230   // loop through every element in our list of atoms | 
| chris@162 | 231   // first loop figures out how long our buffer should be | 
| chris@162 | 232   for (int i = 0; i < msg_getNumElements(m); i++) { | 
| chris@162 | 233     // length of our string is each atom plus a space, or \0 on the end | 
| chris@162 | 234     switch (msg_getType(m, i)) { | 
| chris@162 | 235       case BANG: len[i] = hv_snprintf(NULL, 0, "%s", "bang") + 1; break; | 
| chris@162 | 236       case FLOAT: len[i] = hv_snprintf(NULL, 0, "%g", msg_getFloat(m, i)) + 1; break; | 
| chris@162 | 237       case SYMBOL: len[i] = hv_snprintf(NULL, 0, "%s", msg_getSymbol(m, i)) + 1; break; | 
| chris@162 | 238       case HASH: len[i] = hv_snprintf(NULL, 0, "0x%X", msg_getHash(m, i)) + 1; break; | 
| chris@162 | 239       default: break; | 
| chris@162 | 240     } | 
| chris@162 | 241     size += len[i]; | 
| chris@162 | 242   } | 
| chris@162 | 243 | 
| chris@162 | 244   hv_assert(size > 0); | 
| chris@162 | 245 | 
| chris@162 | 246   // now we do the piecewise concatenation into our final string | 
| chris@162 | 247   // the final buffer we will pass back after concatenating all strings - user should free it | 
| chris@162 | 248   char *finalString = (char *) hv_malloc(size*sizeof(char)); | 
| chris@162 | 249   int pos = 0; | 
| chris@162 | 250   for (int i = 0; i < msg_getNumElements(m); i++) { | 
| chris@162 | 251     // put a string representation of each atom into the final string | 
| chris@162 | 252     switch (msg_getType(m, i)) { | 
| chris@162 | 253       case BANG: hv_snprintf(finalString+pos, len[i], "%s", "bang"); break; | 
| chris@162 | 254       case FLOAT: hv_snprintf(finalString+pos, len[i], "%g", msg_getFloat(m, i)); break; | 
| chris@162 | 255       case SYMBOL: hv_snprintf(finalString+pos, len[i], "%s", msg_getSymbol(m, i)); break; | 
| chris@162 | 256       case HASH: hv_snprintf(finalString+pos, len[i], "0x%X", msg_getHash(m, i)); break; | 
| chris@162 | 257       default: break; | 
| chris@162 | 258     } | 
| chris@162 | 259     pos += len[i]; | 
| chris@162 | 260     finalString[pos-1] = 32; // ASCII space | 
| chris@162 | 261   } | 
| chris@162 | 262   finalString[size-1] = '\0'; // ensure that the string is null terminated | 
| chris@162 | 263   return finalString; | 
| chris@162 | 264 } | 
| chris@162 | 265 | 
| chris@162 | 266 /* | 
| chris@162 | 267  * TODO(mhroth): unnecessary for now | 
| chris@162 | 268 bool msg_resolveDollarArguments(HvMessage *m, HvMessage *n, int z, char *buf, hv_size_t len, const char *args, ...) { | 
| chris@162 | 269   va_list ap; | 
| chris@162 | 270   va_start(ap, args); | 
| chris@162 | 271 | 
| chris@162 | 272   hv_memset(buf, 0, len); // clear the buffer | 
| chris@162 | 273   hv_size_t j = 0; // position in buffer | 
| chris@162 | 274   const hv_size_t numArgs = hv_strlen(args); // the number of arguments | 
| chris@162 | 275 | 
| chris@162 | 276   // if there is only one argument then the result has the chance of being a number, otherwise no | 
| chris@162 | 277   bool isNumber = (numArgs == 1); | 
| chris@162 | 278 | 
| chris@162 | 279   for (hv_size_t i = 0; i < numArgs; ++i) { | 
| chris@162 | 280     switch (args[i]) { | 
| chris@162 | 281       case 'i': { // a message index | 
| chris@162 | 282         const int index = (int) va_arg(ap, int); | 
| chris@162 | 283         if (index < 0) { | 
| chris@162 | 284           // $0 always resolve to "0" | 
| chris@162 | 285           const hv_size_t x = 1; | 
| chris@162 | 286           if (x < len-j) { // always < in order to allow for trailing \0 | 
| chris@162 | 287             j += snprintf(buf+j, len-j, "0"); | 
| chris@162 | 288           } | 
| chris@162 | 289         } else { | 
| chris@162 | 290           switch (msg_getType(m, index)) { | 
| chris@162 | 291             default: | 
| chris@162 | 292             case BANG: break; // this case should never happen | 
| chris@162 | 293             case FLOAT: { | 
| chris@162 | 294               const hv_size_t x = snprintf(NULL, 0, "%g", msg_getFloat(m,index)); | 
| chris@162 | 295               if (x < len-j) { // ensure that the buffer is big enough | 
| chris@162 | 296                 j += snprintf(buf+j, len-j, "%g", msg_getFloat(m,index)); | 
| chris@162 | 297               } | 
| chris@162 | 298               break; | 
| chris@162 | 299             } | 
| chris@162 | 300             case SYMBOL: { | 
| chris@162 | 301               const hv_size_t x = snprintf(NULL, 0, "%s", msg_getSymbol(m,index)); | 
| chris@162 | 302               if (x < len-j) { | 
| chris@162 | 303                 j += snprintf(buf+j, len-j, "%s", msg_getSymbol(m,index)); | 
| chris@162 | 304                 isNumber = false; | 
| chris@162 | 305               } | 
| chris@162 | 306               break; | 
| chris@162 | 307             } | 
| chris@162 | 308           } | 
| chris@162 | 309         } | 
| chris@162 | 310         break; | 
| chris@162 | 311       } | 
| chris@162 | 312       case 's': { // a string | 
| chris@162 | 313         const char *s = (char *) va_arg(ap, char *); | 
| chris@162 | 314         const hv_size_t x = snprintf(NULL, 0, "%s", s); | 
| chris@162 | 315         if (x <= len-j) { | 
| chris@162 | 316           j += snprintf(buf+j, len-j, "%s", s); | 
| chris@162 | 317           isNumber = false; | 
| chris@162 | 318         } | 
| chris@162 | 319         break; | 
| chris@162 | 320       } | 
| chris@162 | 321       default: break; | 
| chris@162 | 322     } | 
| chris@162 | 323   } | 
| chris@162 | 324 | 
| chris@162 | 325   if (isNumber) { | 
| chris@162 | 326     msg_setFloat(n,z,(float) atof(buf)); | 
| chris@162 | 327   } else { | 
| chris@162 | 328     msg_setSymbol(n,z,buf); | 
| chris@162 | 329   } | 
| chris@162 | 330 | 
| chris@162 | 331   va_end(ap); | 
| chris@162 | 332 | 
| chris@162 | 333   return !isNumber; | 
| chris@162 | 334 } | 
| chris@162 | 335 */ |