text: // license:BSD-3-Clause
  // copyright-holders:Grant Galitz, Katelyn Gadd
  /***************************************************************************
  
  	JSMAME web audio backend v0.4
  
  	Original by katelyn gadd - kg at luminance dot org ; @antumbral on twitter
  	Substantial changes by taisel
  
  ***************************************************************************/
  
  var jsmame_web_audio = (function () {
  
  var context = null;
  var gain_node = null;
  var eventNode = null;
  var sampleScale = 32766;
  var inputBuffer = new Float32Array(44100);
  var bufferSize = 44100;
  var start = 0;
  var rear = 0;
  var watchDogDateLast = null;
  var watchDogTimerEvent = null;
  
  function lazy_init () {
  	//Make
  	if (context) {
  		//Return if already created:
  		return;
  	}
  	if (typeof AudioContext != "undefined") {
  		//Standard context creation:
  		context = new AudioContext();
  	}
  	else if (typeof webkitAudioContext != "undefined") {
  		//Older webkit context creation:
  		context = new webkitAudioContext();
  	}
  	else {
  		//API not found!
  		return;
  	}
  	//Generate a volume control node:
  	gain_node = context.createGain();
  	//Set initial volume to 1:
  	gain_node.gain.value = 1.0;
  	//Connect volume node to output:
  	gain_node.connect(context.destination);
  	//Initialize the streaming event:
  	init_event();
  };
  
  function init_event() {
  	//Generate a streaming node point:
  	if (typeof context.createScriptProcessor == "function") {
  		//Current standard compliant way:
  		eventNode = context.createScriptProcessor(4096, 0, 2);
  	}
  	else {
  		//Deprecated way:
  		eventNode = context.createJavaScriptNode(4096, 0, 2);
  	}
  	//Make our tick function the audio callback function:
  	eventNode.onaudioprocess = tick;
  	//Connect stream to volume control node:
  	eventNode.connect(gain_node);
  	//Workarounds for browser issues:
  	initializeWatchDog();
  };
  
  function initializeWatchDog() {
  	watchDogDateLast = (new Date()).getTime();
  	if (watchDogTimerEvent === null) {
  		watchDogTimerEvent = setInterval(function () {
  			var timeDiff = (new Date()).getTime() - watchDogDateLast;
  			if (timeDiff > 500) {
  				//WORKAROUND FOR FIREFOX BUG:
  				//TODO: decide if we want to user agent sniff Firefox here,
  				//since Google Chrome doesn't need this:
  				disconnect_old_event();
  				init_event();
  
  				//Work around autoplay restrictions in Chrome 71+ https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
  				if (context) {
  					context.resume();
  				}
  			}
  		}, 500);
  	}
  };
  
  function disconnect_old_event() {
  	//Disconnect from audio graph:
  	eventNode.disconnect();
  	//IIRC there was a firefox bug that did not GC this event when nulling the node itself:
  	eventNode.onaudioprocess = null;
  	//Null the glitched/unused node:
  	eventNode = null;
  };
  
  function set_mastervolume (
  	// even though it's 'attenuation' the value is negative, so...
  	attenuation_in_decibels
  ) {
  	lazy_init();
  	if (!context) return;
  
  	// http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels
  	// seemingly incorrect/broken. figures. welcome to Web Audio
  	// var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels);
  
  	// HACK: Max attenuation in JSMESS appears to be 32.
  	// Hit ' then left/right arrow to test.
  	// FIXME: This is linear instead of log10 scale.
  	var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32);
  	if (gain_web_audio < +0)
  		gain_web_audio = +0;
  	else if (gain_web_audio > +1)
  		gain_web_audio = +1;
  
  	gain_node.gain.value = gain_web_audio;
  };
  
  function update_audio_stream (
  	pBuffer,           // pointer into emscripten heap. int16 samples
  	samples_this_frame // int. number of samples at pBuffer address.
  ) {
  	lazy_init();
  	if (!context) return;
  
  	for (
  		var i = 0,
  		l = samples_this_frame | 0;
  		i < l;
  		i++
  	) {
  		var offset =
  			// divide by sizeof(int16_t) since pBuffer is offset
  			//  in bytes
  			((pBuffer / 2) | 0) +
  			((i * 2) | 0);
  
  		var left_sample = HEAP16[offset];
  		var right_sample = HEAP16[(offset + 1) | 0];
  
  		// normalize from signed int16 to signed float
  		var left_sample_float = left_sample / sampleScale;
  		var right_sample_float = right_sample / sampleScale;
  
  		inputBuffer[rear++] = left_sample_float;
  		inputBuffer[rear++] = right_sample_float;
  		if (rear == bufferSize) {
  			rear = 0;
  		}
  		if (start == rear) {
  			start += 2;
  			if (start == bufferSize) {
  				start = 0;
  			}
  		}
  	}
  };
  function tick (event) {
  	//Find all output channels:
  	for (var bufferCount = 0, buffers = []; bufferCount < 2; ++bufferCount) {
  		buffers[bufferCount] = event.outputBuffer.getChannelData(bufferCount);
  	}
  	//Copy samples from the input buffer to the Web Audio API:
  	for (var index = 0; index < 4096 && start != rear; ++index) {
  		buffers[0][index] = inputBuffer[start++];
  		buffers[1][index] = inputBuffer[start++];
  		if (start == bufferSize) {
  			start = 0;
  		}
  	}
  	//Pad with silence if we're underrunning:
  	while (index < 4096) {
  		buffers[0][index] = 0;
  		buffers[1][index++] = 0;
  	}
  	//Deep inside the bowels of vendors bugs,
  	//we're using watchdog for a firefox bug,
  	//where the user agent decides to stop firing events
  	//if the user agent lags out due to system load.
  	//Don't even ask....
  	watchDogDateLast = (new Date()).getTime();
  }
  
  function get_context() {
  	return context;
  };
  
  function sample_count() {
  	//TODO get someone to call this from the emulator,
  	//so the emulator can do proper audio buffering by
  	//knowing how many samples are left:
  	if (!context) {
  		//Use impossible value as an error code:
  		return -1;
  	}
  	var count = rear - start;
  	if (start > rear) {
  		count += bufferSize;
  	}
  	return count;
  }
  
  return {
  	set_mastervolume: set_mastervolume,
  	update_audio_stream: update_audio_stream,
  	get_context: get_context,
  	sample_count: sample_count
  };
  
  })();
  
  window.jsmame_set_mastervolume = jsmame_web_audio.set_mastervolume;
  window.jsmame_update_audio_stream = jsmame_web_audio.update_audio_stream;
  window.jsmame_sample_count = jsmame_web_audio.sample_count;
  