I've been exploring the m8 Dirtywave tracker for a few months now, and it's been a fascinating experience. If you're not familiar with it, the m8 is a portable music sequencer built on the Teensy platform that combines a minimal 8-button interface with a powerful tabular workflow. What makes it special isn't just its portability or its sound capabilities, but how the hardware and interface work together to create an experience that's simultaneously constrained and liberating.
When I'm making music on the m8, I don't feel like I'm fighting with the UI or solving puzzles. It's more like intuitive cooking - mixing ingredients, tasting, adapting, and discovering new flavors along the way. I can lay on my couch, pick it up, and within minutes be immersed in the creative process. No re-learning complicated interfaces, no decision paralysis from too many options - just pure creative flow.
This experience got me thinking about programming environments. Most of our programming tools are designed for desktop workstations with keyboards, mice, and large displays. But what about a programming environment designed specifically for a minimal, portable hardware interface? What would programming look like if it was built around a tabular interface navigated with just a handful of buttons?
The Core Idea
I've been conceptualizing a programming environment built around a tabular interface inspired by the m8. The key insight is that while traditional programming involves typing long streams of text, we could instead organize code into a structured table with each function defined as rows with fixed columns:
- NAME: The function identifier
- IN: Input parameter specification
- EXP1-EXP5: Five expression cells (the core of execution)
- OUT: Output specification
At first glance, limiting each function to just five expressions seems incredibly restrictive. But this constraint actually encourages breaking down complex operations into atomic, composable functions - which tends to produce more maintainable code.
The hardware component is just as important as the software model. The target hardware would be deliberately minimal:
- Teensy 4.1 microcontroller (same as the m8)
- 320x240 display
- D-pad, A/B, START/SELECT buttons (8 buttons total)
- microSD slot and headphone jack
You would navigate the table with the D-pad, edit cells with the A button (which brings up a contextual menu), and run your program with START. No keyboard needed - all programming happens on the device itself through selection rather than typing.
This isn't just about building yet another programming tool - it's about exploring how hardware constraints can lead to new programming paradigms. When you start from the assumption that your interface has just 8 buttons and a small screen, you're forced to rethink fundamental aspects of how programming works.
The current concept is built on a stack-based model similar to Forth, but that's just an implementation detail. The core innovation is the tabular programming model and how it maps to minimal hardware controls.
Some interesting properties emerge from this design:
- Error Reduction: By selecting from valid options, many classes of syntax errors simply cannot happen
- Focused Decomposition: The five-expression limit forces thoughtful function decomposition
- Explicit Data Flow: Data flow between expressions is visually explicit in the table
- Portability: The entire environment is designed for programming on the go, in casual positions
Real-World Examples: Demoscene Effects
To demonstrate that this concept isn't just theoretical, let's explore how a classic demoscene effect - a plasma - would be implemented using this tabular programming model:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ PLASMA │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ 0 │ →TIME │ 0 │ →BEAT │ │ void│
│ LOOP │ void│ TIME+ │ CLR │ CALC │ DISP │ LOOP │ void│
│ CLR │ void│ 0 │ VRAM! │ │ │ │ void│
│ CALC │ void│ 0 │ 0 │ Y-LOOP│ │ │ void│
│ Y-LOOP │ Y │ 0 │ X-LOOP│ Y 1+ │ Y<H? │ Y-LOOP│ void│
│ X-LOOP │ X Y │ PXCALC│ PLOT │ X 1+ │ X<W? │ X-LOOP│ void│
│ PXCALC │ X Y │ X 16/ │ SIN │ Y 16/ │ COS │ + │ VAL │
│ PLOT │ X Y V│ TIME │ V + │ BEAT │ * │ !PIXEL│ void│
│ TIME+ │ void│ TIME │ 1 + │ →TIME │ BEAT+ │ │ void│
│ BEAT+ │ void│ AUDIO │ THRSH>?│ BEAT1+│ │ │ void│
│ BEAT1+ │ void│ BEAT │ 16 + │ 127 MIN│ →BEAT│ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
Let's break down how this plasma effect works in detail:
The entry point PLASMA
first calls INIT
to set up initial state variables, then calls LOOP
to start the main execution loop. The INIT
function initializes two global variables: TIME
set to 0 to track animation time, and BEAT
set to 0 to respond to audio input. The arrow symbol (→
) represents assignment to a variable, making the data flow explicit.
The main LOOP
function orchestrates the entire effect by:
- Calling
TIME+
to increment the time counter - Calling
CLR
to clear the screen by writing 0 to video memory - Calling
CALC
to calculate the plasma values - Calling
DISP
(implied but not shown) to display the frame - Recursively calling itself to continue the animation loop
The actual plasma calculation happens through a nested loop structure. CALC
sets up the initial Y coordinate (0) and calls Y-LOOP
. The Y-LOOP
function then processes each row of pixels by:
- Setting the initial X coordinate to 0
- Calling
X-LOOP
to process that row - Incrementing Y by 1
- Checking if Y is less than the screen height (
Y<H?
) - If so, recursively calling itself to process the next row
Similarly, X-LOOP
processes each pixel in a row by:
- Calling
PXCALC
to calculate the plasma value for that pixel - Calling
PLOT
to set the pixel - Incrementing X by 1
- Checking if X is less than the screen width (
X<W?
) - If so, recursively calling itself to process the next pixel
The core math of the plasma effect happens in PXCALC
. It:
- Takes X and Y coordinates as input
- Divides X by 16 to scale it down
- Calculates the sine of that scaled X value
- Divides Y by 16 to scale it down
- Calculates the cosine of that scaled Y value
- Adds the sine and cosine values together
- Returns this sum as the plasma value (
VAL
)
This creates a simple plasma pattern based on the interference of sine and cosine waves. The division by 16 controls the frequency of the waves.
PLOT
takes X, Y coordinates and a value V as input, then:
- Adds the current
TIME
to the value to animate the plasma - Multiplies by the current
BEAT
value to make it respond to audio - Calls
!PIXEL
to set the pixel at (X,Y) with the resulting color value
The TIME+
function increments the TIME
variable by 1 and then calls BEAT+
to update the beat value. The audio responsiveness is implemented by BEAT+
and BEAT1+
:
BEAT+
reads the audio input level (AUDIO
) and checks if it's above a threshold (THRSH>?
)- If it is, it calls
BEAT1+
to increase the beat value BEAT1+
adds 16 to the current beat value, limits it to a maximum of 127, and stores it back in theBEAT
variable
This creates a visual "pulse" effect when audio input is detected, as the plasma pattern will get brighter or more saturated.
What's particularly fascinating about this implementation is how it manages data flow using the stack. Between each expression cell, data implicitly moves via the stack. For example, in the PXCALC
function:
│ PXCALC │ X Y │ X 16/ │ SIN │ Y 16/ │ COS │ + │ VAL │
The stack operations would be:
- Start with X and Y on the stack
X 16/
: Pop X, divide by 16, push result (let's call it X')SIN
: Pop X', calculate sine, push result (sin(X'))Y 16/
: Pop Y, divide by 16, push result (Y')COS
: Pop Y', calculate cosine, push result (cos(Y'))+
: Pop cos(Y'), pop sin(X'), add them, push result (sin(X') + cos(Y'))
The final value on the stack is the plasma value that gets returned as VAL
.
This illustrates how the stack implicitly connects operations within a row, while the explicit function calls (like X-LOOP
calling PXCALC
and then PLOT
) handle data flow between rows.
To execute this tabular code, a virtual machine would need to:
- Maintain a data stack and a return stack
- Process each expression cell from left to right within a row
- Handle function calls by pushing the current position to the return stack and jumping to the new function
- Support basic stack operations, arithmetic, conditionals, and variable storage
- Provide hardware abstraction for screen access, audio input, etc.
The beauty of this approach is that the VM can be quite simple while still supporting powerful programs. The tabular structure adds a layer of organization that makes the code more readable and navigable than traditional Forth, while still leveraging the power of stack-based programming.
Here's another example of a classic demoscene effect - a tunnel:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ MAIN │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ 0 │ →TIME │ GEN-LUT│ │ │ void│
│ LOOP │ void│ TIME+ │ CLR │ CALC │ DISP │ LOOP │ void│
│ CLR │ void│ 0 │ VRAM! │ │ │ │ void│
│ GEN-LUT │ void│ 0 │ ANGLE │ 0 │ DIST │ │ void│
│ ANGLE │ Y │ 0 │ ANG-X │ Y 1+ │ Y<H? │ ANGLE │ void│
│ ANG-X │ X Y │ X CX -│ Y CY -│ ATAN2 │ →ANG │ X+ │ void│
│ DIST │ Y │ 0 │ DST-X │ Y 1+ │ Y<H? │ DIST │ void│
│ DST-X │ X Y │ X CX -│ Y CY -│ HYPOT │ →DST │ X+ │ void│
│ CALC │ void│ 0 │ 0 │ Y-LOOP│ │ │ void│
│ Y-LOOP │ Y │ 0 │ X-LOOP│ Y 1+ │ Y<H? │ Y-LOOP│ void│
│ X-LOOP │ X Y │ X Y │ TEX │ PLOT │ X 1+ │ X<W? │ void│
│ TEX │ X Y │ ANG@ │ DST@ │ TIME +│ PTRN │ │ C │
│ PLOT │ X Y C│ X │ Y │ C │ !PIXEL│ │ void│
│ TIME+ │ void│ TIME │ 1 + │ →TIME │ │ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
The tunnel effect works by precalculating angle and distance lookup tables (GEN-LUT
, ANGLE
, ANG-X
, DIST
, DST-X
) and then using those tables to map each screen pixel to a position in a texture pattern. The angle (ATAN2
) and distance (HYPOT
) calculations would normally be expensive operations, but by precalculating them into lookup tables, the effect can run at full speed even on limited hardware.
What's impressive about these examples is how they demonstrate complex mathematics and visualization techniques in a highly constrained programming model. Despite the five-expression limit per function, these programs can create sophisticated visual effects with smooth animation and even audio responsiveness.
While demoscene effects are a great way to demonstrate the capabilities of this system, I'm more excited about its potential for creative applications like pixel art editors, music tools, or interactive storytelling environments.
Imagine a pixel art painting program implemented in this tabular format. The main structure might look something like this:
┌─────────┬─────┬───────┬───────┬───────┬───────┬───────┬─────┐
│ NAME │ IN │ EXP1 │ EXP2 │ EXP3 │ EXP4 │ EXP5 │ OUT │
├─────────┼─────┼───────┼───────┼───────┼───────┼───────┼─────┤
│ MAIN │ void│ INIT │ LOOP │ │ │ │ void│
│ INIT │ void│ LOAD-│ CANVAS│ UI-INIT│ │ │ void│
│ LOOP │ void│ INPUT │ PROCESS│ RENDER│ SAVE? │ LOOP │ void│
│ INPUT │ void│ JOY │ BUTTONS│ →STATE│ │ │ void│
│ PROCESS │ void│ MODE? │ DRAW │ FILL │ SELECT│ MENU │ void│
│ RENDER │ void│ CANVAS│ CURSOR│ UI │ FLIP │ │ void│
└─────────┴─────┴───────┴───────┴───────┴───────┴───────┴─────┘
The real power would come from how naturally the hardware maps to the application. The D-pad could move your cursor or navigate UI elements, the A button would draw or select, and B would undo or cancel. The entire interface would be designed around the constraints of the hardware, making it feel natural and intuitive rather than limited.
One of the challenges with this approach is scaling beyond simple demos. To address this, I've been thinking about a hierarchical organization model inspired by the m8's structure. The m8's navigation system is quite elegant - it divides the interface into "Views" that can be navigated by holding SHIFT and using the direction keys, with a helpful mini-map in the corner showing your location.
The context-sensitive menu system would be a crucial part of making this environment work with so few controls. When you press the A button to edit a cell, you would see a menu of valid options for that context. This eliminates syntax errors and makes discovery of available functions organic.
For example, when editing an expression cell, the menu might show available functions, variables, constants, and operators. The options would be filtered based on the current context, so you only see what's relevant. This approach makes programming feel more like navigation than typing.
What excites me most about this concept isn't just the software model, but how the hardware and software could work together when designed as an integrated system. The m8 succeeds because it isn't just tracker software running on portable hardware - it's a complete experience where every aspect of the hardware and software are designed to work together.
Similarly, this tabular programming environment isn't just about cramming programming capabilities into a Game Boy-like device. It's about rethinking programming from the ground up for a specific hardware context. The result would be something that doesn't feel constrained when you're using it - it feels natural and intuitive for its purpose, even if it looks strange and completely foreign at first.
This tabular programming concept opens up many possibilities for exploration. By rethinking how programming can work within tight hardware constraints, we might discover new approaches that actually enhance rather than limit creative expression. I'm sharing this idea because I believe there's something genuinely interesting in the intersection of minimal hardware interfaces and structured programming models.
I currently have a working but pretty bare bone web prototype, to validate the general concept — the Forth-like interpreter works, and can render examples mentioned in this article.