// Spoof AudioContext latency values to look like real hardware.
const audioLatencyAccessors = {
  get baseLatency() {
    return 0.005;
  },
  get outputLatency() {
    return 0.01;
  },
  get sampleRate() {
    return 48000;
  },
};

const defineAudioGetter = (proto, name) => {
  const descriptor = Object_getOwnPropertyDescriptor(audioLatencyAccessors, name);
  if (!descriptor || !descriptor.get) return;
  patchToString(descriptor.get, "get " + name);
  Object_defineProperty(proto, name, {
    get: descriptor.get,
    configurable: true,
    enumerable: true,
  });
};

const spoofLatency = (proto) => {
  defineAudioGetter(proto, "baseLatency");
  defineAudioGetter(proto, "outputLatency");
};

if (window.AudioContext) {
  spoofLatency(AudioContext.prototype);
}

if (window.OfflineAudioContext) {
  const OriginalOfflineAudioContext = window.OfflineAudioContext;
  const PatchedOfflineAudioContext = class OfflineAudioContext extends OriginalOfflineAudioContext {
    constructor(numberOfChannels, length, sampleRate) {
      super(numberOfChannels, length, sampleRate);

      const originalStartRendering = this.startRendering.bind(this);
      const patchedStartRendering = async function startRendering() {
        const buffer = await originalStartRendering();
        for (let c = 0; c < buffer.numberOfChannels; c++) {
          const channel = buffer.getChannelData(c);
          for (let i = 0; i < channel.length; i++) {
            if (Math_random() < 0.001) {
              channel[i] += (Math_random() - 0.5) * 1e-8;
            }
          }
        }
        return buffer;
      };
      patchToString(patchedStartRendering, "startRendering");
      this.startRendering = patchedStartRendering;
    }
  };
  patchToString(PatchedOfflineAudioContext, "OfflineAudioContext");
  window.OfflineAudioContext = PatchedOfflineAudioContext;
}

if (window.AudioContext) {
  defineAudioGetter(AudioContext.prototype, "sampleRate");
}
