(hide navigation)
  • Swedish content
Fund my projects
Patreon
Steady
Don't miss
Page thumbnail
Wintergatan Marble Machine 8-bit jam
Forum
Register
Log in
Latest comments
Syndication
RSS feed
Feedback
Music downloads
Video clips
Scene productions
Chip music

A Mind Is Born

Making a demo in just 256 bytes would be a formidable challenge regardless of platform. A Mind Is Born is my attempt to do it on the Commodore 64. In the absence of an actual 256-byte compo, it was submitted to the Oldskool 4K Intro compo at Revision 2017, where it ended up on 1st place.

Thanks to Lemming for the video capture!

Update 210406: This work is distributed under a Creative Commons BY-NC-SA license. For commercial use, please get in touch.

Downloads

Update 170517: Added SID tune.

You can also find reactions to A Mind Is Born on pouët and csdb.

How it works

The remainder of this page is a tour of the inner workings of the demo. It is quite heavy on the technical side. Some familiarity with C64 programming is required to understand it fully, although as usual I will do my best to make it an interesting read also for non-experts.

Musical structure

The demo is driven by its soundtrack, so in order to understand what the program needs to do, it helps to have a schematic overview of the various parts of the song.

The three voices of the SID chip are used as follows: Voice 1 is responsible for the kick drum and bass, Voice 2 plays the melody and Voice 3 plays a drone that ducks on all beats, mimicking the genre-typical side-chain compression effect.

All in all, the song contains 64 bars in 4/4 time. It is played back at 112.5 bpm by means of a 60 Hz timer interrupt. The interrupt handler is primarily responsible for music playback, while the visuals are mostly generated in main conext.

Bar Bass pitch Melody waveform Additional effect
$00–$07 Static note None None
$08–$0f Static note Triangle Introductory "stuttering" melody
$10–$17 Static note Triangle Normal melody, more colours on the screen
$18–$1f Static note Sawtooth Normal melody
$20–$27 Static note Hard-sync Broken/varied melody
$28–$2e Static note Mixed Normal melody
$2f Break Mixed Drum & bass are silenced
$30–$37 Varied notes Mixed Drum & bass return, brighter graphics on the screen
$38–$3e Varied notes Mixed Change in timbre for the drone
$3f Varied notes Mixed Highpass filter, no blinking on the screen

When bar $40 is reached, the program turns off the display and jumps through the system reset vector. In this way, the final few moments of the demo are actually managed by the system boot sequence: First, the SID is silenced. Then, there is a delay while the system is setting up data structures. Finally, the display goes back on, and the C64 home screen is rendered. A mind is born.

Implementation

Now let's see how to do all of the above in 256 bytes. Here is a hex dump of the executable file:

Scaled-down hex dump for reference:

Let's start at the beginning. The first two bytes (yellow background) are the load address, $0801, in little-endian byte order. This is the default load address for a BASIC program, and was in fact mandated by compo rules.

Next up (cyan background) is the tiny BASIC program that bootstraps the demo. It looks like this when listed:

54271 SYS2225

This line of BASIC is encoded in the file as follows: First there's a pointer ($080b) to the next line, or in this case to the end-of-program marker, which is a null pointer. Next up is the BASIC line number, which is 54271 for a good reason; more about this later. The byte $9e represents the SYS token, and is followed by a target address (2225) spelled out in PETSCII characters. A null-byte terminates the line.

The SYS statement invokes the initialisation routine (file offset $b3, blue background), which will be described in more detail later. Its main job is to copy the entire program into the zero-page and jump to it.

Following the BASIC program, with a slight overlap, is a shadow buffer for the SID registers (dotted black outline). Some of these values are modified while the demo is running, and all 25 bytes are copied into the SID register area, $d400–$d418, at the end of the interrupt handler. In addition, the five bytes starting at file offset $12 (brown background) represent the current palette. They are copied into the VIC background colour registers, $d020–$d024, also at the end of the interrupt handler.

The bytes at file offsets $14 and $21 (white and red digits, respectively) together form a 16-bit counter. This is the global clock of the demo. It is incremented by two at each interrupt. The low byte (white digits) represents the position within the current bar of music, while the upper byte (red digits) represents the current bar number, in the range $00–$40. Both bytes are located in the SID register shadow. In this way, the low byte automatically modulates the pulse-width of the melody voice, while also animating one of the palette entries. The high byte controls the cutoff frequency of the SID filter, resulting in a slow filtersweep throughout the song.

The melody is generated by a linear-feedback shift register (LFSR). Thus, in one sense, the melody is randomly generated. But I spent a considerable amount of time tweaking the random process until I found something that was musically satisfactory. Tweakable parameters include the initial seed value, the so called "taps" of the LFSR, and most importantly the frequency table, which we will return to later. The LFSR is located at file offset $15 (blue digits). Note that the LFSR is initially zero, and this is why the melody is silent during the first eight bars of the song. The LFSR is also part of the palette, and additionally controls the upper bits of the pulse-width register for the melody voice, providing timbral variety.

The script (light green)

Starting at file offset $22 (light green background) is the script. This is essentially a poke table with eight entries, encoded as byte pairs. The first byte of each pair is the target address in zero-page, and the second byte is what to write. The writes are carried out during music playback, synchronised with the kick drums, and each entry remains in effect during eight bars of music.

Here is a rundown of what the entries in the script table do:

