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!

Thinking Outside the Cartridge

By yupferris

Thinking Outside the Cartridge

Modern Ideas Applied to Archaic Devices

  • 1,397