chris@160: /** chris@160: * Copyright (c) 2014, 2015, Enzien Audio Ltd. chris@160: * chris@160: * Permission to use, copy, modify, and/or distribute this software for any chris@160: * purpose with or without fee is hereby granted, provided that the above chris@160: * copyright notice and this permission notice appear in all copies. chris@160: * chris@160: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH chris@160: * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY chris@160: * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, chris@160: * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM chris@160: * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR chris@160: * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR chris@160: * PERFORMANCE OF THIS SOFTWARE. chris@160: */ chris@160: chris@160: #include "HvMessage.h" chris@160: chris@160: HvMessage *msg_init(HvMessage *m, hv_size_t numElements, hv_uint32_t timestamp) { chris@160: m->timestamp = timestamp; chris@160: m->numElements = (hv_uint16_t) numElements; chris@160: m->numBytes = (hv_uint16_t) msg_getByteSize(numElements); chris@160: return m; chris@160: } chris@160: chris@160: HvMessage *msg_initWithFloat(HvMessage *m, hv_uint32_t timestamp, float f) { chris@160: m->timestamp = timestamp; chris@160: m->numElements = 1; chris@160: m->numBytes = sizeof(HvMessage); chris@160: msg_setFloat(m, 0, f); chris@160: return m; chris@160: } chris@160: chris@160: HvMessage *msg_initWithBang(HvMessage *m, hv_uint32_t timestamp) { chris@160: m->timestamp = timestamp; chris@160: m->numElements = 1; chris@160: m->numBytes = sizeof(HvMessage); chris@160: msg_setBang(m, 0); chris@160: return m; chris@160: } chris@160: chris@160: HvMessage *msg_initWithSymbol(HvMessage *m, hv_uint32_t timestamp, char *s) { chris@160: m->timestamp = timestamp; chris@160: m->numElements = 1; chris@160: m->numBytes = sizeof(HvMessage); chris@160: msg_setSymbol(m, 0, s); chris@160: return m; chris@160: } chris@160: chris@160: HvMessage *msg_initWithHash(HvMessage *m, hv_uint32_t timestamp, hv_uint32_t h) { chris@160: m->timestamp = timestamp; chris@160: m->numElements = 1; chris@160: m->numBytes = sizeof(HvMessage); chris@160: msg_setHash(m, 0, h); chris@160: return m; chris@160: } chris@160: chris@160: HvMessage *msg_initV(HvMessage *const m, const hv_uint32_t timestamp, const char *format, ...) { chris@160: va_list ap; chris@160: va_start(ap, format); chris@160: chris@160: const int numElem = (int) hv_strlen(format); chris@160: msg_init(m, numElem, timestamp); chris@160: for (int i = 0; i < numElem; i++) { chris@160: switch (format[i]) { chris@160: case 'b': msg_setBang(m,i); break; chris@160: case 'f': msg_setFloat(m, i, (float) va_arg(ap, double)); break; chris@160: case 's': msg_setSymbol(m, i, (char *) va_arg(ap, char *)); break; chris@160: case 'h': // hash not supported chris@160: default: break; chris@160: } chris@160: } chris@160: va_end(ap); chris@160: chris@160: return m; chris@160: } chris@160: chris@160: hv_size_t msg_getNumHeapBytes(const HvMessage *m) { chris@160: // get the size of all symbol elements chris@160: hv_size_t rsizeofsym = 0; chris@160: for (int i = 0; i < msg_getNumElements(m); ++i) { chris@160: if (msg_isSymbol(m,i)) { chris@160: rsizeofsym += (hv_size_t) hv_strlen(msg_getSymbol(m,i)) + 1; // +1 to allow for trailing '\0' chris@160: } chris@160: } chris@160: chris@160: // the total byte size on the heap chris@160: return (msg_getByteSize(msg_getNumElements(m)) + rsizeofsym); chris@160: } chris@160: chris@160: void msg_copyToBuffer(const HvMessage *m, char *buffer, hv_size_t len) { chris@160: HvMessage *r = (HvMessage *) buffer; chris@160: chris@160: // assert that the message is not already larger than the length of the buffer chris@160: hv_assert(msg_getNumBytes(m) <= len); chris@160: chris@160: // copy the basic message to the buffer chris@160: hv_memcpy(r, m, msg_getNumBytes(m)); chris@160: chris@160: hv_size_t len_r = msg_getNumBytes(m); chris@160: chris@160: char *p = buffer + msg_getByteSize(msg_getNumElements(m)); // points to the end of the base message chris@160: for (int i = 0; i < msg_getNumElements(m); ++i) { chris@160: if (msg_isSymbol(m,i)) { chris@160: const hv_size_t symLen = (hv_size_t) hv_strlen(msg_getSymbol(m,i)) + 1; // include the trailing null char chris@160: hv_assert(len_r + symLen <= len); // stay safe! chris@160: hv_strncpy(p, msg_getSymbol(m,i), symLen); chris@160: msg_setSymbol(r, i, p); chris@160: p += symLen; chris@160: len_r += symLen; chris@160: } chris@160: } chris@160: chris@160: r->numBytes = (hv_uint16_t) len_r; // update the message size in memory chris@160: } chris@160: chris@160: // the message is serialised such that all symbol elements are placed in order at the end of the buffer chris@160: HvMessage *msg_copy(const HvMessage *m) { chris@160: const hv_size_t heapSize = msg_getNumHeapBytes(m); chris@160: char *r = (char *) hv_malloc(heapSize); chris@160: msg_copyToBuffer(m, r, heapSize); chris@160: return (HvMessage *) r; chris@160: } chris@160: chris@160: void msg_free(HvMessage *m) { chris@160: hv_free(m); // because heap messages are serialised in memory, a simple call to free releases the message chris@160: } chris@160: chris@160: bool msg_hasFormat(const HvMessage *m, const char *fmt) { chris@160: if (fmt == NULL) return false; chris@160: if (msg_getNumElements(m) != hv_strlen(fmt)) return false; chris@160: for (int i = 0; i < msg_getNumElements(m); i++) { chris@160: switch (fmt[i]) { chris@160: case 'b': if (!msg_isBang(m, i)) return false; break; chris@160: case 'f': if (!msg_isFloat(m, i)) return false; break; chris@160: case 's': if (!msg_isSymbol(m, i)) return false; break; chris@160: case 'h': if (!msg_isHash(m, i)) return false; break; chris@160: default: return false; chris@160: } chris@160: } chris@160: return true; chris@160: } chris@160: chris@160: bool msg_compareSymbol(const HvMessage *m, int i, const char *s) { chris@160: switch (msg_getType(m,i)) { chris@160: case SYMBOL: return !hv_strcmp(msg_getSymbol(m, i), s); chris@160: case HASH: return (msg_getHash(m,i) == msg_symbolToHash(s)); chris@160: default: return false; chris@160: } chris@160: } chris@160: chris@160: bool msg_equalsElement(const HvMessage *m, int i_m, const HvMessage *n, int i_n) { chris@160: if (i_m < msg_getNumElements(m) && i_n < msg_getNumElements(n)) { chris@160: if (msg_getType(m, i_m) == msg_getType(n, i_n)) { chris@160: switch (msg_getType(m, i_m)) { chris@160: case BANG: return true; chris@160: case FLOAT: return (msg_getFloat(m, i_m) == msg_getFloat(n, i_n)); chris@160: case SYMBOL: return msg_compareSymbol(m, i_m, msg_getSymbol(n, i_n)); chris@160: case HASH: return msg_getHash(m,i_m) == msg_getHash(n,i_n); chris@160: default: break; chris@160: } chris@160: } chris@160: } chris@160: return false; chris@160: } chris@160: chris@160: void msg_setElementToFrom(HvMessage *n, int i_n, const HvMessage *const m, int i_m) { chris@160: switch (msg_getType(m, i_m)) { chris@160: case BANG: msg_setBang(n, i_n); break; chris@160: case FLOAT: msg_setFloat(n, i_n, msg_getFloat(m, i_m)); break; chris@160: case SYMBOL: msg_setSymbol(n, i_n, msg_getSymbol(m, i_m)); break; chris@160: case HASH: msg_setHash(n, i_n, msg_getHash(m, i_m)); chris@160: default: break; chris@160: } chris@160: } chris@160: chris@160: hv_uint32_t msg_symbolToHash(const char *s) { chris@160: // this hash is based MurmurHash2 chris@160: // http://en.wikipedia.org/wiki/MurmurHash chris@160: // https://sites.google.com/site/murmurhash/ chris@160: static const unsigned int n = 0x5bd1e995; chris@160: static const int r = 24; chris@160: chris@160: int len = (int) hv_strlen(s); chris@160: hv_uint32_t x = (hv_uint32_t) (len); // seed (0) ^ len chris@160: chris@160: while (len >= 4) { chris@160: hv_uint32_t k = *((hv_uint32_t *)s); chris@160: k *= n; chris@160: k ^= k >> r; chris@160: k *= n; chris@160: x *= n; chris@160: x ^= k; chris@160: s += 4; len -= 4; chris@160: } chris@160: chris@160: switch(len) { chris@160: case 3: x ^= s[2] << 16; chris@160: case 2: x ^= s[1] << 8; chris@160: case 1: x ^= s[0]; x *= n; chris@160: default: break; chris@160: } chris@160: chris@160: x ^= x >> 13; chris@160: x *= n; chris@160: x ^= x >> 15; chris@160: chris@160: return x; chris@160: } chris@160: chris@160: hv_uint32_t msg_getHash(const HvMessage *const m, int i) { chris@160: hv_assert(i < msg_getNumElements(m)); // invalid index chris@160: switch (msg_getType(m,i)) { chris@160: case BANG: return 0xFFFFFFFF; chris@160: case FLOAT: { chris@160: float f = msg_getFloat(m,i); chris@160: return *((hv_uint32_t *) &f); chris@160: } chris@160: case SYMBOL: return msg_symbolToHash(msg_getSymbol(m,i)); chris@160: case HASH: return (&(m->elem)+i)->data.h; chris@160: default: return 0; chris@160: } chris@160: } chris@160: chris@160: char *msg_toString(const HvMessage *m) { chris@160: hv_assert(msg_getNumElements(m) > 0); chris@160: int *len = (int *) hv_alloca(msg_getNumElements(m)*sizeof(int)); chris@160: int size = 0; // the total length of our final buffer chris@160: chris@160: // loop through every element in our list of atoms chris@160: // first loop figures out how long our buffer should be chris@160: for (int i = 0; i < msg_getNumElements(m); i++) { chris@160: // length of our string is each atom plus a space, or \0 on the end chris@160: switch (msg_getType(m, i)) { chris@160: case BANG: len[i] = hv_snprintf(NULL, 0, "%s", "bang") + 1; break; chris@160: case FLOAT: len[i] = hv_snprintf(NULL, 0, "%g", msg_getFloat(m, i)) + 1; break; chris@160: case SYMBOL: len[i] = hv_snprintf(NULL, 0, "%s", msg_getSymbol(m, i)) + 1; break; chris@160: case HASH: len[i] = hv_snprintf(NULL, 0, "0x%X", msg_getHash(m, i)) + 1; break; chris@160: default: break; chris@160: } chris@160: size += len[i]; chris@160: } chris@160: chris@160: hv_assert(size > 0); chris@160: chris@160: // now we do the piecewise concatenation into our final string chris@160: // the final buffer we will pass back after concatenating all strings - user should free it chris@160: char *finalString = (char *) hv_malloc(size*sizeof(char)); chris@160: int pos = 0; chris@160: for (int i = 0; i < msg_getNumElements(m); i++) { chris@160: // put a string representation of each atom into the final string chris@160: switch (msg_getType(m, i)) { chris@160: case BANG: hv_snprintf(finalString+pos, len[i], "%s", "bang"); break; chris@160: case FLOAT: hv_snprintf(finalString+pos, len[i], "%g", msg_getFloat(m, i)); break; chris@160: case SYMBOL: hv_snprintf(finalString+pos, len[i], "%s", msg_getSymbol(m, i)); break; chris@160: case HASH: hv_snprintf(finalString+pos, len[i], "0x%X", msg_getHash(m, i)); break; chris@160: default: break; chris@160: } chris@160: pos += len[i]; chris@160: finalString[pos-1] = 32; // ASCII space chris@160: } chris@160: finalString[size-1] = '\0'; // ensure that the string is null terminated chris@160: return finalString; chris@160: } chris@160: chris@160: /* chris@160: * TODO(mhroth): unnecessary for now chris@160: bool msg_resolveDollarArguments(HvMessage *m, HvMessage *n, int z, char *buf, hv_size_t len, const char *args, ...) { chris@160: va_list ap; chris@160: va_start(ap, args); chris@160: chris@160: hv_memset(buf, 0, len); // clear the buffer chris@160: hv_size_t j = 0; // position in buffer chris@160: const hv_size_t numArgs = hv_strlen(args); // the number of arguments chris@160: chris@160: // if there is only one argument then the result has the chance of being a number, otherwise no chris@160: bool isNumber = (numArgs == 1); chris@160: chris@160: for (hv_size_t i = 0; i < numArgs; ++i) { chris@160: switch (args[i]) { chris@160: case 'i': { // a message index chris@160: const int index = (int) va_arg(ap, int); chris@160: if (index < 0) { chris@160: // $0 always resolve to "0" chris@160: const hv_size_t x = 1; chris@160: if (x < len-j) { // always < in order to allow for trailing \0 chris@160: j += snprintf(buf+j, len-j, "0"); chris@160: } chris@160: } else { chris@160: switch (msg_getType(m, index)) { chris@160: default: chris@160: case BANG: break; // this case should never happen chris@160: case FLOAT: { chris@160: const hv_size_t x = snprintf(NULL, 0, "%g", msg_getFloat(m,index)); chris@160: if (x < len-j) { // ensure that the buffer is big enough chris@160: j += snprintf(buf+j, len-j, "%g", msg_getFloat(m,index)); chris@160: } chris@160: break; chris@160: } chris@160: case SYMBOL: { chris@160: const hv_size_t x = snprintf(NULL, 0, "%s", msg_getSymbol(m,index)); chris@160: if (x < len-j) { chris@160: j += snprintf(buf+j, len-j, "%s", msg_getSymbol(m,index)); chris@160: isNumber = false; chris@160: } chris@160: break; chris@160: } chris@160: } chris@160: } chris@160: break; chris@160: } chris@160: case 's': { // a string chris@160: const char *s = (char *) va_arg(ap, char *); chris@160: const hv_size_t x = snprintf(NULL, 0, "%s", s); chris@160: if (x <= len-j) { chris@160: j += snprintf(buf+j, len-j, "%s", s); chris@160: isNumber = false; chris@160: } chris@160: break; chris@160: } chris@160: default: break; chris@160: } chris@160: } chris@160: chris@160: if (isNumber) { chris@160: msg_setFloat(n,z,(float) atof(buf)); chris@160: } else { chris@160: msg_setSymbol(n,z,buf); chris@160: } chris@160: chris@160: va_end(ap); chris@160: chris@160: return !isNumber; chris@160: } chris@160: */