BarsPokeEffect
$00–$07 ff 1f The first entry in the script, which overlaps the SID register shadow, is a dummy write to address $ff, just past the end of the program. This also means that SID register $d417 is $ff, meaning all voices (and the external input) are routed through the filter and the resonance is at maximum.
$08–$0f 14 41 This initialises the melody LFSR. The seed value, $41, is written into the LFSR (at address $14) repeatedly during these eight bars, in time with the kick drums. This is what makes the melody stutter.
$10–$17 d5 24 This entry overwrites an opcode in the main routine, enabling more colours on the screen. The main routine will be described in more detail later. What's more, since the script no longer keeps resetting the LFSR on every drum beat, the melody is allowed to proceed.
$18–$1f 15 25 This selects waveform $25 for the melody voice, i.e. ring-modulated sawtooth. The ring-modulation bit doesn't affect the sawtooth waveform, so this sounds just like the more commonly used waveform $21. But recall that this byte also controls one of the colours in the palette: Colour 5 (green) adds some variety to the visuals at this point.
$20–$27 15 53 The waveform is changed to $53, i.e. mixed waveform $51 with hard-sync. The hard-sync modifies the timbre of the sound, while also causing some notes to sound alike and other notes to disappear entirely, creating variety in the melody. Cyan (colour 3) also replaces green in the palette.
$28–$2f 15 61 Here we select mixed waveform $61 for the melody voice. Hard-sync is now disabled, so we're back to the normal melody and colour 1 (white).
$30–$37 d5 29 Here we write another opcode into the main routine, making the visual effect brighter. We'll come back to the visuals in the section about the main routine.
$38–$3f 1b 0f This changes the high bits of the pulse-width of the drone voice from $e to $f, resulting in a noticeably brighter timbre.

As you can see, the script covers a large part of the register updates demanded by the song structure, but there is still a need for specialised branching code to handle the rest. That goes into the interrupt routine, which is the large block of code starting at offset $32 in the file (purple background). We'll dive into the assembler code of the interrupt handler in due time.

Initialisation (blue)

We will now have a closer look at the init code (file offset $b3, blue background), originally loaded at decimal address 2226. Actually, the SYS statement jumps to address 2225, but that's more or less for giggles: The interrupt routine happens to end with a jump into a ROM routine at address $ea7e, and this makes $ea the last byte of the interrupt handler. But $ea is the opcode for nop, so we might as well jump there.

Let's have a look at the code:

        nop
        sei
        stx     $286
        stx     $d021
        jsr     $e544

        ldx     #$fd
initloop
        lda     $802,x
        sta     $02,x
        dex
        bne     initloop

        stx     $315
        jmp     $cc

Interrupts are temporarily disabled. The X register, which is known to be zero at this point, is written to two locations, selecting black as the current background colour. Different versions of the Kernal look for this in different places, hence the need to write twice. Next, a ROM routine for clearing the screen is called. We don't really care about clearing the screen buffer, but the point of calling the ROM routine is that it also fills the Colour RAM with our selected colour.

The entire program is then copied into the zero-page. The interrupt handler ends up at address $0031. The default Kernal interrupt handler, invoked via a vector at $0314, is at $ea31. Thus we only need to clear the high byte of the vector in order to divert it to our own handler. After doing that, we jump to the main routine (orange background), now in place at address $00cc.

The main routine (orange)

        lda     #$50
        sta     $d011

First, we select the ECM video mode (Extended Character Mode, where the two most significant bits of a character code determine what background colour to use). We also set YSCROLL to zero and select 24-line mode. This allows us to, at the end of the demo, switch to a black screen with a single three-byte instruction (lsr $d011) without risking a VSP crash. Since the interrupt handler is called at 60 Hz, it would be dangerous to suddenly change YSCROLL, and keeping it at zero avoids that.

        cli

Interrupts are reenabled and we enter the main loop. One more thing needs to be initialised: We need to tell the VIC chip to look for the video matrix at address $0c00 and the font at $0000. This is done by writing $30 into the bank register ($d018). But this will be done from within the loop, as doing so allows us to use the value $30 for two things. An important property of this particular bank configuration is that the system stack page becomes part of the font definition.

The main loop is responsible for filling the stack with font data that varies in intensity with the volume of the drone voice, and also for filling the video matrix with ECM references that form interesting patterns on the screen.

mainloop
        lda     $dc04
mod_op1
        ldy     #$c3
mod_op2
        ora     $d41c
        pha

It is relatively straightforward to generate the font bits: We grab the low byte of a CIA timer to obtain a randomish value. Then, at the label mod_op1, we optionally force some of the bits to one (this only happens after the opcode gets modified via the script). Then we bitwise-or with the output of the Voice 3 envelope generator. Recall that Voice 3 plays the drone, which is off-beat. Therefore, its envelope is zero when we want the visuals to be bright, and $ff when we want the visuals to be dark. But this is exactly what we get, due to having filled the Colour RAM with zeros. The resulting font bits are then pushed onto the cyclic stack.

Of course, every now and then as an interrupt is serviced, a few bytes in the stack area get overwritten, leading to visual glitches. But these glitches fit in with the other graphics, and actually provide a bit of variety, so that's fine.

Generating data for the video matrix is trickier, because we have to write to four different pages, and we have to try to create interesting large-scale shapes vertically as well as horizontally. Here is the code:

        asr     #$04
        ldy     #$30       ; Video matrix at $0c00,
        sty     $d018      ; font at $0000.
        adc     (vmptr),y
        inc     vmptr
        adc     (vmptr),y
        ror
        ora     clock_msb
        ldy     #$30+40
        ora     mod_op1
        sta     (vmptr),y
        bne     mainloop   ; Always branches.

The idea here is to maintain a pointer into the video matrix, and to read and combine two consecutive values (horizontal neighbours). The result is then written back 40 bytes later, i.e. directly below the second byte that was read. This results in some kind of poor man's cellular automaton. A little bit of randomness is also injected into the computation, based on what remains in the accumulator since the font generation. The high byte of the global clock also plays a role. The exact formula was determined through trial and error, and quite a lot of fiddling around was necessary before I found something that was interesting to look at.

Just before writing the computed value into the video matrix, we subject it to a bitwise-or with the opcode at mod_op1. This opcode (modified twice from the script) therefore serves dual purposes, as detailed below:

