Commits

Eric Fredricksen committed e77750b

Initial commit after moving here from planetesimals project

  • Participants

Comments (0)

Files changed (1)

File bytebeat.html

+<!DOCTYPE html>
+<head>
+<title>Bytebeat Composer</title>
+<link href='http://fonts.googleapis.com/css?family=PT+Sans' rel='stylesheet'/>
+<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet'/>
+<style>
+* {
+  font-family: PT Sans, Tahoma, sans serif;
+  font-size: 14pt;
+}
+   input {
+     font-family: PT Mono, Courier, serif;
+   }
+h2 {
+  font-size: 16pt;
+}
+button {
+  width: 40px;
+  height: 40px;
+}
+.err {
+  color: grey;
+}
+body {
+  margin: 4em;
+  color: #dfd;
+  background-color: #333; 
+}
+  
+a, a:hover, a:visited {
+  color: #afa;
+  text-decoration: none;
+  font-weight: 400;
+}
+
+</style>
+<h2>
+Bytebeat Composer
+</h2>
+<hr>
+<h3>
+  Formula
+</h3>
+  <input id=formula size=100 type=text 
+   value="((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7" 
+   onKeyUp="reformulate(this.value)">
+<br>
+  <button onclick='bits(-1)'> - </button>
+  <span id=bits>10</span> bits
+  <button onclick='bits(+1)'> + </button>
+    &nbsp;    &nbsp;    &nbsp;    &nbsp;
+  <button onclick='node.pause()'> &#x25fb; </button>
+  <button onclick='node.play()'> &#x25b7; </button>
+<p>
+<hr>
+<h3>About</h3>
+<p>
+  <a href=http://canonical.org/~kragen/bytebeat/>Bytebeat</a> is
+  music, or at least audio, generated using a one-liner formula.
+  Here's some points to note about this
+  particular player:
+<ul>
+
+<li>It is implemented using the <a href=
+  https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html>
+  HTML5 Web Audio API</a>, currently supported only by Chrome.
+
+<li> The generated waveform changes live as the generating formula is
+  altered.
+
+<li> The waveform is scaled from any arbitrary number of bits, rather
+  than the typical eight.
+
+<li> The sample rate is fixed at 44100 Hz; compositions for 8 kHz
+  audio will sound quite different here.
+
+<li> The playback is stereo with the right channel on a slight delay
+  to add some space.
+
+<li> The default composition is "Crowd" by Kragen Javier Sitaker.
+</ul>
+
+<script src=live.js></script>
+<script>
+
+var context = new webkitAudioContext();
+var CHANNELS = 2;
+var node = context.createJavaScriptNode(4096, 0, CHANNELS);
+
+var generator = function (t) {
+  return ((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7;
+}
+
+function reformulate(v) {
+  try {
+    var t = 0;
+    eval(v);
+    var e = "generator = function (t) { return " + v + "; }";
+    console.log(e);
+    eval(e);
+    document.getElementById('formula').setAttribute('class', '');
+  } catch (e) {
+    document.getElementById('formula').setAttribute('class', 'err');
+  }
+}
+
+var x = 0;
+node.sample_rate = context.sampleRate;
+node.gain = 0.25;
+var DELAY = Math.round(node.sample_rate * 0.015);
+var BITS = 10;
+node.rightfn = function (t) { return generator(t+DELAY); };
+node.onaudioprocess = function (e) {
+  var mask = (1 << BITS) - 1;
+  var div = 1 << (BITS - 1);
+  var left = e.outputBuffer.getChannelData(0);
+  if (CHANNELS > 1)
+    var right = e.outputBuffer.getChannelData(1);
+  for (var i = 0; i < left.length; ++i) {
+    t = x++;
+    left[i] = this.gain * ((mask & generator(t)) / div - 1);
+    if (CHANNELS > 1)
+      right[i] = this.gain * ((mask & this.rightfn(t)) / div - 1); 
+  }
+}
+  
+var filter = context.createBiquadFilter();
+if (filter) {
+  filter.type = filter.LOWPASS;
+  filter.frequency.value = 4000;
+  filter.Q.value = 0.75;
+}
+
+var reverb// = context.createConvolver();
+if (reverb) {
+  /*
+  reverb.buffer = context.createBuffer(CHANNELS, 2 * context.sampleRate, 
+                                       context.sampleRate);
+  for (var c = 0; c < reverb.buffer.numberOfChannels; ++c) {
+    var d = reverb.buffer.getChannelData(c);
+    for (var i = 0; i < reverb.buffer.length; ++i)
+      //d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / reverb.buffer.length, 2);
+      d[i] = (Math.sin(i / 10) * 2 - 1) * Math.pow(1 - i / reverb.buffer.length, 2);
+  }
+  */
+  loadWav('feedback-spring.wav', function (buffer) { reverb.buffer = buffer; });
+}
+
+var delay = context.createDelayNode();
+if (delay) {
+  delay.delayTime.value = .4;
+}
+
+var compressor = context.createDynamicsCompressor();
+if (compressor) compressor = context.createGainNode();  // shunt
+
+node.playing = false;
+
+var source = context.createGainNode();
+var endpoint = source;
+
+if (filter) {
+  endpoint.connect(filter);
+  endpoint = filter;
+}
+
+if (reverb) {
+  source.connect(reverb);
+  
+  var g = context.createGainNode();
+  reverb.connect(g);
+  
+  g.connect(compressor||context.destination);
+  
+  /*
+    var s = context.createBufferSource();
+    s.buffer = reverb.buffer;
+    s.connect(context.destination);
+    s.noteOn(0);
+    */
+}
+
+if (delay) {
+  endpoint.connect(delay);
+  var g = context.createGainNode();
+  delay.connect(g);
+  g.gain.value = 0.6;
+  g.connect(compressor||context.destination);
+  g.connect(delay);
+}
+
+endpoint.connect(compressor);
+compressor.connect(context.destination);
+
+node.play = function () {
+  if (this.playing) return;
+  node.connect(source);
+  //compressor.connect(context.destination);
+  this.playing = true;
+}
+
+node.pause = function () {
+  if (!this.playing) return;
+  node.disconnect();
+  //compressor.disconnect();
+  this.playing = false;
+}
+
+function bits(inc) {
+  BITS += inc;
+  document.getElementById('bits').innerText = BITS;
+}
+
+function loadWav(url, callback) {
+  var request = new XMLHttpRequest();
+  request.open("GET", url, true);
+  request.responseType = "arraybuffer";
+  request.onload = function() {
+    context.decodeAudioData(this.response, callback);
+  }
+  request.onerror = function() { console.log('XHR ERROR SOUND', this.sound); }
+  request.send();
+}
+
+
+bits(0);
+node.play();
+
+</script>