Home Reference Source Test

src/bundle.js

  1. import EncodeHelper from './common/helpers'
  2. import { isArray, isInt } from './common/utils'
  3.  
  4. import AtomicInt32 from './atomic/int32'
  5. import AtomicString from './atomic/string'
  6. import AtomicTimetag from './atomic/timetag'
  7. import Message from './message'
  8.  
  9. /** OSC Bundle string */
  10. export const BUNDLE_TAG = '#bundle'
  11.  
  12. /**
  13. * An OSC Bundle consist of a Timetag and one or many Bundle Elements.
  14. * The elements are either OSC Messages or more OSC Bundles
  15. */
  16. export default class Bundle {
  17. /**
  18. * Create a Bundle instance
  19. * @param {...*} args Timetag and elements. See examples for options
  20. *
  21. * @example
  22. * const bundle = new Bundle(new Date() + 500)
  23. *
  24. * @example
  25. * const message = new Message('/test/path', 51.2)
  26. * const anotherBundle = new Bundle([message], Date.now() + 1500)
  27. *
  28. * @example
  29. * const message = new Message('/test/path', 51.2)
  30. * const anotherMessage = new Message('/test/message', 'test', 12)
  31. * const anotherBundle = new Bundle(message, anotherMessage)
  32. */
  33. constructor(...args) {
  34. /**
  35. * @type {number} offset
  36. * @private
  37. */
  38. this.offset = 0
  39. /** @type {AtomicTimetag} timetag */
  40. this.timetag = new AtomicTimetag()
  41. /** @type {array} bundleElements */
  42. this.bundleElements = []
  43.  
  44. if (args.length > 0) {
  45. // first argument is an Date or js timestamp (number)
  46. if (args[0] instanceof Date || isInt(args[0])) {
  47. this.timetag = new AtomicTimetag(args[0])
  48. } else if (isArray(args[0])) {
  49. // first argument is an Array of Bundle elements
  50. args[0].forEach((item) => {
  51. this.add(item)
  52. })
  53.  
  54. // second argument is an Date or js timestamp (number)
  55. if (args.length > 1 && (args[1] instanceof Date || isInt(args[1]))) {
  56. this.timetag = new AtomicTimetag(args[1])
  57. }
  58. } else {
  59. // take all arguments as Bundle elements
  60. args.forEach((item) => {
  61. this.add(item)
  62. })
  63. }
  64. }
  65. }
  66.  
  67. /**
  68. * Take a JavaScript timestamp to set the Bundle's timetag
  69. * @param {number} ms JS timestamp in milliseconds
  70. *
  71. * @example
  72. * const bundle = new Bundle()
  73. * bundle.timestamp(Date.now() + 5000) // in 5 seconds
  74. */
  75. timestamp(ms) {
  76. if (!isInt(ms)) {
  77. throw new Error('OSC Bundle needs an integer for setting the timestamp')
  78. }
  79.  
  80. this.timetag = new AtomicTimetag(ms)
  81. }
  82.  
  83. /**
  84. * Add a Message or Bundle to the list of elements
  85. * @param {Bundle|Message} item
  86. */
  87. add(item) {
  88. if (!(item instanceof Message || item instanceof Bundle)) {
  89. throw new Error('OSC Bundle contains only Messages and Bundles')
  90. }
  91.  
  92. this.bundleElements.push(item)
  93. }
  94.  
  95. /**
  96. * Interpret the Bundle as packed binary data
  97. * @return {Uint8Array} Packed binary data
  98. */
  99. pack() {
  100. const encoder = new EncodeHelper()
  101.  
  102. // an OSC Bundle consists of the OSC-string "#bundle"
  103. encoder.add(new AtomicString(BUNDLE_TAG))
  104.  
  105. // followed by an OSC Time Tag
  106. if (!this.timetag) {
  107. this.timetag = new AtomicTimetag()
  108. }
  109.  
  110. encoder.add(this.timetag)
  111.  
  112. // followed by zero or more OSC Bundle Elements
  113. this.bundleElements.forEach((item) => {
  114. encoder.add(new AtomicInt32(item.pack().byteLength))
  115. encoder.add(item)
  116. })
  117.  
  118. return encoder.merge()
  119. }
  120.  
  121. /**
  122. * Unpack binary data to read a Bundle
  123. * @param {DataView} dataView The DataView holding the binary representation of a Bundle
  124. * @param {number} [initialOffset=0] Offset of DataView before unpacking
  125. * @return {number} Offset after unpacking
  126. */
  127. unpack(dataView, initialOffset = 0) {
  128. if (!(dataView instanceof DataView)) {
  129. throw new Error('OSC Bundle expects an instance of type DataView')
  130. }
  131.  
  132. // read the beginning bundle string
  133. const parentHead = new AtomicString()
  134. parentHead.unpack(dataView, initialOffset)
  135.  
  136. if (parentHead.value !== BUNDLE_TAG) {
  137. throw new Error('OSC Bundle does not contain a valid #bundle head')
  138. }
  139.  
  140. // read the timetag
  141. const timetag = new AtomicTimetag()
  142. let offset = timetag.unpack(dataView, parentHead.offset)
  143.  
  144. // read the bundle elements
  145. this.bundleElements = []
  146.  
  147. while (offset < dataView.byteLength) {
  148. const head = new AtomicString()
  149. const size = new AtomicInt32()
  150.  
  151. offset = size.unpack(dataView, offset)
  152.  
  153. // check if Packet is a Bundle or a Message
  154. let item
  155. head.unpack(dataView, offset)
  156.  
  157. if (head.value === BUNDLE_TAG) {
  158. item = new Bundle()
  159. } else {
  160. item = new Message()
  161. }
  162.  
  163. offset = item.unpack(dataView, offset)
  164.  
  165. this.bundleElements.push(item)
  166. }
  167.  
  168. this.offset = offset
  169. this.timetag = timetag
  170.  
  171. return this.offset
  172. }
  173. }