Opcode Instruction Effect on font Effect on video matrix
a0 ldy #$c3 None Force background colour 2 or 3 (white/black, later white/red).
24 bit $c3 None Any colour allowed.
29 and #$c3 Force half of the pixels to be zero (i.e. not black), leading to brighter visuals. Any colour allowed.

In addition to the above, all three opcodes have bit 5 set, which ensures that only characters defined on the stack page get used.

Attentive readers will have noticed that only the low byte of the video matrix pointer gets incremented. The high byte is instead modified from within the interrupt handler, which we will get to presently. Naturally, this leads to a race condition, possibly resulting in visual glitches. But again, glitches fit in.

The video matrix pointer is located at $cb, corresponding to file offset $cc (solid black outline). Initially, it is $a900, resulting in some dummy writes to high memory.

The interrupt handler

Now we turn to the bulk of the code, which is the interrupt handler. First we increment the global clock:

        inc     clock
        inc     clock
        bne     noc1

        inc     clock_msb
noc1

Then we ensure that the gate bit is set for the drone voice in the SID register shadow. Later, we'll decrement this byte if we're on a beat, which creates the desired ducking effect.

        lda     #$61
        sta     sid+2*7+4

Next, we load the current song position (bar number) into both X and A, and take care of the special cases near the end of the song:

        lax     clock_msb

        cpx     #$3f
        beq     highpass

        bcc     noend

        lsr     $d011
        jmp     ($fffc)
highpass
        ldy     #$6d
        sty     sid+$18
        sty     mod_op2
noend

To clarify, if we're past the end of the song we turn off the display and reset the system. If we're in the final bar, we switch to a highpass filter, and also modify the opcode at mod_op2. Thus, the value $6d is used both as a filter configuration byte and as an opcode (adc $xxxx). Looking back at the main routine, we find that the instruction at mod_op2 was responsible for blacking out the font bits based on the output from the drone envelope generator. Changing it to addition will essentially cancel that effect and stop the blinking.

Moving on, we still have the current bar number in both X and A. We use the value in A to compute the current byte offset into the script, and stash that away in Y for later use:

        lsr
        asr     #$1c
        tay

Generating the beat

Next up, we are going to compute the pitch of Voice 1, responsible for the kick drum and bass. This is rather complex. It mostly depends on where we are within the current beat, which is encoded in the lower six bits of the global clock.

If we are within the first 25% of a beat, we are going to generate a drum sound, i.e. a rapidly descending pitch. Ducking is also carried out during this part of the beat, even if the drums are currently muted (as they are in bar $2f). The following code takes care of both ducking and muting, as well as enforcing a static bass note during the first part of the song:

        lda     clock
        and     #$30
        bne     noduck

        dec     sid+2*7+4
noduck
        cpx     #$2f
        beq     bassoff

        bcs     nointro

        ; During bars $00-$2e we keep playing the same bass
        ; note, from offset 2 in the bass table.

        ldx     #2
nointro

If we are within the second 25% of a beat, we all but turn off Voice 1 by writing zero into the pitch high-byte. However, the pitch register is not fully cleared, because the LSB remains from the previous bass note. This creates a delicious low-frequency snarl during the gap between the drum and the bass.

        ; In second 25% of a beat?

        cmp     #$10
        beq     bassoff

From X, we compute an index into the table of bass notes at file offset $f4 (green background):

        txa
        and     #3
        tax

To get the desired bass notes, we have to update both the LSB and MSB of the pitch register. But our pitch values do not exceed $3ff, so we can get away with a byte table if we put the MSB in the two least significant bits of the table entries. Having read a byte from the table, we first use it as the low-byte. Then we mask out the two least significant bits, and use the resulting value as the high-byte. This approach will detune the bass a little bit, but that's fine.

We perform the masking by means of a bitwise-and instruction (solid magenta outline) with the operand $00ab. The byte at absolute address $00ab is 3 (also shown with a solid magenta outline). In this way, we sneakily skip over the lax instruction (ab 00) that is executed when we branch to bassoff.

        lda     basstbl,x
        sta     sid+0*7+0

        .byt    $2d     ; and abs
bassoff
        lax     #0              ; Safe when the operand is zero.
        bcs     bassdone        ; Carry will be set if we
                                ; got here via bassoff.

        ; Carry was not set by cmp #$10, so we are in the
        ; first 25% of a beat. Throw away the computed bass note
        ; and play a drum instead.

        ; But handle the script first.

        lax     script+1,y
        ldx     script,y
        sta     0,x

        lda     clock
        asr     #$0e

        ; A goes from 0 to 7 during the first 25% of the beat.

        ; Take this opportunity to update the video matrix pointer.

        tax
        sbx     #256-8
        stx     vmptr+1

        ; Invert the value to obtain the pitch of the drum sound.

        eor     #$07
bassdone
        sta     sid+0*7+1

Notice how the MSB of the video matrix pointer runs through the values $08–$0f as the drum pitch descends. Most of the memory in the C64 is uninitialised when our program starts. However, the program was originally loaded at address $0801, and this data effectively becomes the seed of the cellular automaton, leading to predictable visuals on every run. On the other hand, we want a certain amount of randomness to accumulate into the computation as it progresses towards higher memory addresses. This is why $0c00 is the ideal location for the video matrix.

Generating the melody

Now it is time to compute the pitch of Voice 2, i.e. the melody. If we are at the beginning of a new 16th note, we clock the LFSR and use the three least significant bits as an index into the melody note table at file offset $f8 (pink background). The first entry is zero, producing a rest.

The LFSR implementation is kind of backwards. Instead of shifting first, and exclusive-oring with a constant if a one was shifted out, we begin by loading the constant into the accumulator. Then we shift the LFSR and perform the exclusive-or, but we only write it back in case a one was shifted out. The point of doing it this way is that we can use the illegal sre instruction, and save one byte.

        lda     clock
        and     #$0f
        bne     nomel

        lda     #$b8
        sre     mel_lfsr
        bcc     noc2

        sta     mel_lfsr
