text: import events from 'events'
  import { BufferEncoder, decodeAudioData } from './utils.js'
  import { BLOCK_SIZE } from './constants.js'
  import AudioBuffer from './AudioBuffer.js'
  import AudioListener from './AudioListener.js'
  import AudioDestinationNode from './AudioDestinationNode.js'
  import AudioBufferSourceNode from './AudioBufferSourceNode.js'
  import GainNode from './GainNode.js'
  import ScriptProcessorNode from './ScriptProcessorNode.js'
  import PannerNode from './PannerNode/index.js'
  
  
  class AudioContext extends events.EventEmitter {
    #playing=true
  
    constructor(opts) {
      super();
  
      var outBuff
  
      /*Object.defineProperty(this, 'currentTime', {
      writable: false,
      get: function() {}
    })*/
  
      Object.defineProperty(this, 'destination', {
        writable: false,
        value: new AudioDestinationNode(this)
      })
      //this.destination = new AudioDestinationNode(this)
  
      // TODO
      // Object.defineProperty(this, 'sampleRate', {
      //   writable: false,
      //   value: {}
      // })
  
      Object.defineProperty(this, 'listener', {
        writable : false,
        value    : new AudioListener(),
      })
  
      this.currentTime = 0
      this.sampleRate = 44100
      this.numberOfChannels = 2
      this.bitDepth = 16
  
      this.format = {
        numberOfChannels: 2,
        bitDepth: 16,
        sampleRate: this.sampleRate
      }
  
      opts = opts || {}
      if (opts.bufferSize) this.format.bufferSize = opts.bufferSize
      if (opts.numBuffers) this.format.numBuffers = opts.numBuffers
  
      this.outStream = null
  
      let frame = 0,
          audioOutLoopRunning = false,
          encoder = BufferEncoder(this.format)
  
      const tick = () => {
        if (!this.#playing) return
        try {
          outBuff = this.destination._tick()
          // If there is space in the output stream's buffers, we write,
          // otherwise we wait for 'drain'
          frame += BLOCK_SIZE
          this.currentTime = frame * 1 / this.sampleRate
          // TODO setImmediate here is for cases where the outStream won't get
          // full and we end up with call stack max size reached.
          // But is it optimal?
          if (this.outStream.write(encoder(outBuff._data))) setImmediate(tick)
          else this.outStream.once('drain', tick)
        } catch (e) {
          audioOutLoopRunning = false
          if (err) return this.emit('error', err)
        }
      }
  
      // When a new connection is established, start to pull audio
      this.destination._inputs[0].on('connection', () => {
        if (audioOutLoopRunning) return
        if (!this.outStream) throw new Error('you need to set outStream to send the audio somewhere')
        audioOutLoopRunning = true
        tick()
      })
    }
  
    createBuffer(numberOfChannels, length, sampleRate) {
      return new AudioBuffer(numberOfChannels, length, sampleRate)
    }
  
    decodeAudioData(audioData, successCallback, errorCallback) {
      // Pseudo overload
      if (arguments.length > 1) {
        // Callback
        decodeAudioData(audioData, function(err, audioBuffer) {
          if (err) errorCallback(err)
          else successCallback(audioBuffer)
        })
      } else {
        // Promise
        return decodeAudioData(audioData)
      }
    }
  
    createBufferSource() {
      return new AudioBufferSourceNode(this)
    }
  
    createGain() {
      return new GainNode(this)
    }
  
    createScriptProcessor(bufferSize, numberOfInputChannels, numberOfOutputChannels) {
      return new ScriptProcessorNode(this, bufferSize, numberOfInputChannels, numberOfOutputChannels)
    }
  
    createPanner() {
      return new PannerNode(this)
    }
  
    /*
  {
  
      readonly attribute AudioDestinationNode destination
      readonly attribute float sampleRate
      readonly attribute double currentTime
      readonly attribute AudioListener listener
  
      // AudioNode creation
      MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement)
  
      MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream)
      MediaStreamAudioDestinationNode createMediaStreamDestination()
  
      AnalyserNode createAnalyser()
      DelayNode createDelay(optional double maxDelayTime = 1.0)
      BiquadFilterNode createBiquadFilter()
      WaveShaperNode createWaveShaper()
      PannerNode createPanner()
      ConvolverNode createConvolver()
  
      ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs = 6)
      ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6)
  
      DynamicsCompressorNode createDynamicsCompressor()
  
      OscillatorNode createOscillator()
      PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag)
  
  }
    */
  
    [Symbol.dispose]() {
      this.#playing = false
      if (this.outStream) this.outStream.close?.() || this.outStream.end?.()
    }
  }
  
  export default AudioContext
  