async enable(options = {}, legacy = false) {

    /*START-ESM*/

    // This block is stripped out of the IIFE and CJS versions where it isn't needed.

    // If this code is executed by Node.js in "module" mode (when "type": "module" is used in the
    // package.json file), then we must import the `jzz` module. I import it in this convoluted way
    // to prevent Webpack from automatically bundling it in browser bundles where it isn't needed.
    if (Utilities.isNode) {

      // Some environments may have both Node.js and browser runtimes (Electron, NW.js, React
      // Native, etc.) so we also check for the presence of the window.navigator property.
      try {
        window.navigator;
      } catch (err) {
        let jzz = await Object.getPrototypeOf(async function() {}).constructor(`
        let jzz = await import("jzz");
        return jzz.default;
        `)();
        if (!global.navigator) global.navigator = {}; // for Node.js prior to v21
        Object.assign(global.navigator, jzz);
      }

      // The `performance` module appeared in Node.js v8.5.0 but has started to be automatically
      // imported only in v16+.
      try {
        performance;
      } catch (err) {
        global.performance = await Object.getPrototypeOf(async function() {}).constructor(`
        let perf_hooks = await import("perf_hooks");
        return perf_hooks.performance;
        `)();
      }

    }

    /*END-ESM*/

    this.validation = (options.validation !== false);

    if (this.validation) {
      // Backwards-compatibility. Previous syntax was: enable(callback, sysex)
      if (typeof options === "function") options = {callback: options, sysex: legacy};
      if (legacy) options.sysex = true;
    }

    // If already enabled, trigger callback and resolve promise but do not dispatch events.
    if (this.enabled) {
      if (typeof options.callback === "function") options.callback();
      return Promise.resolve();
    }

    /**
     * Event emitted when an error occurs trying to enable `WebMidi`
     *
     * @event WebMidi#error
     * @type {object}
     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in
     * milliseconds since the navigation start of the document).
     * @property {WebMidi} target The object that triggered the event
     * @property {string} type `error`
     * @property {*} error Actual error that occurred
     */
    const errorEvent = {
      timestamp: this.time,
      target: this,
      type: "error",
      error: undefined
    };

    /**
     * Event emitted once the MIDI interface has been successfully created (which implies user has
     * granted access to MIDI).
     *
     * @event WebMidi#midiaccessgranted
     * @type {object}
     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in milliseconds
     * since the navigation start of the document).
     * @property {WebMidi} target The object that triggered the event
     * @property {string} type `midiaccessgranted`
     */
    const midiAccessGrantedEvent = {
      timestamp: this.time,
      target: this,
      type: "midiaccessgranted"
    };

    /**
     * Event emitted once `WebMidi` has been fully enabled
     *
     * @event WebMidi#enabled
     * @type {object}
     * @property {DOMHighResTimeStamp} timestamp The moment when the event occurred (in milliseconds
     * since the navigation start of the document).
     * @property {WebMidi} target The object that triggered the event
     * @property {string} type `"enabled"`
     */
    const enabledEvent = {
      timestamp: this.time,
      target: this,
      type: "enabled"
    };

    // Request MIDI access (this is where the prompt will appear)
    try {
      if (typeof options.requestMIDIAccessFunction === "function") {
        this.interface = await options.requestMIDIAccessFunction(
          {sysex: options.sysex, software: options.software}
        );
      } else {
        this.interface = await navigator.requestMIDIAccess(
          {sysex: options.sysex, software: options.software}
        );
      }
    } catch(err) {
      errorEvent.error = err;
      this.emit("error", errorEvent);
      if (typeof options.callback === "function") options.callback(err);
      return Promise.reject(err);
    }

    // Now that the Web MIDI API interface has been created, we trigger the 'midiaccessgranted'
    // event. This allows the developer an occasion to assign listeners on 'connected' events.
    this.emit("midiaccessgranted", midiAccessGrantedEvent);

    // We setup the state change listener before creating the ports so that it properly catches the
    // the ports' `connected` events
    this.interface.onstatechange = this._onInterfaceStateChange.bind(this);

    // Update inputs and outputs (this is where `Input` and `Output` objects are created).
    try {
      await this._updateInputsAndOutputs();
    } catch (err) {
      errorEvent.error = err;
      this.emit("error", errorEvent);
      if (typeof options.callback === "function") options.callback(err);
      return Promise.reject(err);
    }

    // If we make it here, the ports have been successfully created, so we trigger the 'enabled'
    // event.
    this.emit("enabled", enabledEvent);

    // Execute the callback (if any) and resolve the promise with 'this' (for chainability)
    if (typeof options.callback === "function") options.callback();
    return Promise.resolve(this);

  }
