mika's open learning notebook

field notes, essays, and things learned by building


Software Engineering

Source Code as Data: Building a Code-to-Music Pipeline

What I learned from converting source code into sound and why larger files require analysis, aggregation, and deterministic structure instead of one event per token.

The first version can be almost embarrassingly literal

A tiny experiment can map source text directly to sound:

character / token / line
→ number
→ pitch
→ duration
→ play

That is useful because it proves the core transformation works.

But it does not scale very far.

A large source file quickly exposes the weakness of a one-input-event-to-one-musical-event design.

Source code has structure

Code is more useful when treated as structured data instead of raw text.

Different layers contain different information:

characters
→ tokens
→ syntax
→ statements
→ functions/classes
→ modules
→ project structure

A music system can decide which layer controls which musical dimension.

Example:

syntax category → instrument family
nesting depth → register
function size → phrase length
identifier hash → motif
control flow → rhythm
module → section

The important shift is from “every symbol makes a sound” to “the code describes a composition.”

Analyze first, synthesize second

A clean architecture separates:

source
→ analysis
→ musical representation
→ arrangement
→ synthesis/playback

The analysis phase should not know how audio is rendered.

The synth should not need to understand Python, JavaScript, or another source language.

That separation makes both sides easier to change.

Intermediate representations are powerful

Instead of sending source tokens directly into audio code, create a neutral musical representation.

Example:

Track
  instrument: synth
  events:
    note C4 at beat 0
    note E4 at beat 1

Track
  instrument: kick
  events:
    hit at beat 0
    hit at beat 2

Then the code analyzer only needs to produce musical events.

Playback becomes a separate problem.

This is the same reason compilers use intermediate representations: one representation can sit between very different input and output systems.

Large inputs need aggregation

If a 2,000-line file produces a separate audible action for every small token, the result becomes slow, noisy, or computationally expensive.

A better system aggregates.

Examples:

many tokens
→ one measure

one function
→ one phrase

one file
→ one section

one module
→ one track or movement

This preserves information without demanding that every unit be heard individually.

Deterministic randomness is better than pure randomness

Random BPM, notes, or variation can make the output feel alive.

But unrestricted randomness makes the same code sound unrelated every time.

A useful compromise is seeded randomness:

seed = hash(source or project)

Then the generated song can contain variation while remaining reproducible.

Same source:

→ same seed
→ same broad musical identity

That makes debugging and comparison much easier.

Musical structure needs a second layer

Even good note generation does not automatically create a listenable song.

Song structure is a higher-level concern.

intro
→ section A
→ section B
→ variation
→ breakdown
→ return
→ ending

The code can influence the contents of sections while an arrangement layer keeps the result coherent.

This taught me that “generate events” and “compose a song” are separate problems.

Complexity should be bounded

A generator should place limits on work.

Useful limits include:

  • maximum simultaneous voices
  • maximum generated events per section
  • capped phrase length
  • normalized file size
  • sampling/aggregation for huge inputs
  • bounded audio buffers

A program should degrade gracefully when input becomes large instead of exploding in work.

Main lesson

Turning code into sound is not mainly an audio problem.

It is a data-modeling problem:

What information in the code matters?
How should it be represented?
How much detail should survive?
How does that representation become musical structure?

Once those layers are separate, the project becomes much easier to reason about.

Next time

Design the pipeline before adding more sounds:

source parser
→ code features
→ musical IR
→ arranger
→ synth engine
→ playback

Then test each boundary independently.