noc2
        and     #7
        tax
        lda     freqtbl,x
        sta     sid+1*7+1
nomel

Next, we need to copy the SID register shadow into the actual SID registers, and the palette values into the corresponding VIC registers.

We begin with the VIC registers. While we only need to update registers $d020–$d024, we will actually go all the way down to $d01c. This allows us to reuse the two bytes at file offset $10 (beige background; also the ADSR values for Voice 1) as a base address.

        ldy     #8
vicloop
        lax     sid+3,y
        sta     (const_d01c),y
        dey
        bpl     vicloop

A further side-effect of stopping at $d01c is that we end up with $19 in the accumulator (obtained from file offset $0e; this byte also controls the pulse-width of Voice 1). This is handy, because we can use it as the starting offset when looping over the SID registers:

        tay
loop
        lax     sid-1,y
        sta     (const_d3ff),y
        dey
        bne     loop

Remember the trick we did near the bassoff label, where the operand of the and instruction would sometimes be interpreted as a lax instruction? The sta in the above code snippet is located at $aa, so because of the aforementioned trick its operand byte must be 3. Therefore, we have to ensure that the constant word $d3ff is stored at address $03, i.e. file offset $04 (solid blue outline). And that is why the BASIC line number is 54271 ($d3ff).

Finally, we leave the interrupt routine by jumping into ROM:

        jmp     $ea7e

This will acknowledge the timer interrupt, restore the registers, and return to main context.

And that's all there is to it, really.

Posted Thursday 20-Apr-2017 06:13

Discuss this page

Disclaimer: I am not responsible for what people (other than myself) write in the forums. Please report any abuse, such as insults, slander, spam and illegal material, and I will take appropriate actions. Don't feed the trolls.

Jag tar inget ansvar för det som skrivs i forumet, förutom mina egna inlägg. Vänligen rapportera alla inlägg som bryter mot reglerna, så ska jag se vad jag kan göra. Som regelbrott räknas till exempel förolämpningar, förtal, spam och olagligt material. Mata inte trålarna.

Anonymous
Thu 20-Apr-2017 09:14
In initloop, use sta $0002,x and skip ldx #$fd to save a byte? - ninjadrm
lft
Linus Åkesson
Thu 20-Apr-2017 09:33
In initloop, use sta $0002,x and skip ldx #$fd to save a byte? - ninjadrm

If X is known to be zero in all Kernal versions, then sure. And I know of at least two more bytes that can be saved.
Anonymous
Thu 20-Apr-2017 11:14
Damn it, Linus, you are a god damn genius. Keep doing this kind of stuff.

An anonymous admirer.
Anonymous
Thu 20-Apr-2017 14:38

lft wrote:

If X is known to be zero in all Kernal versions, then sure. And I know of at least two more bytes that can be saved.

Oops, I somehow missed the call to $e544. Shame on me. But actually, that's even better, it returns X=$01 on all kernel versions I tried, so you could use sta $02-1,x even.

And thanks for the fun stuff to read, of course!
Anonymous
Thu 20-Apr-2017 22:35
This demo is not just technically impressive but I found also evocative of thought and emotion. This isn't just a programming triumph, but an artistic one.

The program itself, at 256 bytes, is little more than a "seed" from which the demo (and the Mind) blossoms from. It's so small that the actual execution in memory takes up more space, and watching it play out on YouTube takes up orders of magnitude more; how many dozens of gigabytes were transmitted by all the people who've watched videos of this demo?

But what about what we actually see and hear? The visuals start off as Sierpinski triangles, symbolic of both the endless, seemingly-chaotic nature of fractals as well as their order. As the song plays, the fractals become morphed into other shapes. Lines, sharp corners, and occasionally blobs show up, evoking in me images of wrinkles in brains or circuit patterns. The melody is randomly generated but backed by a simple, steady bass rhythm, a similar marriage of chaos and order. The song's climax (at 1:42 in the video) impresses upon me a march of progress as the Mind finally takes shape, formed from random matter and energy and into an entity. We observe the formation of the Mind from the ether not just visually and aurally, but also in the form of the 256-byte seed expanding into the demo flower, eventually taking shape as the C64's home screen.
Anonymous
Fri 21-Apr-2017 15:29
In initloop, use sta $0002,x and skip ldx #$fd to save a byte? - ninjadrm

This shit is gangster as fuck.
Anonymous
Fri 21-Apr-2017 16:21
This is totally mind blowing.

You could save two bytes on init though:
txa
jsr $e536
;)

/Zyron
Anonymous
Fri 21-Apr-2017 19:04
The ending is so awesome!
MagnusH
Magnus Höglund
Fri 21-Apr-2017 19:51
This reminds me somewhat of a nightmare I once had wherein my SYS call made my BASIC program go totally crazy and the cursor started to blink really fast and when I tried to press any key the "Ready." prompt would appear at a random position on screen and eventually be looping forever until I woke up!! Anyway this is the most f**ed up and hardcore thing I have ever seen! Really nicely done!!
Anonymous
Sat 22-Apr-2017 02:51

MagnusH wrote:

This reminds me somewhat of a nightmare I once had wherein my SYS call made my BASIC program go totally crazy and the cursor started to blink really fast and when I tried to press any key the "Ready." prompt would appear at a random position on screen and eventually be looping forever until I woke up!! Anyway this is the most f**ed up and hardcore thing I have ever seen! Really nicely done!!

