StackBeat

From Esolang
Jump to navigation Jump to search

StackBeat is a specialized stack-based esoteric programming language. It was designed with the goal to combine a compact stack machine implementation with the wonders of raw sound synthesis using the Bytebeat technique. The first working StackBeat implementation was created by User:Plugnburn on August 21, 2013, then it was updated on August 26, 2013 to its current state.

Language structure and program flow

How it works

A StackBeat program represents a single expression that takes the integer timestamp as an input on the stack top and must leave computed signal level at the stack top as the output. StackBeat is configured to generate 8KHz 8-bit mono PCM audio. So, to understand the process better, the following steps are performed virtually 8000 times per second by the interpreter:

  1. The timestamp is incremented and the stack is initialized with the timestamp value on the top, as well as a special register called Timestamp Register (TR);
  2. The StackBeat program takes this stack and TR, performs some computations and leaves the output value at the stack top;
  3. This top value is reduced to just one byte and sent to audio output.

Note that this is only a theoretical model of the process, the real implementation is far more different because the straightforward implementation of the above would be very slow.

Instruction set

StackBeat program always begins with integer output waveform duration (in seconds), followed by the : character and the actual instructions.

StackBeat has a very simple instruction set. It consists of 5 stack operations, 2 unary operations and 10 binary operations. Anything not in the following list is not allowed in the code.

Stack operations

  • 123456 - numeric push operation: just write any decimal integer number directly in the code and it will be pushed onto the stack.
  • _ - TR push operation: push the value from TR register (that holds current timestamp) onto the stack.
  • @ - duplicate the top value on the stack.
  • $ - drop the stack top value.
  • # - swap two top stack values.

Unary operations

  • ~ - bitwise NOT (inversion) of the stack top.
  • ! - logical NOT (boolean inversion) of the stack top, casted to integer: the top becomes 0 if it wasn't 0, and 1 if it was 0.

Binary operations

StackBeat has the following binary operation set: +,-,*,/,>,<,^,&,|,%. All of them are identical to the equivalent C (or JS) operators, except > and < that are actually bitwise shifting operators, equal to >> and << in C respectively.

For all these binary operations the interpreter pops the first, then the second operand from the stack, performs the operation and pushes the result onto the stack. The important note is that the first operand must always be on the stack top.

Examples

"Crowd", a Hello World in bytebeat world, 2 minutes long:

120:7#>19_>7&1^4-_>1_<7_>_&+12#>1_<^||

The famous "42 melody", 1 minute long:

60:10#>42&_*

"Droid" composition, 25 seconds long:

25:@6#>1&1#<1#-#@6#>1&5-#>*

Some cyber beat from gasman (30.09.2011), 16 seconds long:

16:5#>@2_>*|

Other examples are coming soon. ;)

Implementation

The original golfed reference implementation in JavaScript (ECMAScript 5 standard) takes StackBeat code as the single parameter and returns ready WAV file data: URL:

function(w,a,v,e,r){w=w.split(":");for(a=w[0]*8e3,w=w[1].split(r=v="");e=w.shift();)+e==e?v+=e:v&&(r+="Y("+(+v)+");",v=""),r+=(e=="@"?"s=s.concat(s.slice(-1))":e=="_"?"Y(R)":e=="$"?"Z":e=="#"?"Y(Z,Z)":~"+-*/%^&|".indexOf(e)?"Y(Z"+e+"Z)":~"<>".indexOf(e)?"Y(Z"+e+e+"Z)":~"~!".indexOf(e)?"Y(+"+e+"Z)":"")+";";r=Function("s,R","s=[R=s];"+(r+"return String.fromCharCode(Z&255)").replace(/Z/g,"s.pop()").replace(/Y/g,"s.push"));w=(v="data")+":audio/wav;base64,UklGRl9fX19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgA";for(e=0;e<a;)v+=r(e++);return w+btoa(v)}

This implementation is an optimizing interpreter (i.e. it compiles StackBeat expression to a JS function and then uses this function in the actual processing loop). It also exists in the form of a bookmarklet, just drag this link to your bookmark bar and you'll be able to play StackBeat expressions just by selecting them in your browser, or entering your code in a popup box when no text is selected:

javascript:new%20Audio((function(w,a,v,e,r){w=w.split(":");for(a=w[0]*8e3,w=w[1].split(r=v="");e=w.shift();)+e==e?v+=e:v&&(r+="Y("+(+v)+");",v=""),r+=(e=="@"?"s=s.concat(s.slice(-1))":e=="_"?"Y(R)":e=="$"?"Z":e=="#"?"Y(Z,Z)":~"+-*/%^&|".indexOf(e)?"Y(Z"+e+"Z)":~"<>".indexOf(e)?"Y(Z"+e+e+"Z)":~"~!".indexOf(e)?"Y(+"+e+"Z)":"")+";";r=Function("s,R","s=[R=s];"+(r+"return%20String.fromCharCode(Z&255)").replace(/Z/g,"s.pop()").replace(/Y/g,"s.push"));w=(v="data")+":audio/wav;base64,UklGRl9fX19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgA";for(e=0;e<a;)v+=r(e++);return%20w+btoa(v)})((getSelection()+"")||prompt("Enter%20the%20valid%20StackBeat%20expression"))).play()

There also exists an implementation in D, which may be found on GitHub via the link at the bottom of the page. This implementation takes StackBeat code as a single command-line parameter and outputs unsigned 8-bit PCM data at 8kHz.

Other notes

The idea of using a stack-based bytebeat-oriented language was first seen in viznut's IBNIZ audiovisual demo-tool. The same approach was used in some iOS app called Glitch Machine. But this idea clearly lacked a simple, short and powerful implementation purely concentrated on audio synthesis. Until now.

External resources