<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://en.xen.wiki/index.php?action=history&amp;feed=atom&amp;title=User%3AXenwolf%2Fcommon.js</id>
	<title>User:Xenwolf/common.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://en.xen.wiki/index.php?action=history&amp;feed=atom&amp;title=User%3AXenwolf%2Fcommon.js"/>
	<link rel="alternate" type="text/html" href="https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;action=history"/>
	<updated>2026-07-21T04:36:59Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;diff=98877&amp;oldid=prev</id>
		<title>Xenwolf: copy from User:Plumtree/common.js</title>
		<link rel="alternate" type="text/html" href="https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;diff=98877&amp;oldid=prev"/>
		<updated>2022-11-07T08:20:10Z</updated>

		<summary type="html">&lt;p&gt;copy from &lt;a href=&quot;/w/User:Plumtree/common.js&quot; title=&quot;User:Plumtree/common.js&quot;&gt;User:Plumtree/common.js&lt;/a&gt;&lt;/p&gt;
&lt;a href=&quot;https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;amp;diff=98877&amp;amp;oldid=98508&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Xenwolf</name></author>
	</entry>
	<entry>
		<id>https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;diff=98508&amp;oldid=prev</id>
		<title>Xenwolf: test in-browser sound generation</title>
		<link rel="alternate" type="text/html" href="https://en.xen.wiki/index.php?title=User:Xenwolf/common.js&amp;diff=98508&amp;oldid=prev"/>
		<updated>2022-10-26T18:28:15Z</updated>

		<summary type="html">&lt;p&gt;test in-browser sound generation&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;const AudioContext = window.AudioContext || window.webkitAudioContext;&lt;br /&gt;
const audio = new AudioContext();&lt;br /&gt;
&lt;br /&gt;
// the first element should be 0&lt;br /&gt;
const builtin_spectra = {&lt;br /&gt;
	sine: [0, 1],&lt;br /&gt;
	semisine: [0],&lt;br /&gt;
	flute: [0, 1, 0.55, 0.2, 0.2, 0.05],&lt;br /&gt;
	violin: [0, 1, 0.5, 0.45, 0.5, 0.7, 0.6, 0.75, 0.6, 0.45, 0.35, 0.25, 0.05, 0.2, 0.15],&lt;br /&gt;
};&lt;br /&gt;
for (var i = 1; i &amp;lt;= 100; i++)&lt;br /&gt;
	builtin_spectra.semisine.push(1 / (i * i));&lt;br /&gt;