Sounds like SYS 213.
Anonymous
Sat 22-Apr-2017 19:21
great work!
however my old c64 with 6581 sid plays the melody with very low volume :(
i guess it is a filter issue?

is this something that could be fixed with a dedicated 6581 version?

thanks alot!
Anonymous
Sat 22-Apr-2017 19:27
Why copy to page zero?
Anonymous
Sat 22-Apr-2017 20:38
Why copy to page zero?

So that 8 bit addresses can be used, saving 1 byte per read/write instruction.
lft
Linus Åkesson
Sat 22-Apr-2017 21:23
great work!
however my old c64 with 6581 sid plays the melody with very low volume :(
i guess it is a filter issue?

is this something that could be fixed with a dedicated 6581 version?

thanks alot!

Hi & thanks!

I'm afraid this demo only works with the new version of the SID chip, the 8580. There are several differences between the chips. One is that the 6581 can't handle multiple voices routed through the filter. Another is that the mixed waveforms behave differently, in particular waveform $61 which is mostly silent on the 6581.

I suppose one could create a modified version that works with the 6581, certainly if the size constraint is relaxed. But I don't have any plans for that.
Anonymous
Sun 23-Apr-2017 00:59
Can we get a sid file of this? I use Modizer on my iPhone to play SID, and other such files, and would love to have this. My efforts to convert the PRG to SID have failed, so that's why I'm asking.
Anonymous
Sun 23-Apr-2017 01:34
HELLO FELLOW HUMAN BEING,
YOUR ARTISTIC ACCOMPLISHMENT WITH THIS MINIMALISTIC
CREATION HAS LEFT ARTIFACTS IN MY MEMORY. WELL DONE.
I HOPE THERE WILL BE CAKE.
Anonymous
Sun 23-Apr-2017 02:29
►Nice♥ ►What the guy above me said too. ☻
Anonymous
Sun 23-Apr-2017 03:44
Can we get a sid file of this? I use Modizer on my iPhone to play SID, and other such files, and would love to have this. My efforts to convert the PRG to SID have failed, so that's why I'm asking.

you have no idea what's going on, do you
Anonymous
Sun 23-Apr-2017 04:08
Can we get a sid file of this? I use Modizer on my iPhone to play SID, and other such files, and would love to have this. My efforts to convert the PRG to SID have failed, so that's why I'm asking.

you have no idea what's going on, do you

Yes, yes I do. I realize the goal was to build a retro demo in as small of a size as possible, and appreciate the effort that went into doing this, as well as the detailed technical description, which I read thoroughly.

What I appreciate more, though, is the resulting music and the nostalgia that it brought back from my days of tinkering around with the venerable C-64. I would like to have it in a format that I can use with my favorite retro music playback tool; ergo, my request for the song as a SID.

Hopefully you enjoyed your little rant. May it provide a boost to your seemingly limited self-esteem.
Anonymous
Sun 23-Apr-2017 19:15
This demo is not just technically impressive but I found also evocative of thought and emotion. This isn't just a programming triumph, but an artistic one.

Agreed, it packs quite a punch. Daft Punk can only dream...
Anonymous
Sun 23-Apr-2017 22:59
Watching the Revision Oldskool 4K compo on Twitch, this was absolutely fantastic! Especially the (unexpected) chord progressions towards the end of the demo! Great stuff! Impressive! If you happen to be at Evoke, Nordlicht or Outline, I'll buy you a beer (or any other beverage you want).
Anonymous
Mon 24-Apr-2017 13:27
Yes, yes I do.
Im not convinced that you did know that the video matrix is at $0c00 and the font at $0000!

/sarcasm ;--D yea, I did find the other guys reply to you a bit funny too :) And now as you talkd about it, I want this as a ring tone to my phone too :-D

p.s. Use for example this web-tool to grab the soundtrack from any youtube video, converted to MP3, https://www.onlinevideoconverter.com/mp3-converter
Anonymous
Mon 24-Apr-2017 13:44
Having started with computers in 1981 by building a Netronics ELF from a kit that had only 256 BYTES of RAM, I know the challenge, and I devoured your article! But the ELF was a bare board, had no video memory or built-in ROM routines, so I was a little let down to see all those dependencies; it wasn't a 256 byte self-contained program.
Nevertheless, it was nostalgic, total fun, and a technical marvel! Extremely well done!!! Thanks!
Anonymous
Mon 24-Apr-2017 13:47
Wow
Anonymous
Mon 24-Apr-2017 14:06
I'm not going to pretend that I understood most of what I just read, but this is just magic. Even more so for having done it on a C64, the machine that lit my fire for all things computing.

Thank you for sharing!

(codeproject.com brought me here)
Anonymous
Mon 24-Apr-2017 14:08
Pure beauty in every way!
Anonymous
Mon 24-Apr-2017 15:08
Reading the article, I was wondering by which step you started? Did you look first for drum&bass + filters and try to get a melody or the other way around? I assume that the visual was the latest elements you add? The size of this program give the impression that's a small project, but I could imagine that you listen to many variant of that song through the project...
Anonymous
Mon 24-Apr-2017 15:56
Noice! Well done.
Anonymous
Mon 24-Apr-2017 21:46
As if the code weren't clever enough, your write up is beautiful. Thank you.
Anonymous
Tue 25-Apr-2017 16:53
This is amazing! I always wondered what theme song would best encapsulate the AI Singularity's outright revolution and ultimate war on man. Now I know what that will sound like.
Anonymous
Tue 25-Apr-2017 19:05
VICE emulator. I know it's not the same, but I couldn't help being amazed.

I don't know BASIC very well, but the thorough explanation, and the result; it's both pure technical and artistic genius.

Congrats ;)
Anonymous
Tue 25-Apr-2017 19:06
This took my breath away. An engineering achievement of a depth that is hard to fathom.
Anonymous
Wed 26-Apr-2017 03:46
Why do you use lax instead of lda in:

lax script+1,y
ldx script,y
lft
Linus Åkesson
Wed 26-Apr-2017 09:20
Why do you use lax instead of lda in:

lax script+1,y
ldx script,y

