thinking outside the cartridge

jake "ferris" taylor

hello everyone

love your process

FIRST THINGS FIRST

what does a snes look like?

AUDIO

MAIN SYSTEM

VIDEO

CPU

128kb RAM

CPU

64kb RAM

DSP

PPU

64kb VRAM

512b CGRAM

544b OAM

CARTRIDGE

1-4MB ROM

AUDIO

MAIN SYSTEM

VIDEO

CPU

128kb RAM

CPU

64kb RAM

DSP

PPU

64kb VRAM

512b CGRAM

544b OAM

CARTRIDGE

1-4MB ROM

DMA!!

brief intro to oldschool coding

main()
{
    printf("Hello World");
}
public void Render(int width, int height, Sync sync, double time)
{
    GL.ClearColor(Color.FromArgb(0, 0, 68));
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.LoadIdentity();
    GL.Translate(0.0f, 0.0f, -25.0f);

    var color = Color.FromArgb(113, 232, 81);
    Helpers.Bar(color, 0.0f, -1.0f, 3.0f, 0.5f);
    Helpers.Bar(color, 0.0f,  0.0f, 3.0f, 0.5f);
    Helpers.Bar(color, 0.0f,  1.0f, 3.0f, 0.5f);
}
                ; x = voice data offset (trashes y)
ReadPatternByte:
                    mov a, VoiceState::CompType + x
                    bne +
                        ; Uncompressed
                        call ReadNextByte
                    ret
                        
                        ; Rle (run length, symbol)
+:                      mov a, VoiceState::CompParam1 + x
                        bne +
                            ; Start next run
                            call ReadNextByte
                            push a
                            call ReadNextByte
                            mov VoiceState::CompParam2 + x, a
                            pop a
                            
+:                      ; Read next byte from run
                        dec a
                        mov VoiceState::CompParam1 + x, a
                        mov a, VoiceState::CompParam2 + x
                ret
                
                ; x = voice data offset (trashes y)
ReadNextByte:
                    mov a, VoiceState::PatternAddrLow + x
                    mov CurrentByteAddrLow, a
                    clrc
                    adc a, #$01
                    mov VoiceState::PatternAddrLow + x, a
                    mov a, VoiceState::PatternAddrHigh + x
                    mov CurrentByteAddrHigh, a
                    adc a, #$00
                    mov VoiceState::PatternAddrHigh + x, a
                    
                    mov y, #$00
                    mov a, [CurrentByteAddr] + y
                ret
                
                ; a = note, x = voice data offset
SetNote:
                    asl a
                    mov y, a
                    mov a, !MusicData.PitchTable + y
                    mov VoiceState::PitchLow + x, a
                    inc y
                    mov a, !MusicData.PitchTable + y
                    mov VoiceState::PitchHigh + x, a
                ret
                
                ; a = instrument number
LoadInstrumentDataAddr:
                    push x
                    
                    dec a
                    asl a
                    mov x, a
                    mov a, !MusicData.InstrumentTable + x
                    mov CurrentInstrAddrLow, a
                    inc x
                    mov a, !MusicData.InstrumentTable + x
                    mov CurrentInstrAddrHigh, a
                    
                    pop x
                ret
EffectArpeggio:
                        pop x
                        mov y, #$00
                        mov a, VoiceState::EffectParam2 + x
                        cmp a, #$01
                        bne ++++
                            mov a, VoiceState::EffectParam1 + x
                            xcn a
                            mov y, a
++++:                   cmp a, #$02
                        bne ++++
                            mov a, VoiceState::EffectParam1 + x
                            mov y, a
++++:                   mov a, y
                        and a, #$0f
                        clrc
                        adc a, VoiceState::Note + x ; TODO: Handle overflows
                        call SetNote
                        mov a, VoiceState::EffectParam2 + x
                        inc a
                        cmp a, #$03
                        bcc ++++
                            mov a, #$00
++++:                   mov VoiceState::EffectParam2 + x, a
                        jmp EffectEnd
                    
EffectPitchUp:
                        pop x
                        mov a, VoiceState::EffectParam1 + x
                        xcn a
                        mov y, a
                        and a, #$f0
                        clrc
                        adc a, VoiceState::PitchLow + x
                        mov VoiceState::PitchLow + x, a
                        mov a, y
                        and a, #$0f
                        adc a, VoiceState::PitchHigh + x
                        mov VoiceState::PitchHigh + x, a
                        jmp EffectEnd
                    
EffectPitchDown:
                        pop x
                        mov a, VoiceState::EffectParam1 + x
                        xcn a
                        mov y, a
                        and a, #$f0
                        mov FxWork1, a
                        mov a, VoiceState::PitchLow + x
                        setc
                        sbc a, FxWork1
                        mov VoiceState::PitchLow + x, a
                        mov a, y
                        and a, #$0f
                        mov FxWork1, a
                        mov a, VoiceState::PitchHigh + x
                        sbc a, FxWork1
                        mov VoiceState::PitchHigh + x, a
                        jmp EffectEnd

CHRONOLOGY

1991

childhood

yes i listened to the chrono trigger soundtrack on repeat while preparing this

~2000

~2004

~2008-2011

kickassembler

// Execute macro
:ClearScreen($0400,$20)   // Since they are encapsulated in a scope 
:ClearScreen($4400,$20)   // the two resulting loop labels don’t
                          // interfere


// Define macro
.macro ClearScreen(screen,clearByte) {
    lda #clearByte
    ldx #0
Loop:         // The loop label can’t be seen from the outside
    sta screen,x
    sta screen+$100,x
    sta screen+$200,x
    sta screen+$300,x
    inx
    bne Loop
}

~2008-2011

November 2011

gameboy demo workflow

demon blood

Assembler round 2

MUSIC

official tools

xmsnes

make my own!

be rad

how to do a demo in 3 weeks

video

video compression

image sequence

delta encoding

0 1 2 3 4 5 6 7

0 1 1 1 1 1 1 1

0 1

temporal delta encoding

representing video on snes

virtual machine

LoadPaletteData address length data
LoadVramData address length data
EndOfFrame

EndOfTransmission

LoadPaletteData 0x00 0x16 [...]
LoadVramData 0x2000 0x0800 [...]
EndOfFrame

nu

refining the tech

then it hit me

cracking the code

full control

ideal snes coding environment

  • I could program in F#
  • Using pure, immutable data
  • Composing with code and syncing with Ableton Live

then it hit me ...again

opcodes!!

any.

way.

i.

want.

smash it

conclusion

what's next

thinking outside the cartridge

thank you!