Home Reference Source Test

src/message.js

import {
  isArray,
  isBlob,
  isFloat,
  isInt,
  isString,
  isUndefined,
} from './common/utils'

import Helper, { typeTag, prepareAddress } from './common/helpers'

import AtomicBlob from './atomic/blob'
import AtomicFloat32 from './atomic/float32'
import AtomicInt32 from './atomic/int32'
import AtomicString from './atomic/string'

/**
 * An OSC message consists of an OSC Address Pattern followed
 * by an OSC Type Tag String followed by zero or more OSC Arguments
 */
export default class Message {
  /**
   * Create a Message instance
   * @param {array|string} args Address
   * @param {...*} args OSC Atomic Data Types
   *
   * @example
   * const message = new Message(['test', 'path'], 50, 100.52, 'test')
   *
   * @example
   * const message = new Message('/test/path', 51.2)
   */
  constructor(...args) {
    /**
     * @type {number} offset
     * @private
     */
    this.offset = 0
    /** @type {string} address */
    this.address = ''
    /** @type {string} types */
    this.types = ''
    /** @type {array} args */
    this.args = []

    if (args.length > 0) {
      if (!(isString(args[0]) || isArray(args[0]))) {
        throw new Error('OSC Message constructor first argument (address) must be a string or array')
      }

      this.address = prepareAddress(args.shift())
      this.types = args.map(item => typeTag(item)).join('')
      this.args = args
    }
  }

  /**
   * Add an OSC Atomic Data Type to the list of elements
   * @param {*} item
   */
  add(item) {
    if (isUndefined(item)) {
      throw new Error('OSC Message needs a valid OSC Atomic Data Type')
    }

    this.args.push(item)
    this.types += typeTag(item)
  }

  /**
   * Interpret the Message as packed binary data
   * @return {Uint8Array} Packed binary data
   */
  pack() {
    if (this.address.length === 0 || this.address[0] !== '/') {
      throw new Error('OSC Message has an invalid address')
    }

    const encoder = new Helper()

    // OSC Address Pattern and Type string
    encoder.add(new AtomicString(this.address))
    encoder.add(new AtomicString(`,${this.types}`))

    // followed by zero or more OSC Arguments
    if (this.args.length > 0) {
      let argument

      this.args.forEach((value) => {
        if (isInt(value)) {
          argument = new AtomicInt32(value)
        } else if (isFloat(value)) {
          argument = new AtomicFloat32(value)
        } else if (isString(value)) {
          argument = new AtomicString(value)
        } else if (isBlob(value)) {
          argument = new AtomicBlob(value)
        } else {
          throw new Error('OSC Message found unknown argument type')
        }

        encoder.add(argument)
      })
    }

    return encoder.merge()
  }

  /**
   * Unpack binary data to read a Message
   * @param {DataView} dataView The DataView holding the binary representation of a Message
   * @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 Message expects an instance of type DataView.')
    }

    // read address pattern
    const address = new AtomicString()
    address.unpack(dataView, initialOffset)

    // read type string
    const types = new AtomicString()
    types.unpack(dataView, address.offset)

    if (address.value.length === 0 || address.value[0] !== '/') {
      throw new Error('OSC Message found malformed or missing address string')
    }

    if (types.value.length === 0 && types.value[0] !== ',') {
      throw new Error('OSC Message found malformed or missing type string')
    }

    let { offset } = types
    let next
    let type

    const args = []

    // read message arguments (OSC Atomic Data Types)
    for (let i = 1; i < types.value.length; i += 1) {
      type = types.value[i]

      if (type === 'i') {
        next = new AtomicInt32()
      } else if (type === 'f') {
        next = new AtomicFloat32()
      } else if (type === 's') {
        next = new AtomicString()
      } else if (type === 'b') {
        next = new AtomicBlob()
      } else {
        throw new Error('OSC Message found non-standard argument type')
      }

      offset = next.unpack(dataView, offset)
      args.push(next.value)
    }

    this.offset = offset
    this.address = address.value
    this.types = types.value
    this.args = args

    return this.offset
  }
}