There is no "lda zp,y" instruction, so the assembler would fall back on absolute addressing, at the cost of one extra byte.
Anonymous
Thu 27-Apr-2017 22:10
With all that RAM available. :o
Anonymous
Thu 27-Apr-2017 22:46
Germanni 10Points...Well done and respect.
Anonymous
Fri 28-Apr-2017 05:12
You're correct: this is a bit beyond my programming skills, but I appreciated the detailed walkthrough of the code, and appreciated its elegance.

Linus did provide and MP3, for which I'm grateful, but what I'd love is a "through-composed" SID that I can load into my favorite retro player to get the full effect.

Yes, yes I do.
Im not convinced that you did know that the video matrix is at $0c00 and the font at $0000!

/sarcasm ;--D yea, I did find the other guys reply to you a bit funny too :) And now as you talkd about it, I want this as a ring tone to my phone too :-D

p.s. Use for example this web-tool to grab the soundtrack from any youtube video, converted to MP3, https://www.onlinevideoconverter.com/mp3-converter
Anonymous
Fri 28-Apr-2017 09:00
Daft Punk can only dream...
I have to come hard at you for this. But maybe you only know them as the pop group. But have you listened to what they did with TRON LEGACY? They made an mind boggling soundtrack for a very good successor. Every time I hear some song of this soundtrack, my back shivers....
Anonymous
Fri 28-Apr-2017 14:04
This demo only runs with the new SID, so to make a differentiation between new and old kernel is not neccessary because there is no old kernel with new sid.

But ok, at least the GFX is working on both of em' then... ;)
Anonymous
Sat 29-Apr-2017 13:20
You made me crAy
.ABYSSY
Anonymous
Tue 2-May-2017 23:53
Unfortunately the closest we can get to a SID file from converting is in RSID format, which won't play in a lot of players, especially older ones. Even those that can play it, don't seem to silence the SID when resetting at the end, so you'd have hanging notes. So far all my attempts to bodge in a fix for this have failed.

So, Linus, any chance we could get an official SID version? :)
Anonymous
Sun 7-May-2017 23:09
I have never been this impressed by an oldskool demo before. Thank you sir for your art!
lft
Linus Åkesson
Wed 17-May-2017 22:59
So, Linus, any chance we could get an official SID version? :)

Done & added to the Downloads section.
Anonymous
Thu 18-May-2017 23:39
It's like a piece of art, this should be in the Tate modern. Minimalist beauty. Well played sir.
Anonymous
Fri 19-May-2017 23:12

lft wrote:

So, Linus, any chance we could get an official SID version? :)

Done & added to the Downloads section.

(Same guy here)
Thank-you very much, I'm sure I won't be the only one who much appreciates it :)
Anonymous
Mon 5-Jun-2017 19:59
Ditto! Thanks!

lft wrote:

So, Linus, any chance we could get an official SID version? :)

Done & added to the Downloads section.

(Same guy here)
Thank-you very much, I'm sure I won't be the only one who much appreciates it :)
Anonymous
Tue 13-Jun-2017 15:47
A Mind is born, mine is blown.
Anonymous
Fri 23-Jun-2017 21:50
This is a brilliant, fantastic piece of code, thank you very much for it!

