(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.OSC = factory());
}(this, (function () { 'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _superPropBase(object, property) {
    while (!Object.prototype.hasOwnProperty.call(object, property)) {
      object = _getPrototypeOf(object);
      if (object === null) break;
    }

    return object;
  }

  function _get(target, property, receiver) {
    if (typeof Reflect !== "undefined" && Reflect.get) {
      _get = Reflect.get;
    } else {
      _get = function _get(target, property, receiver) {
        var base = _superPropBase(target, property);

        if (!base) return;
        var desc = Object.getOwnPropertyDescriptor(base, property);

        if (desc.get) {
          return desc.get.call(receiver);
        }

        return desc.value;
      };
    }

    return _get(target, property, receiver || target);
  }

  function isInt(n) {
    return Number(n) === n && n % 1 === 0;
  }
  function isFloat(n) {
    return Number(n) === n && n % 1 !== 0;
  }
  function isString(n) {
    return typeof n === 'string';
  }
  function isArray(n) {
    return Object.prototype.toString.call(n) === '[object Array]';
  }
  function isObject(n) {
    return Object.prototype.toString.call(n) === '[object Object]';
  }
  function isFunction(n) {
    return typeof n === 'function';
  }
  function isBlob(n) {
    return n instanceof Uint8Array;
  }
  function isDate(n) {
    return n instanceof Date;
  }
  function isUndefined(n) {
    return typeof n === 'undefined';
  }
  function pad(n) {
    return n + 3 & ~0x03;
  }
  function hasProperty(name) {
    return Object.prototype.hasOwnProperty.call(typeof global !== 'undefined' ? global : window,
    name);
  }
  function dataView(obj) {
    if (obj.buffer) {
      return new DataView(obj.buffer);
    } else if (obj instanceof ArrayBuffer) {
      return new DataView(obj);
    }
    return new DataView(new Uint8Array(obj));
  }

  function typeTag(item) {
    if (isInt(item)) {
      return 'i';
    } else if (isFloat(item)) {
      return 'f';
    } else if (isString(item)) {
      return 's';
    } else if (isBlob(item)) {
      return 'b';
    }
    throw new Error('OSC typeTag() found unknown value type');
  }
  function prepareAddress(obj) {
    var address = '';
    if (isArray(obj)) {
      return "/".concat(obj.join('/'));
    } else if (isString(obj)) {
      address = obj;
      if (address.length > 1 && address[address.length - 1] === '/') {
        address = address.slice(0, address.length - 1);
      }
      if (address.length > 1 && address[0] !== '/') {
        address = "/".concat(address);
      }
      return address;
    }
    throw new Error('OSC prepareAddress() needs addresses of type array or string');
  }
  function prepareRegExPattern(str) {
    var pattern;
    if (!isString(str)) {
      throw new Error('OSC prepareRegExPattern() needs strings');
    }
    pattern = str.replace(/\./g, '\\.');
    pattern = pattern.replace(/\(/g, '\\(');
    pattern = pattern.replace(/\)/g, '\\)');
    pattern = pattern.replace(/\{/g, '(');
    pattern = pattern.replace(/\}/g, ')');
    pattern = pattern.replace(/,/g, '|');
    pattern = pattern.replace(/\[!/g, '[^');
    pattern = pattern.replace(/\?/g, '.');
    pattern = pattern.replace(/\*/g, '.*');
    return pattern;
  }
  var EncodeHelper =
  function () {
    function EncodeHelper() {
      _classCallCheck(this, EncodeHelper);
      this.data = [];
      this.byteLength = 0;
    }
    _createClass(EncodeHelper, [{
      key: "add",
      value: function add(item) {
        var buffer = item.pack();
        this.byteLength += buffer.byteLength;
        this.data.push(buffer);
        return this;
      }
    }, {
      key: "merge",
      value: function merge() {
        var result = new Uint8Array(this.byteLength);
        var offset = 0;
        this.data.forEach(function (data) {
          result.set(data, offset);
          offset += data.byteLength;
        });
        return result;
      }
    }]);
    return EncodeHelper;
  }();

  var Atomic =
  function () {
    function Atomic(value) {
      _classCallCheck(this, Atomic);
      this.value = value;
      this.offset = 0;
    }
    _createClass(Atomic, [{
      key: "pack",
      value: function pack(method, byteLength) {
        if (!(method && byteLength)) {
          throw new Error('OSC Atomic cant\'t be packed without given method or byteLength');
        }
        var data = new Uint8Array(byteLength);
        var dataView$$1 = new DataView(data.buffer);
        if (isUndefined(this.value)) {
          throw new Error('OSC Atomic cant\'t be encoded with empty value');
        }
        dataView$$1[method](this.offset, this.value, false);
        return data;
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1, method, byteLength) {
        var initialOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
        if (!(dataView$$1 && method && byteLength)) {
          throw new Error('OSC Atomic cant\'t be unpacked without given dataView, method or byteLength');
        }
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC Atomic expects an instance of type DataView');
        }
        this.value = dataView$$1[method](initialOffset, false);
        this.offset = initialOffset + byteLength;
        return this.offset;
      }
    }]);
    return Atomic;
  }();

  var AtomicInt32 =
  function (_Atomic) {
    _inherits(AtomicInt32, _Atomic);
    function AtomicInt32(value) {
      _classCallCheck(this, AtomicInt32);
      if (value && !isInt(value)) {
        throw new Error('OSC AtomicInt32 constructor expects value of type number');
      }
      return _possibleConstructorReturn(this, _getPrototypeOf(AtomicInt32).call(this, value));
    }
    _createClass(AtomicInt32, [{
      key: "pack",
      value: function pack() {
        return _get(_getPrototypeOf(AtomicInt32.prototype), "pack", this).call(this, 'setInt32', 4);
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        return _get(_getPrototypeOf(AtomicInt32.prototype), "unpack", this).call(this, dataView$$1, 'getInt32', 4, initialOffset);
      }
    }]);
    return AtomicInt32;
  }(Atomic);

  var STR_SLICE_SIZE = 65537;
  var STR_ENCODING = 'utf-8';
  function charCodesToString(charCodes) {
    if (hasProperty('Buffer')) {
      return Buffer.from(charCodes).toString(STR_ENCODING);
    } else if (hasProperty('TextDecoder')) {
      return new TextDecoder(STR_ENCODING)
      .decode(new Int8Array(charCodes));
    }
    var str = '';
    for (var i = 0; i < charCodes.length; i += STR_SLICE_SIZE) {
      str += String.fromCharCode.apply(null, charCodes.slice(i, i + STR_SLICE_SIZE));
    }
    return str;
  }
  var AtomicString =
  function (_Atomic) {
    _inherits(AtomicString, _Atomic);
    function AtomicString(value) {
      _classCallCheck(this, AtomicString);
      if (value && !isString(value)) {
        throw new Error('OSC AtomicString constructor expects value of type string');
      }
      return _possibleConstructorReturn(this, _getPrototypeOf(AtomicString).call(this, value));
    }
    _createClass(AtomicString, [{
      key: "pack",
      value: function pack() {
        if (isUndefined(this.value)) {
          throw new Error('OSC AtomicString can not be encoded with empty value');
        }
        var terminated = "".concat(this.value, "\0");
        var byteLength = pad(terminated.length);
        var buffer = new Uint8Array(byteLength);
        for (var i = 0; i < terminated.length; i += 1) {
          buffer[i] = terminated.charCodeAt(i);
        }
        return buffer;
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC AtomicString expects an instance of type DataView');
        }
        var offset = initialOffset;
        var charcode;
        var charCodes = [];
        for (; offset < dataView$$1.byteLength; offset += 1) {
          charcode = dataView$$1.getUint8(offset);
          if (charcode !== 0) {
            charCodes.push(charcode);
          } else {
            offset += 1;
            break;
          }
        }
        if (offset === dataView$$1.length) {
          throw new Error('OSC AtomicString found a malformed OSC string');
        }
        this.offset = pad(offset);
        this.value = charCodesToString(charCodes);
        return this.offset;
      }
    }]);
    return AtomicString;
  }(Atomic);

  var SECONDS_70_YEARS = 2208988800;
  var TWO_POWER_32 = 4294967296;
  var Timetag =
  function () {
    function Timetag() {
      var seconds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
      var fractions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      _classCallCheck(this, Timetag);
      if (!(isInt(seconds) && isInt(fractions))) {
        throw new Error('OSC Timetag constructor expects values of type integer number');
      }
      this.seconds = seconds;
      this.fractions = fractions;
    }
    _createClass(Timetag, [{
      key: "timestamp",
      value: function timestamp(milliseconds) {
        var seconds;
        if (typeof milliseconds === 'number') {
          seconds = milliseconds / 1000;
          var rounded = Math.floor(seconds);
          this.seconds = rounded + SECONDS_70_YEARS;
          this.fractions = Math.round(TWO_POWER_32 * (seconds - rounded));
          return milliseconds;
        }
        seconds = this.seconds - SECONDS_70_YEARS;
        return (seconds + Math.round(this.fractions / TWO_POWER_32)) * 1000;
      }
    }]);
    return Timetag;
  }();
  var AtomicTimetag =
  function (_Atomic) {
    _inherits(AtomicTimetag, _Atomic);
    function AtomicTimetag() {
      var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Date.now();
      _classCallCheck(this, AtomicTimetag);
      var timetag = new Timetag();
      if (value instanceof Timetag) {
        timetag = value;
      } else if (isInt(value)) {
        timetag.timestamp(value);
      } else if (isDate(value)) {
        timetag.timestamp(value.getTime());
      }
      return _possibleConstructorReturn(this, _getPrototypeOf(AtomicTimetag).call(this, timetag));
    }
    _createClass(AtomicTimetag, [{
      key: "pack",
      value: function pack() {
        if (isUndefined(this.value)) {
          throw new Error('OSC AtomicTimetag can not be encoded with empty value');
        }
        var _this$value = this.value,
            seconds = _this$value.seconds,
            fractions = _this$value.fractions;
        var data = new Uint8Array(8);
        var dataView$$1 = new DataView(data.buffer);
        dataView$$1.setInt32(0, seconds, false);
        dataView$$1.setInt32(4, fractions, false);
        return data;
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC AtomicTimetag expects an instance of type DataView');
        }
        var seconds = dataView$$1.getUint32(initialOffset, false);
        var fractions = dataView$$1.getUint32(initialOffset + 4, false);
        this.value = new Timetag(seconds, fractions);
        this.offset = initialOffset + 8;
        return this.offset;
      }
    }]);
    return AtomicTimetag;
  }(Atomic);

  var AtomicBlob =
  function (_Atomic) {
    _inherits(AtomicBlob, _Atomic);
    function AtomicBlob(value) {
      _classCallCheck(this, AtomicBlob);
      if (value && !isBlob(value)) {
        throw new Error('OSC AtomicBlob constructor expects value of type Uint8Array');
      }
      return _possibleConstructorReturn(this, _getPrototypeOf(AtomicBlob).call(this, value));
    }
    _createClass(AtomicBlob, [{
      key: "pack",
      value: function pack() {
        if (isUndefined(this.value)) {
          throw new Error('OSC AtomicBlob can not be encoded with empty value');
        }
        var byteLength = pad(this.value.byteLength);
        var data = new Uint8Array(byteLength + 4);
        var dataView$$1 = new DataView(data.buffer);
        dataView$$1.setInt32(0, this.value.byteLength, false);
        data.set(this.value, 4);
        return data;
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC AtomicBlob expects an instance of type DataView');
        }
        var byteLength = dataView$$1.getInt32(initialOffset, false);
        this.value = new Uint8Array(dataView$$1.buffer, initialOffset + 4, byteLength);
        this.offset = pad(initialOffset + 4 + byteLength);
        return this.offset;
      }
    }]);
    return AtomicBlob;
  }(Atomic);

  var AtomicFloat32 =
  function (_Atomic) {
    _inherits(AtomicFloat32, _Atomic);
    function AtomicFloat32(value) {
      _classCallCheck(this, AtomicFloat32);
      if (value && !isFloat(value)) {
        throw new Error('OSC AtomicFloat32 constructor expects value of type float');
      }
      return _possibleConstructorReturn(this, _getPrototypeOf(AtomicFloat32).call(this, value));
    }
    _createClass(AtomicFloat32, [{
      key: "pack",
      value: function pack() {
        return _get(_getPrototypeOf(AtomicFloat32.prototype), "pack", this).call(this, 'setFloat32', 4);
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        return _get(_getPrototypeOf(AtomicFloat32.prototype), "unpack", this).call(this, dataView$$1, 'getFloat32', 4, initialOffset);
      }
    }]);
    return AtomicFloat32;
  }(Atomic);

  var Message =
  function () {
    function Message() {
      _classCallCheck(this, Message);
      this.offset = 0;
      this.address = '';
      this.types = '';
      this.args = [];
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      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(function (item) {
          return typeTag(item);
        }).join('');
        this.args = args;
      }
    }
    _createClass(Message, [{
      key: "add",
      value: function 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);
      }
    }, {
      key: "pack",
      value: function pack() {
        if (this.address.length === 0 || this.address[0] !== '/') {
          throw new Error('OSC Message has an invalid address');
        }
        var encoder = new EncodeHelper();
        encoder.add(new AtomicString(this.address));
        encoder.add(new AtomicString(",".concat(this.types)));
        if (this.args.length > 0) {
          var argument;
          this.args.forEach(function (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();
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC Message expects an instance of type DataView.');
        }
        var address = new AtomicString();
        address.unpack(dataView$$1, initialOffset);
        var types = new AtomicString();
        types.unpack(dataView$$1, 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');
        }
        var offset = types.offset;
        var next;
        var type;
        var args = [];
        for (var 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$$1, offset);
          args.push(next.value);
        }
        this.offset = offset;
        this.address = address.value;
        this.types = types.value;
        this.args = args;
        return this.offset;
      }
    }]);
    return Message;
  }();

  var BUNDLE_TAG = '#bundle';
  var Bundle =
  function () {
    function Bundle() {
      var _this = this;
      _classCallCheck(this, Bundle);
      this.offset = 0;
      this.timetag = new AtomicTimetag();
      this.bundleElements = [];
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      if (args.length > 0) {
        if (args[0] instanceof Date || isInt(args[0])) {
          this.timetag = new AtomicTimetag(args[0]);
        } else if (isArray(args[0])) {
          args[0].forEach(function (item) {
            _this.add(item);
          });
          if (args.length > 1 && (args[1] instanceof Date || isInt(args[0]))) {
            this.timetag = new AtomicTimetag(args[1]);
          }
        } else {
          args.forEach(function (item) {
            _this.add(item);
          });
        }
      }
    }
    _createClass(Bundle, [{
      key: "timestamp",
      value: function timestamp(ms) {
        if (!isInt(ms)) {
          throw new Error('OSC Bundle needs an integer for setting the timestamp');
        }
        this.timetag = new AtomicTimetag(ms);
      }
    }, {
      key: "add",
      value: function add(item) {
        if (!(item instanceof Message || item instanceof Bundle)) {
          throw new Error('OSC Bundle contains only Messages and Bundles');
        }
        this.bundleElements.push(item);
      }
    }, {
      key: "pack",
      value: function pack() {
        var encoder = new EncodeHelper();
        encoder.add(new AtomicString(BUNDLE_TAG));
        if (!this.timetag) {
          this.timetag = new AtomicTimetag();
        }
        encoder.add(this.timetag);
        this.bundleElements.forEach(function (item) {
          encoder.add(new AtomicInt32(item.pack().byteLength));
          encoder.add(item);
        });
        return encoder.merge();
      }
    }, {
      key: "unpack",
      value: function unpack(dataView$$1) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView$$1 instanceof DataView)) {
          throw new Error('OSC Bundle expects an instance of type DataView');
        }
        var parentHead = new AtomicString();
        parentHead.unpack(dataView$$1, initialOffset);
        if (parentHead.value !== BUNDLE_TAG) {
          throw new Error('OSC Bundle does not contain a valid #bundle head');
        }
        var timetag = new AtomicTimetag();
        var offset = timetag.unpack(dataView$$1, parentHead.offset);
        this.bundleElements = [];
        while (offset < dataView$$1.byteLength) {
          var head = new AtomicString();
          var size = new AtomicInt32();
          offset = size.unpack(dataView$$1, offset);
          var item = void 0;
          head.unpack(dataView$$1, offset);
          if (head.value === BUNDLE_TAG) {
            item = new Bundle();
          } else {
            item = new Message();
          }
          offset = item.unpack(dataView$$1, offset);
          this.bundleElements.push(item);
        }
        this.offset = offset;
        this.timetag = timetag;
        return this.offset;
      }
    }]);
    return Bundle;
  }();

  var Packet =
  function () {
    function Packet(value) {
      _classCallCheck(this, Packet);
      if (value && !(value instanceof Message || value instanceof Bundle)) {
        throw new Error('OSC Packet value has to be Message or Bundle');
      }
      this.value = value;
      this.offset = 0;
    }
    _createClass(Packet, [{
      key: "pack",
      value: function pack() {
        if (!this.value) {
          throw new Error('OSC Packet can not be encoded with empty body');
        }
        return this.value.pack();
      }
    }, {
      key: "unpack",
      value: function unpack(dataView) {
        var initialOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        if (!(dataView instanceof DataView)) {
          throw new Error('OSC Packet expects an instance of type DataView');
        }
        if (dataView.byteLength % 4 !== 0) {
          throw new Error('OSC Packet byteLength has to be a multiple of four');
        }
        var head = new AtomicString();
        head.unpack(dataView, initialOffset);
        var item;
        if (head.value === BUNDLE_TAG) {
          item = new Bundle();
        } else {
          item = new Message();
        }
        item.unpack(dataView, initialOffset);
        this.offset = item.offset;
        this.value = item;
        return this.offset;
      }
    }]);
    return Packet;
  }();

  var defaultOptions = {
    discardLateMessages: false
  };
  var EventHandler =
  function () {
    function EventHandler(options) {
      _classCallCheck(this, EventHandler);
      this.options = Object.assign({}, defaultOptions, options);
      this.addressHandlers = [];
      this.eventHandlers = {
        open: [],
        error: [],
        close: []
      };
      this.uuid = 0;
    }
    _createClass(EventHandler, [{
      key: "dispatch",
      value: function dispatch(packet) {
        var _this = this;
        if (!(packet instanceof Packet)) {
          throw new Error('OSC EventHander dispatch() accepts only arguments of type Packet');
        }
        if (!packet.value) {
          throw new Error('OSC EventHander dispatch() can\'t read empty Packets');
        }
        if (packet.value instanceof Bundle) {
          var bundle = packet.value;
          return bundle.bundleElements.forEach(function (bundleItem) {
            if (bundleItem instanceof Bundle) {
              if (bundle.timetag.value.timestamp() < bundleItem.timetag.value.timestamp()) {
                throw new Error('OSC Bundle timestamp is older than the timestamp of enclosed Bundles');
              }
              return _this.dispatch(bundleItem);
            } else if (bundleItem instanceof Message) {
              var message = bundleItem;
              return _this.notify(message.address, message, bundle.timetag.value.timestamp());
            }
            throw new Error('OSC EventHander dispatch() can\'t dispatch unknown Packet value');
          });
        } else if (packet.value instanceof Message) {
          var message = packet.value;
          return this.notify(message.address, message);
        }
        throw new Error('OSC EventHander dispatch() can\'t dispatch unknown Packet value');
      }
    }, {
      key: "call",
      value: function call(name, data) {
        var success = false;
        if (isString(name) && name in this.eventHandlers) {
          this.eventHandlers[name].forEach(function (handler) {
            handler.callback(data);
            success = true;
          });
          return success;
        }
        var handlerKeys = Object.keys(this.addressHandlers);
        var handlers = this.addressHandlers;
        handlerKeys.forEach(function (key) {
          var foundMatch = false;
          var regex = new RegExp(prepareRegExPattern(prepareAddress(name)), 'g');
          var test = regex.test(key);
          if (test && key.length === regex.lastIndex) {
            foundMatch = true;
          }
          if (!foundMatch) {
            var reverseRegex = new RegExp(prepareRegExPattern(prepareAddress(key)), 'g');
            var reverseTest = reverseRegex.test(name);
            if (reverseTest && name.length === reverseRegex.lastIndex) {
              foundMatch = true;
            }
          }
          if (foundMatch) {
            handlers[key].forEach(function (handler) {
              handler.callback(data);
              success = true;
            });
          }
        });
        return success;
      }
    }, {
      key: "notify",
      value: function notify() {
        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }
        if (args.length === 0) {
          throw new Error('OSC EventHandler can not be called without any argument');
        }
        if (args[0] instanceof Packet) {
          return this.dispatch(args[0]);
        } else if (args[0] instanceof Bundle || args[0] instanceof Message) {
          return this.dispatch(new Packet(args[0]));
        } else if (!isString(args[0])) {
          var packet = new Packet();
          packet.unpack(dataView(args[0]));
          return this.dispatch(packet);
        }
        var name = args[0];
        var data = null;
        if (args.length > 1) {
          data = args[1];
        }
        var timestamp = null;
        if (args.length > 2) {
          if (isInt(args[2])) {
            timestamp = args[2];
          } else if (args[2] instanceof Date) {
            timestamp = args[2].getTime();
          } else {
            throw new Error('OSC EventHandler timestamp has to be a number or Date');
          }
        }
        if (timestamp) {
          var now = Date.now();
          if (now > timestamp) {
            if (!this.options.discardLateMessages) {
              return this.call(name, data);
            }
          }
          var that = this;
          setTimeout(function () {
            that.call(name, data);
          }, timestamp - now);
          return true;
        }
        return this.call(name, data);
      }
    }, {
      key: "on",
      value: function on(name, callback) {
        if (!(isString(name) || isArray(name))) {
          throw new Error('OSC EventHandler accepts only strings or arrays for address patterns');
        }
        if (!isFunction(callback)) {
          throw new Error('OSC EventHandler callback has to be a function');
        }
        this.uuid += 1;
        var handler = {
          id: this.uuid,
          callback: callback
        };
        if (isString(name) && name in this.eventHandlers) {
          this.eventHandlers[name].push(handler);
          return this.uuid;
        }
        var address = prepareAddress(name);
        if (!(address in this.addressHandlers)) {
          this.addressHandlers[address] = [];
        }
        this.addressHandlers[address].push(handler);
        return this.uuid;
      }
    }, {
      key: "off",
      value: function off(name, subscriptionId) {
        if (!(isString(name) || isArray(name))) {
          throw new Error('OSC EventHandler accepts only strings or arrays for address patterns');
        }
        if (!isInt(subscriptionId)) {
          throw new Error('OSC EventHandler subscription id has to be a number');
        }
        var key;
        var haystack;
        if (isString(name) && name in this.eventHandlers) {
          key = name;
          haystack = this.eventHandlers;
        } else {
          key = prepareAddress(name);
          haystack = this.addressHandlers;
        }
        if (key in haystack) {
          return haystack[key].some(function (item, index) {
            if (item.id === subscriptionId) {
              haystack[key].splice(index, 1);
              return true;
            }
            return false;
          });
        }
        return false;
      }
    }]);
    return EventHandler;
  }();

  var dgram = typeof __dirname !== 'undefined' ? require('dgram') : undefined;
  var STATUS = {
    IS_NOT_INITIALIZED: -1,
    IS_CONNECTING: 0,
    IS_OPEN: 1,
    IS_CLOSING: 2,
    IS_CLOSED: 3
  };
  var defaultOpenOptions = {
    host: 'localhost',
    port: 41234,
    exclusive: false
  };
  var defaultSendOptions = {
    host: 'localhost',
    port: 41235
  };
  var defaultOptions$1 = {
    type: 'udp4',
    open: defaultOpenOptions,
    send: defaultSendOptions
  };
  function mergeOptions(base, custom) {
    return Object.assign({}, defaultOptions$1, base, custom, {
      open: Object.assign({}, defaultOptions$1.open, base.open, custom.open),
      send: Object.assign({}, defaultOptions$1.send, base.send, custom.send)
    });
  }
  var DatagramPlugin =
  function () {
    function DatagramPlugin() {
      var _this = this;
      var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      _classCallCheck(this, DatagramPlugin);
      if (!dgram) {
        throw new Error('DatagramPlugin can not be used in browser context');
      }
      this.options = mergeOptions({}, customOptions);
      this.socket = dgram.createSocket(this.options.type);
      this.socketStatus = STATUS.IS_NOT_INITIALIZED;
      this.socket.on('message', function (message) {
        _this.notify(message);
      });
      this.socket.on('error', function (error) {
        _this.notify('error', error);
      });
      this.notify = function () {};
    }
    _createClass(DatagramPlugin, [{
      key: "registerNotify",
      value: function registerNotify(fn) {
        this.notify = fn;
      }
    }, {
      key: "status",
      value: function status() {
        return this.socketStatus;
      }
    }, {
      key: "open",
      value: function open() {
        var _this2 = this;
        var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var options = Object.assign({}, this.options.open, customOptions);
        var port = options.port,
            exclusive = options.exclusive;
        this.socketStatus = STATUS.IS_CONNECTING;
        this.socket.bind({
          address: options.host,
          port: port,
          exclusive: exclusive
        }, function () {
          _this2.socketStatus = STATUS.IS_OPEN;
          _this2.notify('open');
        });
      }
    }, {
      key: "close",
      value: function close() {
        var _this3 = this;
        this.socketStatus = STATUS.IS_CLOSING;
        this.socket.close(function () {
          _this3.socketStatus = STATUS.IS_CLOSED;
          _this3.notify('close');
        });
      }
    }, {
      key: "send",
      value: function send(binary) {
        var customOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        var options = Object.assign({}, this.options.send, customOptions);
        var port = options.port,
            host = options.host;
        this.socket.send(Buffer.from(binary), 0, binary.byteLength, port, host);
      }
    }]);
    return DatagramPlugin;
  }();

  var dgram$1 = typeof __dirname !== 'undefined' ? require('dgram') : undefined;
  var WebSocketServer = typeof __dirname !== 'undefined' ? require('isomorphic-ws').Server : undefined;
  var STATUS$1 = {
    IS_NOT_INITIALIZED: -1,
    IS_CONNECTING: 0,
    IS_OPEN: 1,
    IS_CLOSING: 2,
    IS_CLOSED: 3
  };
  var defaultOptions$2 = {
    udpServer: {
      host: 'localhost',
      port: 41234,
      exclusive: false
    },
    udpClient: {
      host: 'localhost',
      port: 41235
    },
    wsServer: {
      host: 'localhost',
      port: 8080
    },
    receiver: 'ws'
  };
  function mergeOptions$1(base, custom) {
    return Object.assign({}, defaultOptions$2, base, custom, {
      udpServer: Object.assign({}, defaultOptions$2.udpServer, base.udpServer, custom.udpServer),
      udpClient: Object.assign({}, defaultOptions$2.udpClient, base.udpClient, custom.udpClient),
      wsServer: Object.assign({}, defaultOptions$2.wsServer, base.wsServer, custom.wsServer)
    });
  }
  var BridgePlugin =
  function () {
    function BridgePlugin() {
      var _this = this;
      var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      _classCallCheck(this, BridgePlugin);
      if (!dgram$1 || !WebSocketServer) {
        throw new Error('BridgePlugin can not be used in browser context');
      }
      this.options = mergeOptions$1({}, customOptions);
      this.websocket = null;
      this.socket = dgram$1.createSocket('udp4');
      this.socketStatus = STATUS$1.IS_NOT_INITIALIZED;
      this.socket.on('message', function (message) {
        _this.send(message, {
          receiver: 'ws'
        });
        _this.notify(message.buffer);
      });
      this.socket.on('error', function (error) {
        _this.notify('error', error);
      });
      this.notify = function () {};
    }
    _createClass(BridgePlugin, [{
      key: "registerNotify",
      value: function registerNotify(fn) {
        this.notify = fn;
      }
    }, {
      key: "status",
      value: function status() {
        return this.socketStatus;
      }
    }, {
      key: "open",
      value: function open() {
        var _this2 = this;
        var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var options = mergeOptions$1(this.options, customOptions);
        this.socketStatus = STATUS$1.IS_CONNECTING;
        this.socket.bind({
          address: options.udpServer.host,
          port: options.udpServer.port,
          exclusive: options.udpServer.exclusive
        }, function () {
          _this2.websocket = new WebSocketServer({
            host: options.wsServer.host,
            port: options.wsServer.port
          });
          _this2.websocket.binaryType = 'arraybuffer';
          _this2.websocket.on('listening', function () {
            _this2.socketStatus = STATUS$1.IS_OPEN;
            _this2.notify('open');
          });
          _this2.websocket.on('error', function (error) {
            _this2.notify('error', error);
          });
          _this2.websocket.on('connection', function (client) {
            client.on('message', function (message) {
              _this2.send(message, {
                receiver: 'udp'
              });
              _this2.notify(new Uint8Array(message));
            });
          });
        });
      }
    }, {
      key: "close",
      value: function close() {
        var _this3 = this;
        this.socketStatus = STATUS$1.IS_CLOSING;
        this.socket.close(function () {
          _this3.websocket.close(function () {
            _this3.socketStatus = STATUS$1.IS_CLOSED;
            _this3.notify('close');
          });
        });
      }
    }, {
      key: "send",
      value: function send(binary) {
        var customOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        var options = mergeOptions$1(this.options, customOptions);
        var receiver = options.receiver;
        if (receiver === 'udp') {
          var data = binary instanceof Buffer ? binary : Buffer.from(binary);
          this.socket.send(data, 0, data.byteLength, options.udpClient.port, options.udpClient.host);
        } else if (receiver === 'ws') {
          this.websocket.clients.forEach(function (client) {
            client.send(binary, {
              binary: true
            });
          });
        } else {
          throw new Error('BridgePlugin can not send message to unknown receiver');
        }
      }
    }]);
    return BridgePlugin;
  }();

  var scope = typeof global === 'undefined' ? window : global;
  var WebSocket = typeof __dirname === 'undefined' ? scope.WebSocket : require('isomorphic-ws');
  var STATUS$2 = {
    IS_NOT_INITIALIZED: -1,
    IS_CONNECTING: 0,
    IS_OPEN: 1,
    IS_CLOSING: 2,
    IS_CLOSED: 3
  };
  var defaultOptions$3 = {
    host: 'localhost',
    port: 8080,
    secure: false
  };
  var WebsocketClientPlugin =
  function () {
    function WebsocketClientPlugin(customOptions) {
      _classCallCheck(this, WebsocketClientPlugin);
      if (!WebSocket) {
        throw new Error('WebsocketClientPlugin can\'t find a WebSocket class');
      }
      this.options = Object.assign({}, defaultOptions$3, customOptions);
      this.socket = null;
      this.socketStatus = STATUS$2.IS_NOT_INITIALIZED;
      this.notify = function () {};
    }
    _createClass(WebsocketClientPlugin, [{
      key: "registerNotify",
      value: function registerNotify(fn) {
        this.notify = fn;
      }
    }, {
      key: "status",
      value: function status() {
        return this.socketStatus;
      }
    }, {
      key: "open",
      value: function open() {
        var _this = this;
        var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var options = Object.assign({}, this.options, customOptions);
        var port = options.port,
            host = options.host,
            secure = options.secure;
        if (this.socket) {
          this.close();
        }
        var protocol = secure ? 'wss' : 'ws';
        this.socket = new WebSocket("".concat(protocol, "://").concat(host, ":").concat(port));
        this.socket.binaryType = 'arraybuffer';
        this.socketStatus = STATUS$2.IS_CONNECTING;
        this.socket.onopen = function () {
          _this.socketStatus = STATUS$2.IS_OPEN;
          _this.notify('open');
        };
        this.socket.onclose = function () {
          _this.socketStatus = STATUS$2.IS_CLOSED;
          _this.notify('close');
        };
        this.socket.onerror = function (error) {
          _this.notify('error', error);
        };
        this.socket.onmessage = function (message) {
          _this.notify(message.data);
        };
      }
    }, {
      key: "close",
      value: function close() {
        this.socketStatus = STATUS$2.IS_CLOSING;
        this.socket.close();
      }
    }, {
      key: "send",
      value: function send(binary) {
        this.socket.send(binary);
      }
    }]);
    return WebsocketClientPlugin;
  }();

  var WebSocketServer$1 = typeof __dirname !== 'undefined' ? require('isomorphic-ws').Server : undefined;
  var STATUS$3 = {
    IS_NOT_INITIALIZED: -1,
    IS_CONNECTING: 0,
    IS_OPEN: 1,
    IS_CLOSING: 2,
    IS_CLOSED: 3
  };
  var defaultOptions$4 = {
    host: 'localhost',
    port: 8080
  };
  var WebsocketServerPlugin =
  function () {
    function WebsocketServerPlugin(customOptions) {
      _classCallCheck(this, WebsocketServerPlugin);
      if (!WebSocketServer$1) {
        throw new Error('WebsocketServerPlugin can not be used in browser context');
      }
      this.options = Object.assign({}, defaultOptions$4, customOptions);
      this.socket = null;
      this.socketStatus = STATUS$3.IS_NOT_INITIALIZED;
      this.notify = function () {};
    }
    _createClass(WebsocketServerPlugin, [{
      key: "registerNotify",
      value: function registerNotify(fn) {
        this.notify = fn;
      }
    }, {
      key: "status",
      value: function status() {
        return this.socketStatus;
      }
    }, {
      key: "open",
      value: function open() {
        var _this = this;
        var customOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var options = Object.assign({}, this.options, customOptions);
        var port = options.port,
            host = options.host;
        if (this.socket) {
          this.close();
        }
        this.socket = new WebSocketServer$1({
          host: host,
          port: port
        });
        this.socket.binaryType = 'arraybuffer';
        this.socketStatus = STATUS$3.IS_CONNECTING;
        this.socket.on('listening', function () {
          _this.socketStatus = STATUS$3.IS_OPEN;
          _this.notify('open');
        });
        this.socket.on('error', function (error) {
          _this.notify('error', error);
        });
        this.socket.on('connection', function (client) {
          client.on('message', function (message) {
            _this.notify(new Uint8Array(message));
          });
        });
      }
    }, {
      key: "close",
      value: function close() {
        var _this2 = this;
        this.socketStatus = STATUS$3.IS_CLOSING;
        this.socket.close(function () {
          _this2.socketStatus = STATUS$3.IS_CLOSED;
          _this2.notify('close');
        });
      }
    }, {
      key: "send",
      value: function send(binary) {
        this.socket.clients.forEach(function (client) {
          client.send(binary, {
            binary: true
          });
        });
      }
    }]);
    return WebsocketServerPlugin;
  }();

  var defaultOptions$5 = {
    discardLateMessages: false,
    plugin: new WebsocketClientPlugin()
  };
  var STATUS$4 = {
    IS_NOT_INITIALIZED: -1,
    IS_CONNECTING: 0,
    IS_OPEN: 1,
    IS_CLOSING: 2,
    IS_CLOSED: 3
  };
  var OSC =
  function () {
    function OSC(options) {
      _classCallCheck(this, OSC);
      if (options && !isObject(options)) {
        throw new Error('OSC options argument has to be an object.');
      }
      this.options = Object.assign({}, defaultOptions$5, options);
      this.eventHandler = new EventHandler({
        discardLateMessages: this.options.discardLateMessages
      });
      var eventHandler = this.eventHandler;
      if (this.options.plugin && this.options.plugin.registerNotify) {
        this.options.plugin.registerNotify(function () {
          return eventHandler.notify.apply(eventHandler, arguments);
        });
      }
    }
    _createClass(OSC, [{
      key: "on",
      value: function on(eventName, callback) {
        if (!(isString(eventName) && isFunction(callback))) {
          throw new Error('OSC on() needs event- or address string and callback function');
        }
        return this.eventHandler.on(eventName, callback);
      }
    }, {
      key: "off",
      value: function off(eventName, subscriptionId) {
        if (!(isString(eventName) && isInt(subscriptionId))) {
          throw new Error('OSC off() needs string and number (subscriptionId) to unsubscribe');
        }
        return this.eventHandler.off(eventName, subscriptionId);
      }
    }, {
      key: "open",
      value: function open(options) {
        if (options && !isObject(options)) {
          throw new Error('OSC open() options argument needs to be an object');
        }
        if (!(this.options.plugin && isFunction(this.options.plugin.open))) {
          throw new Error('OSC Plugin API #open is not implemented!');
        }
        return this.options.plugin.open(options);
      }
    }, {
      key: "status",
      value: function status() {
        if (!(this.options.plugin && isFunction(this.options.plugin.status))) {
          throw new Error('OSC Plugin API #status is not implemented!');
        }
        return this.options.plugin.status();
      }
    }, {
      key: "close",
      value: function close() {
        if (!(this.options.plugin && isFunction(this.options.plugin.close))) {
          throw new Error('OSC Plugin API #close is not implemented!');
        }
        return this.options.plugin.close();
      }
    }, {
      key: "send",
      value: function send(packet, options) {
        if (!(this.options.plugin && isFunction(this.options.plugin.send))) {
          throw new Error('OSC Plugin API #send is not implemented!');
        }
        if (!(packet instanceof Message || packet instanceof Bundle || packet instanceof Packet)) {
          throw new Error('OSC send() needs Messages, Bundles or Packets');
        }
        if (options && !isObject(options)) {
          throw new Error('OSC send() options argument has to be an object');
        }
        return this.options.plugin.send(packet.pack(), options);
      }
    }]);
    return OSC;
  }();
  OSC.STATUS = STATUS$4;
  OSC.Packet = Packet;
  OSC.Bundle = Bundle;
  OSC.Message = Message;
  OSC.DatagramPlugin = DatagramPlugin;
  OSC.WebsocketClientPlugin = WebsocketClientPlugin;
  OSC.WebsocketServerPlugin = WebsocketServerPlugin;
  OSC.BridgePlugin = BridgePlugin;

  return OSC;

})));
