iTranslated by AI
Getting Started with Chiptune Live Coding in BITCODE
The Constraint of Four Channels
The original Game Boy (DMG-01) sound chip has only four channels: two pulse waves, one waveform memory channel, and one noise channel. Volume is limited to 16 levels (4-bit), and there are four types of duty cycles. These were the only "instruments" available for game music in 1989.
Within these constraints, Game Boy sound programmers created astonishing music. They manipulated the pulse wave duty cycle to alter the timbre, used arpeggios to simulate chords, and utilized the noise channel to switch between sounds from hi-hats to snares. Constraints birthed techniques, and techniques became an aesthetic.
BITCODE is an attempt to bring this "aesthetic of constraints" into the world of live coding. It is a live coding language designed exclusively for chiptune, running entirely in the browser.
What is Live Coding?
Before we dive in, let’s briefly touch on "live coding" for readers who might be unfamiliar with the term.
Live coding is a performance technique where music or visuals are generated by rewriting program code in real time. Rather than placing notes in a DAW piano roll, the execution of the code itself is the "performance." It spread from the TidalCycles and SuperCollider communities in the 2000s and took root worldwide alongside an event culture known as Algorave.
At the core of live coding is the concept of a pattern language. Instead of specifying individual notes by "when, at what pitch, and for how long," you describe a pattern and apply transformations to it. Retrograde, rotation, probabilistic thinning—by layering these transformations, complex musical structures emerge from minimal code.
BITCODE confines this pattern language philosophy to the Game Boy's four channels. If Strudel is a language for freely patterning general-purpose synthesis, BITCODE is a language for manipulating patterns under the physical constraint of having only four sound sources available.
Playing the First Note
Let's start by writing one line of code.
p1("c4 e4 g4 c5")
p1 refers to channel 1 (pulse wave). The contents within the string are the pattern description, where c4 e4 g4 c5 represents a "C-E-G-C" arpeggio. The four notes separated by spaces are placed at equal intervals within one cycle (a time unit equivalent to one measure).
After writing the code, press Cmd+Enter (or Ctrl+Enter on Windows). A simple pulse wave sound will start playing from your speakers.
Each of the four channels corresponds to a dedicated function:
p1("c4 e4 g4 c5") // CH1: Pulse wave (with sweep function)
p2("c3 ~ e3 ~") // CH2: Pulse wave
p3("c2 c2 g2 g2") // CH3: Waveform memory
n1("h l ~ h") // CH4: Noise (h=high period, l=low period)
With just this, a four-part song begins to play. ~ represents a rest.
Only four lines. Yet, these four lines condense musical decisions such as "which channel to use for the melody," "whether the bass should be a pulse wave or waveform memory," and "how to distribute the noise for rhythm." Since there are physically only four channels, if you want a fifth part, you have no choice but to give up one of the existing ones or find a way for one channel to serve two purposes.
Mini-notation: How to Write Patterns
BITCODE adopts the mini-notation syntax widely used in TidalCycles/Strudel for music description. The notations to learn are few, but combining them allows for the generation of surprisingly complex rhythms.
Subdivision: [ ]
Writing multiple notes inside square brackets subdivides that duration.
p1("c4 [e4 g4] a4 [g4 e4]")
Of the four "slots," the second and fourth are subdivided into two. The result is a rhythm that sounds like "quarter, eighth-eighth, quarter, eighth-eighth." You can also nest them.
p1("c4 [e4 [g4 a4]] b4 c5")
This splits the second slot into "e4" and the subdivision "g4, a4," creating an even finer rhythm.
Alternate: < >
The elements inside angle brackets alternate in sequence with each cycle.
p1("<c4 e4 g4 c5> <e4 g4 b4 e5>")
In the first cycle, it plays c4 e4, in the second e4 g4, in the third g4 b4... and so on; each slot circulates independently. The same code spins out a different melody every cycle.
Repetition and Probability: *n and ?
n1("h*4 l*2 h l") // Repeat h 4 times, l 2 times
n1("h l? h ~") // l plays with 50% probability
*n repeats the event n times. Adding ? makes it play with a 50% probability (the remaining 50% is silence). Probabilistic triggering is effective for the noise channel to create a rhythm with a human-like "swing."
Creating Chiptune Sounds
Now that you know how to write patterns, let's move on to parameter control. In BITCODE, parameter manipulation is written using method chaining.
Duty Cycle
The duty cycle determines the timbre of the square wave. There are only four types available on the Game Boy.
p1("c4 e4 g4 c5").duty(50) // 50% for all notes
p1("c4 e4 g4 c5").duty("12 25 50 75") // Different duty cycle per note
12.5% is thin and sharp, 50% is rich, and 75% is the inverted version of 12.5%. By patterning and cycling through this "palette" of only four options, you can give a single square wave channel surprising expressive range. Writing .duty("12 25 50 75") changes the duty cycle with every note, making the timbre itself rhythmically shift.
Volume and Envelope
p1("c4 e4 g4 c5").vol(8) // Volume 0-15 (4-bit)
p1("c4 e4 g4 c5").env(15, "down", 3) // Attenuate from 15, speed 3
Volume has 16 levels. It is not a continuous fader, but a discrete staircase. The envelope also conforms to DMG hardware, controlled by just three parameters: initial volume, direction (up/down), and speed. While it lacks the flexibility of an ADSR envelope, this "rawness" is the very texture of chiptune sound.
Wave Memory (CH3)
Channel 3 features wave memory. You can freely define a waveform of 32 samples × 4 bits.
p3("c3 e3 g3 c4").wave("tri") // Preset: Triangle wave
p3("c3 e3 g3 c4").wave("saw") // Preset: Sawtooth wave
// Describe waveform directly with hexadecimal (32 samples)
p3("c3").wave("0123456789abcdeffedcba9876543210")
// Switch waveforms every cycle
p3("c3 e3 g3 c4").wave("<tri saw sin>")
Defining a waveform at 32-sample resolution is coarse, which is exactly why it is interesting. Writing "0123456789abcdeffedcba9876543210" creates a triangle-like waveform, while "ffffffffffffffff0000000000000000" creates a square wave. The act of manually editing a hexadecimal string to experiment with waveforms evokes the hand-crafted feeling of chiptune.
Noise (CH4)
n1("h l h l") // h=short period (7-bit), l=long period (15-bit)
n1("h l h l").env(15, "down", 1) // With envelope
The Game Boy noise channel is based on an LFSR (Linear Feedback Shift Register) and has two modes: short period (7-bit, metallic timbre) and long period (15-bit, white-noise-like). Just by arranging h and l as a pattern, you can write rhythms that feel like swapping between hi-hats and snares.
Pattern Transformation: Creating Complexity from Minimal Code
Up to this point, we have talked about "writing patterns." Now we reach the true core of BITCODE as a live coding language—pattern transformation.
Speed and Retrograde
p1("c4 e4 g4 c5").fast(2) // Double speed
p1("c4 e4 g4 c5").slow(2) // Half speed
p1("c4 e4 g4 c5").rev() // Retrograde
.fast(2) plays the entire pattern at double speed. The original pattern, which occurred once per cycle, now occurs twice. .rev() is retrograde—playing the pattern backward.
Periodic Transformation: .every()
p1("c4 e4 g4 c5").every(4, x => x.fast(2))
"Double the speed only once every 4 cycles." By alternating between the normal cycle and the transformed cycle, "development" is created within the pattern. You can also compose transformations.
p1("c4 e4 g4 c5").every(4, x => x.rev().fast(2))
Once every 4 cycles, play backward and at double speed. Structural development is automatically generated from minimal code.
Probabilistic Transformation
p1("c4 e4 g4 c5").sometimes(x => x.duty(12)) // Applied with 50% probability
p1("c4 e4 g4 c5").often(x => x.fast(2)) // 75% probability
p1("c4 e4 g4 c5").rarely(x => x.rev()) // 25% probability
sometimes applies the transformation with 50% probability. Even if you keep running the same code, different results emerge with each cycle. The coexistence of deterministic patterns and probabilistic fluctuations—this is the heart of live coding's expressive power.
Palindromes and Rotation
p1("c4 e4 g4 c5").palindrome() // Retrograde on even cycles
p1("c4 e4 g4 c5").rotate(2) // Shift the pattern by 2 elements
.palindrome() plays the odd cycles in forward order and the even cycles in reverse. It is a "palindrome" structure where the pattern goes out and returns. .rotate() shifts the start point of the pattern. Even with the same sequence of notes, changing the start point makes it sound like an entirely different melody.
Chiptune-Specific Effects
BITCODE includes effects that redefine traditional chiptune techniques as pattern operations.
Arpeggio: A Technique for Pseudo-Polyphony
p1("c4 e4").arp("0 4 7") // Major triad arpeggio (specified in semitone intervals)
The Game Boy can only play one note per channel. To play chords, it rapidly switches between multiple notes, tricking the human ear into perceiving them as "playing simultaneously." This is the chiptune arpeggio. .arp("0 4 7") cycles through the root, major third, and perfect fifth at an interval of approximately 35ms. This technique, born from physical constraints, creates the distinct shimmering texture of chiptune.
Sweep
p1("c4 ~ c4 ~").sweep(3, "up") // Ascending sweep
p1("c5 ~ c5 ~").sweep(3, "down") // Descending sweep
Sweeps, which continuously vary the frequency, are a native feature of the Game Boy's CH1. They produce pitch-sliding effects common in sound design.
Echo and Glissando
p1("c4 e4 g4 c5").echo(3, 0.25) // 3 repetitions, pseudo-delay at 0.25 interval
p1("c4 ~ c5 ~").gliss(4) // Slide to the next note in 4 steps
.echo() is a pseudo-delay that repeats while incrementally decreasing the volume. .gliss() is a discrete slide to the next note, serving as a "chiptune version" of portamento. Neither effect exists natively in the DMG hardware, but they function as natural expressions within the chiptune context.
Trying It Out: Composing a Track
Let's combine these elements to create a session.
// === "The Sealed Forest" ===
bpm(104)
// CH1: A melody weaving through the trees
// The starting point shifts every cycle with rotate
p1("~ a4 c5 ~ e5 ~ d5 c5 ~ a4 ~ g4")
.duty("<50 25 50 25>")
.env(11, "down", 4)
.rotate(1)
.every(4, x => x.rev())
.every(7, x => x.arp("0 3 7"))
// CH2: Bells flickering like sunlight filtering through trees
p2("~ ~ ~ e5 ~ ~ ~ ~ a5 ~ ~ ~")
.duty(12)
.vol(4)
.echo(2, 0.2)
.palindrome()
.every(5, x => x.rotate(3))
// CH3: Earthy bass — Am-Em-F-Dm
p3("a3 a3 a3 e3 e3 e3 f3 f3 f3 d3 d3 d3")
.wave("<tri tri saw tri>")
.vol(6)
.every(6, x => x.rev())
.every(8, x => x.rotate(3))
// CH4: The rustle of fallen leaves, footsteps on moss
n1("~ l? ~ ~ h ~ ~ l? ~ h? ~ ~")
.env(7, "down", 4)
.every(3, x => x.rotate(1))
.every(5, x => x.fast(2))
Although this code is under 20 lines, multiple mechanisms are running simultaneously.
The CH1 melody shifts its starting point by one element per cycle using .rotate(1), completing a full rotation every 12 cycles. It reverses every 4 cycles, and an arpeggio is applied every 7 cycles. Since 4 and 7 are coprime, these two transformations coincide only once every 28 cycles. This means the same melody fragment is constantly being reconfigured in slightly different contexts.
The sparse bell pattern in CH2 moves back and forth via .palindrome() and applies .rotate(3) every 5 cycles. Because this transformation cycle is prime relative to CH1, the phase relationship between the two melody lines changes slowly over a long period.
The baseline in CH3 uses .wave("<tri tri saw tri>") to turn the waveform itself into a pattern. It is a triangle wave for three out of four cycles and a sawtooth wave for one—incorporating timbre changes as a part of the rhythm.
The noise in CH4 includes l? and h?, triggering them probabilistically. Even if you keep the same code running, the noise pattern changes subtly with every cycle.
Creativity Born from Constraints
Regarding the design of BITCODE, I would like to state a few decisions and their intentions.
Why Only 4 Channels?
In Strudel, you can add as many tracks as you like by writing $. In BITCODE, there are only four. This constraint is intentional.
When channels are unlimited, you can always rely on the lazy solution of "adding another part." With 4 channels, that option is closed. If you use two channels for melody, only one channel remains for the bass (the wavetable). If you want to make the rhythm more complex, you are forced to decide whether to sacrifice a melody channel.
This "allocation decision" drives composition. Even in actual Game Boy music production, programmers poured creativity into how they utilized their limited channels. BITCODE reproduces this thought process.
Persistent Oscillators and Monophonic Thinking
Each channel in BITCODE maintains one persistent oscillator. When a new note arrives, the previous sound is immediately overwritten, even if it is in the middle of a release. While most Web Audio implementations favor generating a new node for every note, BITCODE faithfully replicates the behavior of the DMG hardware.
This design has three effects:
First, new notes ruthlessly interrupt the decay of the envelope. This "way of cutting off" produces the articulation unique to chiptune. It is not a smooth release, but a clipped, staccato sound. This is a constraint, but also an aesthetic.
Second, the principle of "1 channel = 1 sound" is strictly guaranteed. In node-generation-based implementations, a sound may overlap with the previous one during its release, but this does not happen in BITCODE.
Third, because there is no generation or destruction of oscillators, it is resistant to memory leaks or performance degradation even in long sessions. This stability is important for live coding, where code may run for 30 minutes or an hour.
Aesthetics of a Discrete Parameter Space
All Game Boy parameters are discrete. Volume has 16 steps, duty cycles are limited to 4 types, and the wavetable is 32 samples by 4 bits. It is not a space of continuous values, but a set of finite, countable discrete values.
When you treat this "coarseness" as the target for pattern operations, a unique texture is born. .duty("12 25 50 75") cycles through the four values of the duty cycle. .vol("15 12 8 4") descends the staircase of discrete volumes. Where a continuous parameter space would result in a simple gradient, discrete values create audible "steps" where each increment is clearly distinguished. These steps form the musical texture of chiptune.
1-Bit UI
BITCODE's interface is completely monochrome—composed only of two colors: #ffffff and #000000.
This is not merely a decorative choice. It is a visual constraint that responds to the constraints of the sound source (4-bit volume, 4 channels, 4 duty cycles), consistently applying the design philosophy of "expressing within constraints" even at the interface layer.
The syntax highlighting for the code editor uses no color at all. It distinguishes syntactic elements using only three axes: bold, italics, and opacity. Channel names are bold, methods are italic, comments are dim, and the rest symbol ~ is even dimmer. Even without color, the structure of the code is clearly legible.
From Trackers to Pattern Languages
Software categorized as trackers—LSDj, FamiTracker, and Deflemask—have been established as the tools for creating chiptune on real hardware and PCs. They place notes one by one on a grid that flows vertically. It looks like a spreadsheet, with time flowing from top to bottom. There is also a culture of "writing" music with the MML syntax, and chiptune has historically had a high affinity for text.
The tracker approach is essentially the direct placement of sequences. You manually construct the "result" of the final music one step at a time.
BITCODE converts this to the paradigm of pattern transformation.
// Tracker approach: Direct description of the result
// Step 0: C4
// Step 1: E4
// Step 2: G4
// Step 3: C5
// Step 4: C5 <- Reverse
// Step 5: G4
// Step 6: E4
// Step 7: C4
// BITCODE approach: Description of patterns and transformation rules
p1("c4 e4 g4 c5").palindrome()
A single transformation like .palindrome() replaces eight steps of manual input. Moreover, this transformation is meaningful as a musical operation: "reversal." What is just a string of numbers in a tracker is read as a "palindromic pattern transformation" in BITCODE.
This cognitive shift changes the level of the programmer's thinking. It is no longer about "what note to place next," but "what transformation to apply to this pattern." It is not about placing events, but about designing transformation rules. If a tracker is a metaphor for a score, a pattern language is a metaphor for a function.
Technical Architecture
I will also briefly touch upon the technical stack of BITCODE.
The sound engine is based on the Web Audio API, assigning one persistent OscillatorNode to each channel. For the pulse waves of CH1/CH2, the duty cycle is controlled via the Fourier coefficients of a PeriodicWave, and the CH3 wavetable is also achieved using PeriodicWave. The noise of CH4 is generated by an LFSR algorithm, and the buffer is played back via an AudioBufferSourceNode.
The pattern engine is a cycle-based scheduler that calculates cycle length based on BPM and schedules the timing of each event using setValueAtTime. The mini-notation parser processes data through three stages: tokenizer, AST, and event list.
The UI is React, and the code editor is CodeMirror 5. All external dependencies are loaded via CDN from cdnjs.cloudflare.com, making it a self-contained browser application that requires no installation.
Getting Started
- Access BITCODE chiptune live coder in your browser.
- Enter the following in the editor:
bpm(120)
p1("c4 e4 g4 c5").duty("<12 25 50 75>").env(12, "down", 3)
p2("c3 ~ e3 ~").duty(50).vol(10)
p3("c2 c2 g2 g2").wave("tri").vol(6)
n1("h l h l").env(10, "down", 2)
- Press
⌘(Ctrl) + Enter. - You will hear the sound. Rewrite each parameter and press
⌘(Ctrl) + Enteragain. The sound will change in real-time.
Add transformations:
p1("c4 e4 g4 c5")
.duty("<12 25 50 75>")
.env(12, "down", 3)
.every(4, x => x.rev()) // Reverse every 4 cycles
.sometimes(x => x.fast(2)) // 50% probability of double speed
Change the shape of the patterns, add transformations, and rearrange channel allocation. Your own music will begin to move within these four channels.
Conclusion: Beyond the Constraints
The history of music programming languages has, in a sense, been a history of expanding freedom. From the fixed UGen of MUSIC-N to the free patching of Max/MSP, and from desktop applications to the browser, constraints have been peeled away one after another.
BITCODE intentionally runs counter to that trend. Only 4 channels. 16 volume levels. 4 duty cycles. Only white and black colors.
However, a constraint is not a limitation. A constraint is a framework.
Because there is a framework of 4 channels, the question "how do I use these four?" arises. Because there is a discrete parameter space, meaning is born in the pattern operations that cycle through those finite options. Because there are only white and black, the distinction between bold, italics, and transparency stands out.
Composers using nanoloop or LSDj on the original Game Boy opened up expressions that no one anticipated within the 1989 hardware constraints. BITCODE revisits that spirit with the power of a pattern language. Open your browser and explore the possibilities of those four channels.
Discussion