&lt;br /&gt;
function construct_wave(harmonic_spectrum){&lt;br /&gt;
	var zeros = new Array(harmonic_spectrum.length).fill(0);&lt;br /&gt;
	return audio.createPeriodicWave(harmonic_spectrum, zeros);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// delay `node` by `duration` ms&lt;br /&gt;
function delay(node, duration){&lt;br /&gt;
	var delay = audio.createDelay(120);&lt;br /&gt;
	delay.delayTime.value = duration / 1000;&lt;br /&gt;
	node.connect(delay);&lt;br /&gt;
	return delay;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// connect `nodes` to audio destination and schedule a disconnect after `duration` ms&lt;br /&gt;
function connect_all(nodes, duration){&lt;br /&gt;
	for (var i = 0; i &amp;lt; nodes.length; i++)&lt;br /&gt;
		nodes[i].connect(audio.destination);&lt;br /&gt;
	&lt;br /&gt;
	setTimeout(function(){&lt;br /&gt;
		for (var i = 0; i &amp;lt; nodes.length; i++)&lt;br /&gt;
			nodes[i].disconnect();&lt;br /&gt;
	}, duration);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// limit `node` to ~`duration` ms, also filtering out frequencies &amp;gt;= 20000Hz&lt;br /&gt;
function limit(node, duration){&lt;br /&gt;
	var gain = audio.createGain();&lt;br /&gt;
	gain.gain.value = 0;&lt;br /&gt;
	gain.gain.linearRampToValueAtTime(1, audio.currentTime + 0.05);&lt;br /&gt;
	gain.gain.linearRampToValueAtTime(1, audio.currentTime + duration / 1000);&lt;br /&gt;
	gain.gain.exponentialRampToValueAtTime(0.00001, audio.currentTime + duration / 1000 + 0.05);&lt;br /&gt;
	node.connect(gain);&lt;br /&gt;
	node = gain;&lt;br /&gt;
	&lt;br /&gt;
	var filter = audio.createBiquadFilter();&lt;br /&gt;
	filter.type = &amp;#039;lowpass&amp;#039;;&lt;br /&gt;
	filter.frequency.value = 20000;&lt;br /&gt;
	node.connect(filter);&lt;br /&gt;
	return filter;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// set gain of the `node` to `value`&lt;br /&gt;
function set_gain(node, value){&lt;br /&gt;
	var gain = audio.createGain();&lt;br /&gt;
	gain.gain.value = value;&lt;br /&gt;
	node.connect(gain);&lt;br /&gt;
	return gain;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// construct an oscillator sound active for `duration` ms after `shift` ms&lt;br /&gt;
function play_frequency(frequency, duration, shift, gain, wave){&lt;br /&gt;
	var node = audio.createOscillator();&lt;br /&gt;
	node.frequency.value = frequency;&lt;br /&gt;
	node.setPeriodicWave(wave);&lt;br /&gt;
	node.start();&lt;br /&gt;
	node.stop(audio.currentTime + duration / 1000 + 0.1);&lt;br /&gt;
	&lt;br /&gt;
	node = limit(node, duration);&lt;br /&gt;
	node = set_gain(node, gain);&lt;br /&gt;
	node = delay(node, shift);&lt;br /&gt;
	&lt;br /&gt;
	return node;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// whitespace-separated tokens which are either:&lt;br /&gt;
// * wait:DURATION - increase global time shift by DURATION ms.&lt;br /&gt;
// * FREQUENCY:DURATION:GAIN[:SHIFT[:INSTRUMENT]] - see play_frequency().&lt;br /&gt;
// SHIFT here is local time shift, global time shift will remain intact. Default: 0.&lt;br /&gt;
// INSTRUMENT is the name of the corresponding instrument. Default: sine.&lt;br /&gt;
function parse_sequence(s){&lt;br /&gt;
	var output = [];&lt;br /&gt;
	var notes = s.split(/\s+/);&lt;br /&gt;
	for (var i = 0; i &amp;lt; notes.length; i++){&lt;br /&gt;
		var values = notes[i].split(&amp;#039;:&amp;#039;);&lt;br /&gt;
		var note_data = {};&lt;br /&gt;
		if (values[0] == &amp;#039;wait&amp;#039;){&lt;br /&gt;
			note_data.wait = true;&lt;br /&gt;
			note_data.dur = parseInt(values[1]); // duration in ms&lt;br /&gt;
		} else {&lt;br /&gt;
			note_data.freq = parseFloat(values[0]); // frequency&lt;br /&gt;
			note_data.dur = parseInt(values[1]); // duration in ms&lt;br /&gt;
			note_data.gain = parseFloat(values[2]); // gain&lt;br /&gt;
			note_data.shift = parseInt(values[3] || &amp;#039;0&amp;#039;); // shift in ms&lt;br /&gt;
			note_data.instrument = values[4] || &amp;#039;sine&amp;#039;;&lt;br /&gt;
		}&lt;br /&gt;
		output.push(note_data);&lt;br /&gt;
	}&lt;br /&gt;
	return output;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// parse and play a sequence&lt;br /&gt;
function demonstrate_sequence(s, custom_spectra){&lt;br /&gt;
	var seq = parse_sequence(s);&lt;br /&gt;
	&lt;br /&gt;
	var waves = {};&lt;br /&gt;
	for (var i = 0; i &amp;lt; seq.length; i++){&lt;br /&gt;
		if (seq[i].instrument &amp;amp;&amp;amp; waves[seq[i].instrument] === undefined){&lt;br /&gt;
			waves[seq[i].instrument] = construct_wave(&lt;br /&gt;
				builtin_spectra[seq[i].instrument] || custom_spectra[seq[i].instrument]&lt;br /&gt;
			);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	var notes = [];&lt;br /&gt;
	var total_duration = 0;&lt;br /&gt;
	var total_shift = 0;&lt;br /&gt;
	for (var i = 0; i &amp;lt; seq.length; i++){&lt;br /&gt;
		if (seq[i].wait){&lt;br /&gt;
			total_shift += seq[i].dur;&lt;br /&gt;
		} else {&lt;br /&gt;
			notes.push(play_frequency(&lt;br /&gt;
				seq[i].freq,&lt;br /&gt;
				seq[i].dur,&lt;br /&gt;
				total_shift + seq[i].shift,&lt;br /&gt;
				seq[i].gain,&lt;br /&gt;
				waves[seq[i].instrument]&lt;br /&gt;
			));&lt;br /&gt;
			if (total_shift + seq[i].shift + seq[i].dur &amp;gt; total_duration)&lt;br /&gt;
				total_duration = total_shift + seq[i].shift + seq[i].dur;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	total_duration += 100;&lt;br /&gt;
	&lt;br /&gt;
	connect_all(notes, total_duration);&lt;br /&gt;
	&lt;br /&gt;
	return total_duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
document.querySelectorAll(&amp;#039;.sequence-audio&amp;#039;).forEach(function(button){&lt;br /&gt;
	// error handling&lt;br /&gt;
	if (!button.hasAttribute(&amp;#039;data-sequence&amp;#039;)){&lt;br /&gt;
		console.error(&amp;#039;Audio element requires &amp;quot;data-sequence&amp;quot; attribute to be set!&amp;#039;);&lt;br /&gt;
		console.log(button);&lt;br /&gt;
		button.classList.add(&amp;#039;sequence-audio-invalid&amp;#039;);&lt;br /&gt;
		return;&lt;br /&gt;
	}&lt;br /&gt;
	if (button.hasAttribute(&amp;#039;data-playing&amp;#039;)){&lt;br /&gt;
		console.error(&amp;#039;Audio element should not have &amp;quot;data-playing&amp;quot; attribute set!&amp;#039;);&lt;br /&gt;
		console.log(button);&lt;br /&gt;
		button.classList.add(&amp;#039;sequence-audio-invalid&amp;#039;);&lt;br /&gt;
		return;&lt;br /&gt;
	}&lt;br /&gt;
	var seq = parse_sequence(button.getAttribute(&amp;#039;data-sequence&amp;#039;));&lt;br /&gt;
	for (var i = 0; i &amp;lt; seq.length; i++){&lt;br /&gt;
		if (seq[i].instrument &amp;amp;&amp;amp; builtin_spectra[seq[i].instrument] === undefined){&lt;br /&gt;
			if (!button.hasAttribute(&amp;#039;data-timbre-&amp;#039; + seq[i].instrument)){&lt;br /&gt;
				console.error(&amp;#039;Audio element requires &amp;quot;data-timbre-&amp;#039; + seq[i].instrument + &amp;#039;&amp;quot; attribute to be set!&amp;#039;);&lt;br /&gt;
				console.log(button);&lt;br /&gt;
				button.classList.add(&amp;#039;sequence-audio-invalid&amp;#039;);&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	// indicate that audio JS has been loaded&lt;br /&gt;
	button.classList.add(&amp;#039;sequence-audio-valid&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	button.addEventListener(&amp;#039;click&amp;#039;, function(){&lt;br /&gt;
		// should the button lock while the sequence is playing?&lt;br /&gt;
		var lock = button.hasAttribute(&amp;#039;data-lock&amp;#039;);&lt;br /&gt;
		if (lock &amp;amp;&amp;amp; button.hasAttribute(&amp;#039;data-playing&amp;#039;)){&lt;br /&gt;
			// do nothing&lt;br /&gt;
		} else {&lt;br /&gt;
			if (lock){&lt;br /&gt;
				button.setAttribute(&amp;#039;data-playing&amp;#039;, &amp;#039;true&amp;#039;);&lt;br /&gt;
				button.classList.add(&amp;#039;sequence-audio-playing&amp;#039;);&lt;br /&gt;
			}&lt;br /&gt;
			var s = button.getAttribute(&amp;#039;data-sequence&amp;#039;);&lt;br /&gt;
			&lt;br /&gt;
			var custom_spectra = {};&lt;br /&gt;
			for (var i = 0; i &amp;lt; button.attributes.length; i++){&lt;br /&gt;
				if (button.attributes[i].name.startsWith(&amp;#039;data-timbre-&amp;#039;)){&lt;br /&gt;
					var key = button.attributes[i].name.substring(12);&lt;br /&gt;
					var val = button.attributes[i].value.split(/\s+/);&lt;br /&gt;
					for (var j = 0; j &amp;lt; val.length; j++)&lt;br /&gt;
						val[j] = parseFloat(val[j]);&lt;br /&gt;
					custom_spectra[key] = val;&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
			console.log(&amp;#039;Custom spectra:&amp;#039;, custom_spectra);&lt;br /&gt;
			console.log(&amp;#039;Playing sequence: &amp;#039; + s);&lt;br /&gt;
			var duration = demonstrate_sequence(s, custom_spectra);&lt;br /&gt;
			&lt;br /&gt;
			if (lock){&lt;br /&gt;
				setTimeout(function(){&lt;br /&gt;
					button.classList.remove(&amp;#039;sequence-audio-playing&amp;#039;);&lt;br /&gt;
					button.removeAttribute(&amp;#039;data-playing&amp;#039;);&lt;br /&gt;
				}, duration);&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	});&lt;br /&gt;
});&lt;/div&gt;</summary>
		<author><name>Xenwolf</name></author>
	</entry>
</feed>