Checks if the Web MIDI API is available in the current environment and then tries to connect to
the host's MIDI subsystem. This is an asynchronous operation and it causes a security prompt to
be displayed to the user.

To enable the use of MIDI system exclusive messages, the `sysex` option should be set to
`true`. However, under some environments (e.g. Jazz-Plugin), the `sysex` option is ignored
and system exclusive messages are always enabled. You can check the
[`sysexEnabled`](#sysexEnabled) property to confirm.

To enable access to software synthesizers available on the host, you would set the `software`
option to `true`. However, this option is only there to future-proof the library as support for
software synths has not yet been implemented in any browser (as of September 2021).

By the way, if you call the [`enable()`](#enable) method while WebMidi.js is already enabled,
the callback function will be executed (if any), the promise will resolve but the events
([`"midiaccessgranted"`](#event:midiaccessgranted), [`"connected"`](#event:connected) and
[`"enabled"`](#event:enabled)) will not be fired.

There are 3 ways to execute code after `WebMidi` has been enabled:

- Pass a callback function in the `options`
- Listen to the [`"enabled"`](#event:enabled) event
- Wait for the promise to resolve

In order, this is what happens towards the end of the enabling process:

1. [`"midiaccessgranted"`](#event:midiaccessgranted) event is triggered once the user has
granted access to use MIDI.
2. [`"connected"`](#event:connected) events are triggered (for each available input and output)
3. [`"enabled"`](#event:enabled) event is triggered when WebMidi.js is fully ready
4. specified callback (if any) is executed
5. promise is resolved and fulfilled with the `WebMidi` object.

**Important note**: starting with Chrome v77, a page using Web MIDI API must be hosted on a
secure origin (`https://`, `localhost` or `file:///`) and the user will always be prompted to
authorize the operation (no matter if the `sysex` option is `true` or not).

##### Example
```js
// Enabling WebMidi and using the promise
WebMidi.enable().then(() => {
  console.log("WebMidi.js has been enabled!");
})
```

@param [options] {object}

@param [options.callback] {function} A function to execute once the operation completes. This
function will receive an `Error` object if enabling the Web MIDI API failed.

@param [options.sysex=false] {boolean} Whether to enable MIDI system exclusive messages or not.

@param [options.validation=true] {boolean} Whether to enable library-wide validation of method
arguments and setter values. This is an advanced setting that should be used carefully. Setting
[`validation`](#validation) to `false` improves performance but should only be done once the
project has been thoroughly tested with [`validation`](#validation)  turned on.

@param [options.software=false] {boolean} Whether to request access to software synthesizers on
the host system. This is part of the spec but has not yet been implemented by most browsers as
of April 2020.

@param [options.requestMIDIAccessFunction] {function} A custom function to use to return
the MIDIAccess object. This is useful if you want to use a polyfill for the Web MIDI API
or if you want to use a custom implementation of the Web MIDI API - probably for testing
purposes.

@async

@returns {Promise.<WebMidi>} The promise is fulfilled with the `WebMidi` object for
chainability

@throws {Error} The Web MIDI API is not supported in your environment.
@throws {Error} Jazz-Plugin must be installed to use WebMIDIAPIShim.