/* ---- * ---- file : sysex.c * ---- author : bsp * ---- legal : Distributed under terms of the MIT LICENSE (MIT). * ---- * ---- Permission is hereby granted, free of charge, to any person obtaining a copy * ---- of this software and associated documentation files (the "Software"), to deal * ---- in the Software without restriction, including without limitation the rights * ---- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * ---- copies of the Software, and to permit persons to whom the Software is * ---- furnished to do so, subject to the following conditions: * ---- * ---- The above copyright notice and this permission notice shall be included in * ---- all copies or substantial portions of the Software. * ---- * ---- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * ---- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * ---- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * ---- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * ---- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * ---- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * ---- THE SOFTWARE. * ---- * ---- info : This is part of the "libanalogrytm" package. * ---- * ---- changed: 01Aug2014, 02Aug2014, 04Aug2014 * ---- * ---- */ #include "types.h" #include "debug.h" #include "error.h" #include "sysex.h" /* ---------------------------------------------------------------------------- ar_sysex_meta_get_request_id */ sU8 ar_sysex_meta_get_request_id(const ar_sysex_meta_t *_meta) { sU8 r; if(_meta->obj_nr > 127) { /* Request workbuffer ("X") */ r = AR_SYSEX_REQUESTX_ID_BASE + _meta->obj_type; } else { r = AR_SYSEX_REQUEST_ID_BASE + _meta->obj_type; } return r; } /* ---------------------------------------------------------------------------- ar_sysex_request */ ar_error_t ar_sysex_request(sU8 *_dstBuf, const ar_sysex_meta_t *_meta) { ar_error_t ret; if( (_meta->obj_type >= AR_TYPE_KIT) && (_meta->obj_type <= AR_TYPE_GLOBAL) ) { _dstBuf[ 0] = 0xF0u; _dstBuf[ 1] = 0x00u; _dstBuf[ 2] = 0x20u; _dstBuf[ 3] = 0x3Cu; _dstBuf[ 4] = 0x07u; _dstBuf[ 5] = (_meta->dev_id & 15u); _dstBuf[ 6] = ar_sysex_meta_get_request_id(_meta); _dstBuf[ 7] = _meta->container_version.b.hi; _dstBuf[ 8] = _meta->container_version.b.lo; _dstBuf[ 9] = (_meta->obj_nr & 127u); _dstBuf[10] = 0x00u; _dstBuf[11] = 0x00u; _dstBuf[12] = 0x00u; _dstBuf[13] = 0x05u; _dstBuf[14] = 0xF7u; ret = AR_ERR_OK; } else { ret = AR_ERR_INVALID_OBJ_TYPE; } return ret; } /* ---------------------------------------------------------------------------- ar_sysex_read_next_byte */ ar_error_t ar_sysex_read_next_byte(const sU8 **_bufPtr, sU32 *_bufSize, sU8 *_retByte) { ar_error_t ret; const sU8 *b = *_bufPtr; sU32 sz = *_bufSize; *_retByte = 0xFFu; for(;;) { if(_bufSize > 0) { sU8 c = *b++; sz--; if(c < 0xF8u) { if(0xF7u == c) { /* End of sysex message */ ret = AR_ERR_END_OF_MSG; break; } else { if(c <= 0x7F) { /* Regular sysex message byte */ *_retByte = c; ret = AR_ERR_OK; break; } else { /* Premature start of next regular MIDI message. Abort sysex. */ ret = AR_ERR_ABORT; /* Rewind */ b--; sz++; break; } } } else { /* System realtime message, skip */ } } else { ret = AR_ERR_SHORT_READ; break; } } *_bufPtr = b; *_bufSize = sz; return ret; } /* ---------------------------------------------------------------------------- ar_sysex_parse_dump_header */ ar_error_t ar_sysex_parse_dump_header(const sU8**_srcBuf, sU32 *_srcBufSize, ar_sysex_meta_t *_retMeta) { ar_error_t ret; sU8 c; if(0xF0u == (*_srcBuf)[0]) { (*_srcBuf)++; (*_srcBufSize)--; ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if(0 == c) /* check extended manufacturer id */ { ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if(AR_ELEKTRON_MANUFACTURER_ID_1 == c) { ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if(AR_ELEKTRON_MANUFACTURER_ID_2 == c) { ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if(AR_PRODUCT_ID == c) { /* Read device id */ ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { _retMeta->dev_id = c; /* Read message type */ ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if( ((c >= AR_SYSEX_DUMP_ID_KIT) && (c <= AR_SYSEX_DUMP_ID_GLOBAL)) || ((c >= AR_SYSEX_DUMPX_ID_KIT) && (c <= AR_SYSEX_DUMPX_ID_GLOBAL)) ) { sU8 msgType = c; if(c >= AR_SYSEX_DUMPX_ID_KIT) { _retMeta->obj_type = (c - AR_SYSEX_DUMPX_ID_BASE); } else { _retMeta->obj_type = (c - AR_SYSEX_DUMP_ID_BASE); } /* Read container version hi/major */ ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { _retMeta->container_version.b.hi = c; /* Read container version lo/minor */ ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { _retMeta->container_version.b.lo = c; /* Read object number */ ret = ar_sysex_read_next_byte(_srcBuf, _srcBufSize, &c); if(AR_ERR_OK == ret) { if(msgType >= AR_SYSEX_DUMPX_ID_KIT) { /* RAM object */ _retMeta->obj_nr = c + 128u; } else { _retMeta->obj_nr = c; } /* Succeeded */ ret = AR_ERR_OK; } } } } else { ret = AR_ERR_INVALID_DUMP_MSG_ID; } } } } else { ret = AR_ERR_INVALID_PRODUCT_ID; } } } else { ret = AR_ERR_INVALID_MANUFACTURER_ID; } } } else { ret = AR_ERR_INVALID_MANUFACTURER_ID; } } } else { ret = AR_ERR_INVALID_MANUFACTURER_ID; } } } else { ret = AR_ERR_NOT_A_SYSEX_MSG; } return ret; } /* ---------------------------------------------------------------------------- ar_sysex_get_size */ ar_error_t ar_sysex_get_size(const sU8 *_srcBuf, sU32 _srcBufSize, sU32 *_retMsgSize) { ar_error_t ret; const sU8 *sb = _srcBuf; sU32 sbsz = _srcBufSize; /* Skip sysex message start (0xF0) */ sb++; sbsz--; *_retMsgSize = 1; for(;;) { sU8 c; ret = ar_sysex_read_next_byte(&sb, &sbsz, &c); if(AR_ERR_OK == ret) { (*_retMsgSize)++; /* Continue until end of message is found */ } else { break; } } if(AR_ERR_END_OF_MSG == ret) { (*_retMsgSize)++; /* count in 0xF7 message terminator */ /* Succeeded */ ret = AR_ERR_OK; } return ret; } /* ---------------------------------------------------------------------------- ar_sysex_decode */ void ar_sysex_decode(sU8 *_dst, const sU8**_src, sU32 *_srcSize, sU32 _numSrcBytes, s_u16_t *_retChksum) { sU16 chksum = 0u; sUI pkbNr = 0u; /* 0..7 */ sU8 msbs = 0u; /* (note) must init to zero to prevent VS debug runtime error */ while(_numSrcBytes > 0u) { if(0u == pkbNr) { (void)ar_sysex_read_next_byte(_src, _srcSize, &msbs); chksum += msbs; } else { sU8 c; (void)ar_sysex_read_next_byte(_src, _srcSize, &c); chksum += c; *_dst++ = c | (msbs & 0x80u); } msbs <<= 1; pkbNr = (pkbNr + 1) & 7u; _numSrcBytes--; } S_U14_SET(*_retChksum, chksum); } /* ---------------------------------------------------------------------------- ar_sysex_encode */ void ar_sysex_encode(sU8 *_dst, const sU8 *_src, sU32 _srcSize, s_u16_t *_retChksum) { sU16 chksum = 0u; sUI pkbNr = 0u; sU8 msbs; #ifdef AR_DEBUG sU8 *dstOrig = _dst; sU32 srcSizeOrig = _srcSize; #endif _dst++; while(_srcSize > 0u) { sU8 c = *_src++; if(0u == pkbNr) { msbs = (c & 0x80u) >> 1; } else { msbs |= (c & 0x80u) >> (1u + pkbNr); } *_dst++ = (c & 127u); chksum += (c & 127u); pkbNr++; if(7u == pkbNr) { chksum += msbs; _dst[-8] = msbs; pkbNr = 0u; _dst++; } _srcSize--; } /* Finish last (partial) data packet */ if(pkbNr > 0) { _dst[-(sSI)(pkbNr + 1u)] = msbs; chksum += msbs; } #ifdef AR_DEBUG else { _dst--; } #endif #ifdef AR_DEBUG Dprintf("[dbg] ar_sysex_encode: encoded %u raw bytes to %u syx bytes\n", srcSizeOrig, (sUI)(_dst - dstOrig)); #endif S_U14_SET(*_retChksum, chksum); } /* ---------------------------------------------------------------------------- ar_sysex_to_raw */ ar_error_t ar_sysex_to_raw(sU8 *_dstBuf, const sU8 **_srcBuf, sU32 *_srcBufSize, sU32 *_retRawBufSize, ar_sysex_meta_t *_meta ) { ar_error_t ret; const sU8 *sb = *_srcBuf; sU32 sbsz = *_srcBufSize; ret = ar_sysex_parse_dump_header(&sb, &sbsz, _meta); if(AR_ERR_OK == ret) { sU32 syxMsgSz; /* num valid sysex msg bytes, excluding RT messages */ /* Determine sysex message size */ ret = ar_sysex_get_size(*_srcBuf, *_srcBufSize, &syxMsgSz); if(AR_ERR_OK == ret) { sU32 syxDatSz = (syxMsgSz - 10u/*header*/ - 2u/*chksum*/ - 2u/*data_size*/ - 1u/*0xF7*/); sU32 rawDatSz; sU32 fullPkgSz = syxDatSz; sU32 partialPkgSz; fullPkgSz = (fullPkgSz >> 3) << 3; partialPkgSz = (syxDatSz - fullPkgSz); rawDatSz = (fullPkgSz >> 3) * 7u; if(partialPkgSz > 0u) { rawDatSz += (partialPkgSz - 1u); } if(NULL != _retRawBufSize) { *_retRawBufSize = rawDatSz; } Dprintf("[dbg] ar_sysex_to_raw: syxMsgSz=%u\n", syxMsgSz); Dprintf("[dbg] ar_sysex_to_raw: syxDatSz=%u\n", syxDatSz); if(NULL != _dstBuf) { sU8 c; s_u16_t chksum; /* Decode to raw bytes */ ar_sysex_decode(_dstBuf, &sb, &sbsz, syxDatSz, &_meta->chksum); /* Read checksum */ (void)ar_sysex_read_next_byte(&sb, &sbsz, &chksum.b.hi); (void)ar_sysex_read_next_byte(&sb, &sbsz, &chksum.b.lo); /* Read data size */ (void)ar_sysex_read_next_byte(&sb, &sbsz, &_meta->data_size.b.hi); (void)ar_sysex_read_next_byte(&sb, &sbsz, &_meta->data_size.b.lo); /* Read sysex terminator (0xF7) */ (void)ar_sysex_read_next_byte(&sb, &sbsz, &c); Dprintf("[dbg] ar_sysex_to_raw: syx chksum= %08x %08x\n", chksum.b.hi, chksum.b.lo); Dprintf("[dbg] ar_sysex_to_raw: calc chksum= %08x %08x\n", _meta->chksum.b.hi, _meta->chksum.b.lo); Dprintf("[dbg] ar_sysex_to_raw: syx data size = 0x%08x 0x%08x (0x%04x)\n", _meta->data_size.b.hi, _meta->data_size.b.lo, S_U14_GET(_meta->data_size)); *_srcBuf = sb; *_srcBufSize = sbsz; ret = AR_ERR_OK; } } } return ret; } /* ---------------------------------------------------------------------------- ar_sysex_meta_is_valid */ ar_error_t ar_sysex_meta_is_valid(const ar_sysex_meta_t *_meta) { ar_error_t ret; if(NULL != _meta) { sU8 objNr = (_meta->obj_nr & 127u); /* mask out RAM flag */ ret = AR_ERR_OK; switch(_meta->obj_type) { default: ret = AR_ERR_INVALID_OBJ_TYPE; break; case AR_TYPE_KIT: if(objNr >= AR_NUM_KITS) { ret = AR_ERR_INVALID_OBJ_NR; } break; case AR_TYPE_SOUND: if(objNr >= AR_NUM_SOUNDS) { ret = AR_ERR_INVALID_OBJ_NR; } break; case AR_TYPE_PATTERN: if(objNr >= AR_NUM_PATTERNS) { ret = AR_ERR_INVALID_OBJ_NR; } break; case AR_TYPE_SONG: if(objNr >= AR_NUM_SONGS) { ret = AR_ERR_INVALID_OBJ_NR; } break; case AR_TYPE_SETTINGS: if(objNr >= AR_NUM_SETTINGS) { ret = AR_ERR_INVALID_OBJ_NR; } break; case AR_TYPE_GLOBAL: if(objNr >= AR_NUM_GLOBALS) { ret = AR_ERR_INVALID_OBJ_NR; } break; } } else { ret = AR_ERR_NULLPTR; } return ret; } /* ---------------------------------------------------------------------------- ar_sysex_meta_get_dump_id */ sU8 ar_sysex_meta_get_dump_id(const ar_sysex_meta_t *_meta) { sU8 dumpId = _meta->obj_type; if(_meta->obj_nr >= 128u) { /* RAM object */ dumpId += AR_SYSEX_DUMPX_ID_BASE; } else { /* Flash object */ dumpId += AR_SYSEX_DUMP_ID_BASE; } return dumpId; } /* ---------------------------------------------------------------------------- ar_sysex_meta_get_object_nr */ sU8 ar_sysex_meta_get_object_nr(const ar_sysex_meta_t *_meta) { /* Mask out RAM flag */ return (_meta->obj_nr & 127u); } /* ---------------------------------------------------------------------------- ar_raw_to_sysex */ ar_error_t ar_raw_to_sysex(sU8 *_dstBuf, const sU8 *_srcBuf, sU32 _srcBufSize, sU32 *_retSyxBufSize, const ar_sysex_meta_t *_meta ) { ar_error_t ret; sU32 syxMsgSz; sU32 syxDatSz; s_u16_t chksum; ret = ar_sysex_meta_is_valid(_meta); if(AR_ERR_OK == ret) { sU32 partialPkgSz; syxMsgSz = 10u/*header*/ + 2u/*chksum*/ + 2u/*data_size*/ + 1u/*0xF7*/; syxDatSz = ( (_srcBufSize/* + 6u*/) / 7u ) << 3; /* total size of data packets (including padding) */ partialPkgSz = (_srcBufSize % 7); if(partialPkgSz > 0u) { syxDatSz += 1u + partialPkgSz; } if(NULL != _dstBuf) { _dstBuf[0] = 0xF0u; _dstBuf[1] = 0x00u; /* extended manufacturer id */ _dstBuf[2] = AR_ELEKTRON_MANUFACTURER_ID_1; _dstBuf[3] = AR_ELEKTRON_MANUFACTURER_ID_2; _dstBuf[4] = AR_PRODUCT_ID; _dstBuf[5] = _meta->dev_id; _dstBuf[6] = ar_sysex_meta_get_dump_id(_meta); _dstBuf[7] = _meta->container_version.b.hi; _dstBuf[8] = _meta->container_version.b.lo; _dstBuf[9] = ar_sysex_meta_get_object_nr(_meta); _dstBuf += 10u; /* Write encoded raw data bytes */ ar_sysex_encode(_dstBuf, _srcBuf, _srcBufSize, &chksum); Dprintf("[dbg] ar_raw_to_sysex: chksum= 0x%08x 0x%08x\n", chksum.b.hi, chksum.b.lo); _dstBuf += syxDatSz; /* Write checksum */ _dstBuf[0] = chksum.b.hi; _dstBuf[1] = chksum.b.lo; _dstBuf += 2; /* Write the data size */ S_U14_SET(*((s_u16_t*)_dstBuf), syxDatSz + 2u/*chksum*/ + 2u/*data_size*/ + 1u/*0xF7*/); _dstBuf += 2; /* End of sysex message */ _dstBuf[0] = 0xF7u; } if(NULL != _retSyxBufSize) { *_retSyxBufSize = (syxMsgSz + syxDatSz); } ret = AR_ERR_OK; } return ret; }