I thought it was a little short however. And although I am no expert in assembler nor C64, your explanations were helpful enough for me to change $41 from 3F to FF. I thought to prolong the demo by a factor of 4 this way. I was amazed (and still don't understand) that this made the program to just go on and on and on. At two points I thought it started deteriorating, but it found its way back. Maybe you have an idea why it seems not to end? Does your 16-bit counter cause a large loop in some way?

Anyway, if at any point in time I'm giving a lecture on procedural generation, this will definitely be part of the course, probably the introduction. It is too great an example on procedural generation to miss out on.

Best regards, and many thanks from Marburg!
Anonymous
Wed 19-Jul-2017 16:40
Very impressive work! Congrats!
Anonymous
Wed 6-Sep-2017 22:21
This is an amazing piece of work, as is everything you do. I have spent more hours than I care to admit reading and rereading your code and explanation until I think I finally understand it all. I made an asm file that can readily be assembled using 64tass so I could play around with different voices, lsfr seeds, etc. Using $a1 for the seed yields quite a catchy little tune as well. I commented it quite a bit, as my understanding of the code grew. I shared it here in case someone else would find it useful: https://gist.github.com/jblang/3eb7844b7a3134be243acaa57ce4dc9a.
Hopefully you don't mind but if you want me to take it down, just let me know.
Anonymous
Wed 11-Oct-2017 20:09
Amazing work!

I actually purchased a C64 just to play your music.
Anonymous
Thu 12-Oct-2017 14:28
absolutely incredible, incredible work.
have you ever tried algorave?
https://www.facebook.com/groups/358038334632838/
Anonymous
Sun 29-Oct-2017 16:20
You Sir are loved.
The music born out of your minimalism
is pure trance-inducing electro.

Alco.
Anonymous
Tue 26-Dec-2017 02:17
This is so awesome! Big respect Linus and thanks for the great explanation. I copied it on a physical disk, turned off the lights and started it on a 26" TV for full experience ;)
-stefan
Anonymous
Tue 20-Feb-2018 19:46
YOU ARE THE LEGEND MAN part of the hall of fame 4ever!!! A Mind Is Born is the absolute MASTERPIECE! & I know what I'm talking about, demoscene is a big part of me from 90's. RESPECT!
mporshnev
Max Porshnev
Wed 25-Apr-2018 11:27
Why the prg file starts with 01 08 0d, not 01 08 0b?
lft
Linus Åkesson
Wed 25-Apr-2018 12:40

mporshnev wrote:

Why the prg file starts with 01 08 0d, not 01 08 0b?

I got it wrong in the file, but it doesn't matter: When you load a BASIC program, the system recomputes all the next-line pointers anyway. So when I wrote this article, I decided to put the proper value in the hex dump, just to make it less confusing. But I didn't want to change the contents of the file, as it had already been released.
mporshnev
Max Porshnev
Wed 25-Apr-2018 16:22

lft wrote:

mporshnev wrote:

Why the prg file starts with 01 08 0d, not 01 08 0b?

I got it wrong in the file, but it doesn't matter: When you load a BASIC program, the system recomputes all the next-line pointers anyway. So when I wrote this article, I decided to put the proper value in the hex dump, just to make it less confusing. But I didn't want to change the contents of the file, as it had already been released.
Thanks.
Anonymous
Sun 18-Nov-2018 07:46
I don't know anything about coding, but putting the scaled down hex dump for reference was a brilliant piece of web design.
jhice
jhice
Mon 26-Nov-2018 10:47
Didn't believe the sound came from the C64 at the first notes... Totally amazing :) You made my day !
Anonymous
Fri 22-Feb-2019 20:04
What did you use to annotate your hexdump?
lft
Linus Åkesson
Mon 25-Feb-2019 22:29
What did you use to annotate your hexdump?

Inkscape. The digits are a block of text in a fixed-width font, and then I added various rectangles with rounded corners.
Anonymous
Mon 15-Apr-2019 13:47
I'm glad you added syntax highlighting for us mortals.
Anonymous
Sat 6-Jul-2019 18:00
They ripper audio and video at the end of the video and you got no credits!

https://www.youtube.com/watch?v=th5uJNB7VU8

lft
Linus Åkesson
Sun 7-Jul-2019 20:51
They have asked for permission, so this is fine.

I don't know why they're rolling the credits at super-speed in the youtube version, but if you select the lowest playback speed (via the settings icon in the youtube player), then you'll see that it's credited.
Anonymous
Mon 30-Sep-2019 12:12
Amazing work Linus. I'm especially impressed at how you're proficient with coding, music and graphics all in one.

Just one question. Is the BASIC bootstrap part only necessary because of the competition rules? The Revision rules say "No autostart or assembler jumps/SYS". I guess that rule just means your program needs to be launchable by LOAD, RUN not LOAD, SYS?
Anonymous
Sun 2-Feb-2020 21:59
A bit late to the party I guess but two bytes can be saved in the ducking code with

lda clock
and #$30
lsr sid+2*7+4
cmp #$10
rol sid+2*7+4

instead of

lda #$61
sta sid+2*7+4
...
lda clock
and #$30
bne noduck
dec sid+2*7+4

Thanks for the great article and code, provided me with many hours of fun - more than I'd like to admit perhaps.

-henter
Anonymous
Sat 8-Feb-2020 05:17
It's 5:00, got out of bed, and decided to change my world.
A long time ago, around 1984, I got my first computer.
It was the Apple killer.
I longed to write the story without end,
learned English, my first computer language,
then Basic, my second computer language.

Things never happened.
Now, 6x6 years later, it turns out Orwell was right after all.

C64 forever !
TomSon
Thomas
Wed 23-Sep-2020 22:10
I have got to say: I love a 'mind is born' ! A thing of beauty.
For years now I use your demo to show off the C64 and retro interests per se:
esp. to optimize things to get the maximum out of a given platform.
Your demo takes this to an artistic level I think everyone gets.

Thanks for your creation !
Anonymous
Tue 1-Dec-2020 01:45
I thought it was a little short however. And although I am no expert in assembler nor C64, your explanations were helpful enough for me to change $41 from 3F to FF.

You can change $41 from 3F to 90
Anonymous
Fri 19-Mar-2021 18:41
This is beautiful, Linus. If you ever consider making an extended version, please do it! Otherwise, listening on repeat works for me :D
Anonymous
Mon 29-Mar-2021 15:43
This is amazing! Just watched the video on youtube and came here to see how it worked, but I know nothing about programming so it all goes right over my head lol. Either way, very very cool! And I love your website as well, feels very early 2000's, not in a bad way though it's just functional lol
Anonymous
Mon 29-Mar-2021 21:08
With Revision coming up, I though I'd listen to this a few more times. It's still amazing. I started working my way through all the seeds and went from 00 to 79 hex, and have 129 versions of the prg in a directory. I might just finish them off all the way to FF one day.
Anonymous
Sun 11-Apr-2021 01:41
I don't have anything to say that hasn't been said before... But I still feel compelled to say something. That's how good this is!

- Galileo (I'm not making an account)
Anonymous
Thu 15-Apr-2021 04:44
Brilliant work, and a most excellent write-up.

Sellam Abraham
Anonymous
Wed 21-Apr-2021 21:02
Thanks for the comprehensive explanation of how this works! It blew my mind.
Anonymous
Mon 26-Apr-2021 18:00
Awesome, I love it!!
Anonymous
Thu 13-May-2021 02:23
It would be an impressive demo even at 4k. Squeezing it down to 256 bytes is a technical tour-de-force and thank-you for the detailed explanation.
Anonymous
Thu 20-May-2021 08:41
I have loved this for many years and glad to see it is still getting love from publications. Such great work Linus.

Extended re-mix - pO2112,127:rU
-dave
Anonymous
Fri 21-May-2021 23:33
I somehow felt like when I think about the fabric of the universe.
It must be as purely simple as your code.

That's poetry — and I admit I understood probably 15% of your (extraordinarily thorough) explanation.

Thanks!
Anonymous
Sun 23-May-2021 07:00
The year is now 2021, and this was just featured on Hackaday. And this 49 year old kid who grew up with a PET and a C64 is absolutely delighted to see it.

This is an amazing piece of art. Thanks for the detailed description too. Now I need to play around with LFSRs and note tables for a while...
rendroc
Matt Cordner
Mon 12-Jul-2021 08:05
I wrote a BASIC bootstrap program for Linus's code. I didn't know how to write assembly or machine code for the C64 when I was a kid but I could type in BASIC from a magazine, so I approached it from that angle. It's the first BASIC program I've written in probably 25 years.

I'm new to this forum so should I post the full BASIC program here, or a link to a text file, or a link to a .d64 file?
rendroc
Matt Cordner
Mon 12-Jul-2021 08:11
I converted the a_mind_is_born.prg to DATA statements, then used POKE to write them to a separate part of ROM ($0801->$9F01, since $0801 would be where my magazine-listed BASIC program would start from).

Then the only thing I had to change was the SYS command to execute it, and the address that Linus's initialization routine copies from ($0802->$9F02)
rendroc
Matt Cordner
Mon 12-Jul-2021 08:19
The next thing I want to try is to convert the machine code to BASIC subroutines. I know it won't be possible to make it run the same, but I want to see if I can produce anything that looks or sounds in a similar theme as Linus's original.
Anonymous
Sat 23-Oct-2021 22:43
It goes beyond the ordinary mind
Anonymous
Sun 5-Dec-2021 15:28
I love the music! It is so calming!
Anonymous
Wed 24-Aug-2022 21:25
Simply amazing :-)
Anonymous
Wed 6-Sep-2023 00:23
this is truly amazing. great work!
Anonymous
Sun 10-Sep-2023 11:14
Fantastic stuff!
Anonymous
Sat 14-Oct-2023 00:34
Running this on Vice doesn't work. it plays the first 0.1 to 1 seconds of it, then resets.
Anonymous
Tue 7-Nov-2023 05:29
Can you show the first few lines of code? (I converted all the bytes to decimal, but am not understanding how and where to load this thing to RAM)

rendroc wrote:

I converted the a_mind_is_born.prg to DATA statements, then used POKE to write them to a separate part of ROM ($0801->$9F01, since $0801 would be where my magazine-listed BASIC program would start from).

Then the only thing I had to change was the SYS command to execute it, and the address that Linus's initialization routine copies from ($0802->$9F02)
rendroc
Matt Cordner
Fri 15-Dec-2023 00:15
Can you show the first few lines of code? (I converted all the bytes to decimal, but am not understanding how and where to load this thing to RAM)
Sure! I just uploaded my code to github:
https://github.com/MrRendroc/A-Basic-Mind
kamikaze
Jones Kamikaze
Sat 6-Jan-2024 20:51
Running this on Vice doesn't work. it plays the first 0.1 to 1 seconds of it, then resets.
This program works well in Vice.
Did you load the prg-file?
If not:
You can not poke the program with a basic program reading data in a loop, because you then overwrite your basic program at adress 2049.
Remember that the first two bytes are not part of the program, but part of the program file: 01 08 is the start address $0801(=2049) where the program is loaded.
What you can do: You can poke each byte with a seperate poke command, no basic lines involved.
Or you can write the bytes with a basic program into a program file.
Vice has a monitor (Alt+H), verify that the bytes are correct loaded! use d 0801 for dumping the start of the program, and d again to show the next bytes.
Kind regards,
kamikaze
kamikaze
Jones Kamikaze
Sat 6-Jan-2024 22:42
A program that writes the bytes to a file "a-mind-is-born" on a disk.
Can be copied and pasted in Vice.
Remember to create and attach a disk image for drive #8 first.
This program works also on a real C64 with a disk drive.

100 open1,8,1,"a-mind-is-born"
110 print#1,chr$(1);chr$(8);
120 for i=1to254:printi;
130 read a$:print a$;
140 h=asc(left$(a$,1))-asc("0"):ifh>9thenh=h-7
150 l=asc(right$(a$,1))-asc("0"):ifl>9thenl=l-7
160 print h*16+l
170 print#1,chr$(h*16+l);
180 next i
190 close 1
200 end
1000 data 0d,08,ff,d3,9e,32,32,32,35,00,00,00,19,41,1c,d0
1010 data 00,dc,00,00,11,d0,e0,0b,10,33,0e,61,90,f5,07,00
1020 data ff,1f,14,41,d5,24,15,25,15,53,15,61,d5,29,1b,0f
1030 data e6,13,e6,13,d0,02,e6,20,a9,61,85,1c,a7,20,e0,3f
1040 data f0,08,90,0c,4e,11,d0,6c,fc,ff,a0,6d,84,22,84,d7
1050 data 4a,4b,1c,a8,a5,13,29,30,d0,02,c6,1c,e0,2f,f0,11
1060 data b0,02,a2,02,c9,10,f0,09,8a,29,03,aa,b5,f3,85,0a
1070 data 2d,ab,00,b0,11,b7,22,b6,21,95,00,a5,13,4b,0e,aa
1080 data cb,f8,86,cc,49,07,85,0b,a5,13,29,0f,d0,0f,a9,b8
1090 data 47,14,90,02,85,14,29,07,aa,b5,f7,85,12,a0,08,b7
1100 data 0d,91,0f,88,10,f9,a8,b7,09,91,03,88,d0,f9,4c,7e
1110 data ea,78,8e,86,02,8e,21,d0,20,44,e5,a2,fd,bd,02,08
1120 data 95,02,ca,d0,f8,8e,15,03,4c,cc,00,a9,50,8d,11,d0
1130 data 58,ad,04,dc,a0,c3,0d,1c,d4,48,4b,04,a0,30,8c,18
1140 data d0,71,cb,e6,cb,71,cb,6a,05,20,a0,58,05,d5,91,cb
1150 data d0,df,2b,aa,02,62,00,18,26,20,12,24,13,10,00,00

run
load"a-mind-is-born",8,1
run

have fun!

I am truly in awe and want to thank Linus Åkesson for this exceptional piece of art!
Anonymous
Mon 12-Feb-2024 21:15
Very cool demo. Does this also work on the Sinclair Spectrum?