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.
- a_mind_is_born (C64 executable, 256 bytes)
- A_Mind_Is_Born (SID tune, 325 bytes)
- Linus Akesson - A Mind Is Born (MP3, 2.1 MB)
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:
Bars | Poke | Effect |
---|---|---|
$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.
Thu 20-Apr-2017 09:14
Linus Åkesson
Thu 20-Apr-2017 09:33
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.
Thu 20-Apr-2017 11:14
An anonymous admirer.
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!
Thu 20-Apr-2017 22:35
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.
Fri 21-Apr-2017 15:29
This shit is gangster as fuck.
Fri 21-Apr-2017 16:21
You could save two bytes on init though:
txa
jsr $e536
;)
/Zyron
Fri 21-Apr-2017 19:04
Magnus Höglund
Fri 21-Apr-2017 19:51
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.
Sat 22-Apr-2017 19:21
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!
Sat 22-Apr-2017 19:27
Sat 22-Apr-2017 20:38
So that 8 bit addresses can be used, saving 1 byte per read/write instruction.
Linus Åkesson
Sat 22-Apr-2017 21:23
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.
Sun 23-Apr-2017 00:59
Sun 23-Apr-2017 01:34
YOUR ARTISTIC ACCOMPLISHMENT WITH THIS MINIMALISTIC
CREATION HAS LEFT ARTIFACTS IN MY MEMORY. WELL DONE.
I HOPE THERE WILL BE CAKE.
Sun 23-Apr-2017 02:29
Sun 23-Apr-2017 03:44
you have no idea what's going on, do you
Sun 23-Apr-2017 04:08
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.
Sun 23-Apr-2017 19:15
Agreed, it packs quite a punch. Daft Punk can only dream...
Sun 23-Apr-2017 22:59
Mon 24-Apr-2017 13:27
/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
Mon 24-Apr-2017 13:44
Nevertheless, it was nostalgic, total fun, and a technical marvel! Extremely well done!!! Thanks!
Mon 24-Apr-2017 13:47
Mon 24-Apr-2017 14:06
Thank you for sharing!
(codeproject.com brought me here)
Mon 24-Apr-2017 14:08
Mon 24-Apr-2017 15:08
Mon 24-Apr-2017 15:56
Mon 24-Apr-2017 21:46
Tue 25-Apr-2017 16:53
Tue 25-Apr-2017 19:05
I don't know BASIC very well, but the thorough explanation, and the result; it's both pure technical and artistic genius.
Congrats ;)
Tue 25-Apr-2017 19:06
Wed 26-Apr-2017 03:46
lax script+1,y
ldx script,y
Linus Åkesson
Wed 26-Apr-2017 09:20
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.
Thu 27-Apr-2017 22:10
Thu 27-Apr-2017 22:46
Fri 28-Apr-2017 05:12
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.
/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
Fri 28-Apr-2017 09:00
Fri 28-Apr-2017 14:04
But ok, at least the GFX is working on both of em' then... ;)
Sat 29-Apr-2017 13:20
.ABYSSY
Tue 2-May-2017 23:53
So, Linus, any chance we could get an official SID version? :)
Sun 7-May-2017 23:09
Linus Åkesson
Wed 17-May-2017 22:59
Done & added to the Downloads section.
Thu 18-May-2017 23:39
Fri 19-May-2017 23:12
lft wrote:
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 :)
Mon 5-Jun-2017 19:59
lft wrote:
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 :)
Tue 13-Jun-2017 15:47
Fri 23-Jun-2017 21:50
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!
Wed 19-Jul-2017 16:40
Wed 6-Sep-2017 22:21
Hopefully you don't mind but if you want me to take it down, just let me know.
Wed 11-Oct-2017 20:09
I actually purchased a C64 just to play your music.
Thu 12-Oct-2017 14:28
have you ever tried algorave?
https://www.facebook.com/groups/358038334632838/
Sun 29-Oct-2017 16:20
The music born out of your minimalism
is pure trance-inducing electro.
Alco.
Tue 26-Dec-2017 02:17
-stefan
Tue 20-Feb-2018 19:46
Max Porshnev
Wed 25-Apr-2018 11:27
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.
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.
Sun 18-Nov-2018 07:46
jhice
Mon 26-Nov-2018 10:47
Fri 22-Feb-2019 20:04
Linus Åkesson
Mon 25-Feb-2019 22:29
Inkscape. The digits are a block of text in a fixed-width font, and then I added various rectangles with rounded corners.
Mon 15-Apr-2019 13:47
Sat 6-Jul-2019 18:00
https://www.youtube.com/watch?v=th5uJNB7VU8
Linus Åkesson
Sun 7-Jul-2019 20:51
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.
Mon 30-Sep-2019 12:12
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?
Sun 2-Feb-2020 21:59
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
Sat 8-Feb-2020 05:17
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 !
Thomas
Wed 23-Sep-2020 22:10
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 !
Tue 1-Dec-2020 01:45
You can change $41 from 3F to 90
Fri 19-Mar-2021 18:41
Mon 29-Mar-2021 15:43
Mon 29-Mar-2021 21:08
Sun 11-Apr-2021 01:41
- Galileo (I'm not making an account)
Thu 15-Apr-2021 04:44
Sellam Abraham
Wed 21-Apr-2021 21:02
Mon 26-Apr-2021 18:00
Thu 13-May-2021 02:23
Thu 20-May-2021 08:41
Extended re-mix - pO2112,127:rU
-dave
Fri 21-May-2021 23:33
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!
Sun 23-May-2021 07:00
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...
Matt Cordner
Mon 12-Jul-2021 08:05
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?
Matt Cordner
Mon 12-Jul-2021 08:11
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)
Matt Cordner
Mon 12-Jul-2021 08:19
Sat 23-Oct-2021 22:43
Sun 5-Dec-2021 15:28
Wed 24-Aug-2022 21:25
Wed 6-Sep-2023 00:23
Sun 10-Sep-2023 11:14
Sat 14-Oct-2023 00:34
Tue 7-Nov-2023 05:29
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)
Matt Cordner
Fri 15-Dec-2023 00:15
https://github.com/MrRendroc/A-Basic-Mind
Jones Kamikaze
Sat 6-Jan-2024 20:51
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
Jones Kamikaze
Sat 6-Jan-2024 22:42
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!
Mon 12-Feb-2024 21:15
Thu 5-Sep-2024 10:30
10 data 13,8,255,211,158,50,50,50,53,0,0,0,25,65,28,208
11 data 0,220,0,0,17,208,224,11,16,51,14,97,144,245,7,0
12 data 255,31,20,65,213,36,21,37,21,83,21,97,213,41,27,15
13 data 230,19,230,19,208,2,230,32,169,97,133,28,167,32,224,63
14 data 240,8,144,12,78,17,208,108,252,255,160,109,132,34,132,215
15 data 74,75,28,168,165,19,41,48,208,2,198,28,224,47,240,17
16 data 176,2,162,2,201,16,240,9,138,41,3,170,181,243,133,10
17 data 45,171,0,176,17,183,34,182,33,149,0,165,19,75,14,170
18 data 203,248,134,204,73,7,133,11,165,19,41,15,208,15,169,184
19 data 71,20,144,2,133,20,41,7,170,181,247,133,18,160,8,183
20 data 13,145,15,136,16,249,168,183,9,145,3,136,208,249,76,126
21 data 234,120,142,134,2,142,33,208,32,68,229,162,253,189,2,8
22 data 149,2,202,208,248,142,21,3,76,204,0,169,80,141,17,208
23 data 88,173,4,220,160,195,13,28,212,72,75,4,160,48,140,24
24 data 208,113,203,230,203,113,203,106,5,32,160,88,5,213,145,203
25 data 208,223,43,170,2,98,0,24,38,32,18,36,19,16,0,0
30 FOR A=2049 TO 2304:READ B:POKE A,B:NEXT:RUN
Linus Åkesson
Fri 6-Sep-2024 16:06
I like how the program overwrites itself in memory, which works because the loop appears after the data statements.
Wed 25-Sep-2024 10:40
Kudos man - keep going
Mon 30-Sep-2024 12:37
https://stigc.dk/c64/basic/?s=2TVNLbmMxDNv7FO8AWljyTw7QRYpJNwWaQdABui3QI_T-KCn7oYMkdt6TJZEUrfn4-vz-PLSIi7UmpiraXFo-v0VyfKxJRxzHsifVlYfXtsI6GMBjFZbo0rBWmQNrFatNhuSktvLYqigyoia6l45NyuDqhescEag4hRotadmpBc2mnBt7xkMxdJ3RsBTi1D74koh6SVp3es2gSkxqMvzErazTLDTQjtJ5oo5JqbEZAbRVYVQZbTXgrxEGUFbfYHR6yIS-FcUrtUnat9Cj80g3os4hFE-wmbNKwWEkOFjXEkwUqo2VDBF1aIjdCVyhFAE6cOJgnQwtQEAI9VErqW_iGaoApCLDMjgUWVLFuE4W2pYcLcRUr0nnph3jimEuhTPPj__w7nIeAjrRJfv1lxI9vph0kJ5LP1CYESsRCgkRI0HryfQcOufF9rYIxL9A4Rxyp95z6dqIAc4QT7btRmmot-36Huk0HBtRjRx8nR10eyLZdpzTJXBieD3MMckjhkwTGdWuEYnKuBQ12ek3qsnDlB825X4-a8Y1WcaFYLh04XiKgWiy9lvBDP2XN0zgL7aQEtShNyUleV7EVPLxcn8c1yfQmsf7nRemXh6365_j-fL3_no7rvJ8ebt9vF8e_95-AA