src/bundle.js
- import EncodeHelper from './common/helpers'
- import { isArray, isInt } from './common/utils'
-
- import AtomicInt32 from './atomic/int32'
- import AtomicString from './atomic/string'
- import AtomicTimetag from './atomic/timetag'
- import Message from './message'
-
- /** OSC Bundle string */
- export const BUNDLE_TAG = '#bundle'
-
- /**
- * An OSC Bundle consist of a Timetag and one or many Bundle Elements.
- * The elements are either OSC Messages or more OSC Bundles
- */
- export default class Bundle {
- /**
- * Create a Bundle instance
- * @param {...*} args Timetag and elements. See examples for options
- *
- * @example
- * const bundle = new Bundle(new Date() + 500)
- *
- * @example
- * const message = new Message('/test/path', 51.2)
- * const anotherBundle = new Bundle([message], Date.now() + 1500)
- *
- * @example
- * const message = new Message('/test/path', 51.2)
- * const anotherMessage = new Message('/test/message', 'test', 12)
- * const anotherBundle = new Bundle(message, anotherMessage)
- */
- constructor(...args) {
- /**
- * @type {number} offset
- * @private
- */
- this.offset = 0
- /** @type {AtomicTimetag} timetag */
- this.timetag = new AtomicTimetag()
- /** @type {array} bundleElements */
- this.bundleElements = []
-
- if (args.length > 0) {
- // first argument is an Date or js timestamp (number)
- if (args[0] instanceof Date || isInt(args[0])) {
- this.timetag = new AtomicTimetag(args[0])
- } else if (isArray(args[0])) {
- // first argument is an Array of Bundle elements
- args[0].forEach((item) => {
- this.add(item)
- })
-
- // second argument is an Date or js timestamp (number)
- if (args.length > 1 && (args[1] instanceof Date || isInt(args[1]))) {
- this.timetag = new AtomicTimetag(args[1])
- }
- } else {
- // take all arguments as Bundle elements
- args.forEach((item) => {
- this.add(item)
- })
- }
- }
- }
-
- /**
- * Take a JavaScript timestamp to set the Bundle's timetag
- * @param {number} ms JS timestamp in milliseconds
- *
- * @example
- * const bundle = new Bundle()
- * bundle.timestamp(Date.now() + 5000) // in 5 seconds
- */
- timestamp(ms) {
- if (!isInt(ms)) {
- throw new Error('OSC Bundle needs an integer for setting the timestamp')
- }
-
- this.timetag = new AtomicTimetag(ms)
- }
-
- /**
- * Add a Message or Bundle to the list of elements
- * @param {Bundle|Message} item
- */
- add(item) {
- if (!(item instanceof Message || item instanceof Bundle)) {
- throw new Error('OSC Bundle contains only Messages and Bundles')
- }
-
- this.bundleElements.push(item)
- }
-
- /**
- * Interpret the Bundle as packed binary data
- * @return {Uint8Array} Packed binary data
- */
- pack() {
- const encoder = new EncodeHelper()
-
- // an OSC Bundle consists of the OSC-string "#bundle"
- encoder.add(new AtomicString(BUNDLE_TAG))
-
- // followed by an OSC Time Tag
- if (!this.timetag) {
- this.timetag = new AtomicTimetag()
- }
-
- encoder.add(this.timetag)
-
- // followed by zero or more OSC Bundle Elements
- this.bundleElements.forEach((item) => {
- encoder.add(new AtomicInt32(item.pack().byteLength))
- encoder.add(item)
- })
-
- return encoder.merge()
- }
-
- /**
- * Unpack binary data to read a Bundle
- * @param {DataView} dataView The DataView holding the binary representation of a Bundle
- * @param {number} [initialOffset=0] Offset of DataView before unpacking
- * @return {number} Offset after unpacking
- */
- unpack(dataView, initialOffset = 0) {
- if (!(dataView instanceof DataView)) {
- throw new Error('OSC Bundle expects an instance of type DataView')
- }
-
- // read the beginning bundle string
- const parentHead = new AtomicString()
- parentHead.unpack(dataView, initialOffset)
-
- if (parentHead.value !== BUNDLE_TAG) {
- throw new Error('OSC Bundle does not contain a valid #bundle head')
- }
-
- // read the timetag
- const timetag = new AtomicTimetag()
- let offset = timetag.unpack(dataView, parentHead.offset)
-
- // read the bundle elements
- this.bundleElements = []
-
- while (offset < dataView.byteLength) {
- const head = new AtomicString()
- const size = new AtomicInt32()
-
- offset = size.unpack(dataView, offset)
-
- // check if Packet is a Bundle or a Message
- let item
- head.unpack(dataView, offset)
-
- if (head.value === BUNDLE_TAG) {
- item = new Bundle()
- } else {
- item = new Message()
- }
-
- offset = item.unpack(dataView, offset)
-
- this.bundleElements.push(item)
- }
-
- this.offset = offset
- this.timetag = timetag
-
- return this.offset
- }
- }