a lot of refactors, and waffling on audio engines.

This commit is contained in:
Shoofle 2025-04-06 11:16:30 -04:00
parent 35bb4dd60b
commit f3a852edc3
78 changed files with 3992 additions and 592 deletions

View File

@ -285,6 +285,8 @@ TheWorld:
ld hl, MY_OAM
; go through MY_OAM and, for any which aren't still visible, set their
; y value to zero.
; this could probably be rewritten as a simpler lookup table if we rewrote it
; using jp hl and pointer math rather than explicitly listing all the possibilities
.cleanUpLoop
ld a, [hl] ; a is the y value and hl points to y
: cp a, (2+1+5)*8

View File

@ -1,15 +1,13 @@
; async variables somewhere in RAM
def vAsyncAF equ ASYNC_VARS_START
def vAsyncHL EQU vAsyncAF+2
def vAsyncDE EQU vAsyncHL+2
def vAsyncBC equ vAsyncDE+2
def vAsyncPC equ vAsyncBC+2
def vAsyncNext equ vAsyncPC+2
def vAsyncAfter equ vAsyncNext+2
def vAsyncProgress equ vAsyncAfter+2
def vAsyncMainSP equ vAsyncProgress+2
def vAsyncThreadSP equ vAsyncMainSP+2
PUSHS "Async Variables", WRAM0[ASYNC_VARS_START]
vAsyncAF: dw
vAsyncHL: dw
vAsyncDE: dw
vAsyncBC: dw
vAsyncPC: dw
vAsyncMainSP: dw
vAsyncThreadSP: dw
POPS
; canonical ordering to push should be: AF, BC, DE, HL,
def ASYNC_STACK_TOP equ $ffc0
@ -18,6 +16,7 @@ def ASYNC_STACK_TOP equ $ffc0
; second value is the destination of the async call, at ffbc = ffbe-2 = ffc0-2-2
def ASYNC_THREAD_CALL equ ASYNC_STACK_TOP - 2 - 2
; timing for async execution, in scanlines
def SAFE_ASYNC_START EQU 148
def SAFE_ASYNC_END EQU 153

853
Audio.inc
View File

@ -1,347 +1,534 @@
PUSHS "Sound Variables", WRAM0[AUDIO_VARS_START]
ordersStart: dw
println "orders start is ", ordersStart
ordersLength: db
; audio player state
currentOrderNumber: db
currentRowNumber: db
println "currentRowNumber is ", currentRowNumber
currentTickNumber: db
currentPattern: dw
println "current pattern is ", currentPattern
audioReadHead: dw
AUDIO_VARIABLES_BEGIN:
speed: db
macro channel ; macro to define all the channell data in parallel
channel_\1_note: db
channel_\1_duty: db
channel_\1_volume: db
channel_\1_slide: db
channel_\1_arp: db
channel_\1_pan: db
channel_\1_vibrato: db
channel_\1_trigger: db
endm
channel 1
channel 2
channel 3
channel 4
println "channel 1 note is ", channel_1_note
AUDIO_VARIABLES_END:
POPS
SoundSetup:
ld a, LOW(gbtarottheme)
ld [ordersStart], a
ld a, HIGH(gbtarottheme)
ld [ordersStart+1], a
ld a, BANK(gbt_play)
ld [rROMB0], a
ld a, 0
ld [currentOrderNumber], a
ld [currentRowNumber], a
ld [currentTickNumber], a
ld de,pythonrangetest
ld bc,BANK(instr_test_data)
ld a,$05
call gbt_play ; Play song
ld a, $FF
ld [currentRowNumber], a
ld a, [ordersStart]
ld l, a
ld a, [ordersStart+1]
ld h, a
inc hl
ld a, [hl+]
ld [currentPattern], a
ld [audioReadHead], a
ld a, [hl+]
ld [currentPattern+1], a
ld [audioReadHead+1], a
ld hl, ZEROES
ld de, AUDIO_VARIABLES_BEGIN
ld bc, AUDIO_VARIABLES_END - AUDIO_VARIABLES_BEGIN
call CopyRange
ld a, 6
ld [speed], a
ld a, %1000_1111
ld [rNR52], a
ld a, $ff
ld [rNR51], a
ld a, $11
ld [rNR50], a
ld a, 1
call gbt_loop
ld a, [cvCardBank]
ld [rROMB0], a
ret
SoundUpdate:
ld a, [speed]
ld b, a
ld a, [currentTickNumber]
inc a
call ArrayClampLoopingB
ld [currentTickNumber], a
ld a, BANK(gbt_play)
ld [rROMB0], a
cp a, 0
jp nz, .doneAllPackets ; if we're not zero, then just update the things
call gbt_update
ld b, 64
ld a, [currentRowNumber]
inc a
call ArrayClampLoopingB
ld [currentRowNumber], a
cp a, 0
jp nz, :+ ; if the new row number is zero, then we go to the next pattern.
ld a, AUDHIGH_RESTART
ld [rNR14], a
; TODO: update for song orders
ld a, [currentPattern]
ld [audioReadHead], a
ld a, [currentPattern+1]
ld [audioReadHead+1], a
:
ld a, [audioReadHead]
ld l, a
ld a, [audioReadHead+1]
ld h, a
ld a, [currentRowNumber]
ld b, a
ld a, [hl+]
and a, $F0
cp a, 0
ret nz
ld a, [hl+]
cp a, b
jp nz, .doneAllPackets ; return if the current row doesn't equal the row we're looking at
; else we're looking at packets
.packetExamine
ld a, l
ld [audioReadHead], a
ld a, h
ld [audioReadHead+1], a
ld d, h
ld e, l
ld hl, channel_1_note
ld a, [de]
and a, $0F
cp a, 1
jp nz, :+
ld hl, channel_1_note
: cp a, 2
jp nz, :+
ld hl, channel_2_note
: cp a, 3
jp nz, :+
ld hl, channel_3_note
: cp a, 4
jp nz, :+
ld hl, channel_4_note
:
ld a, [de]
and a, $F0 ; grab the top nibble of [hl], which holds the effect.
; if nibble is zero, then update our read head (already done) and return.
cp a, $00
jp z, .doneAllPackets
ld bc, channel_1_note - channel_1_note
cp a, $10
jp z, .updateValue
ld bc, channel_1_volume - channel_1_note
cp a, $20
jp z, .updateVolume
ld bc, channel_1_duty - channel_1_note
cp a, $30
jp z, .updateValue
ld bc, channel_1_pan - channel_1_note
cp a, $40
jp z, .updateValue
ld bc, channel_1_arp - channel_1_note
cp a, $50
jp z, .updateValue
ld bc, channel_1_vibrato - channel_1_note
cp a, $60
jp z, .updateValue
ld bc, channel_1_slide - channel_1_note
cp a, $70
jp z, .updateValue
ld bc, channel_1_volume - channel_1_note
cp a, $80
jp z, .noteCut
cp a, $90
jp z, .patternJump
cp a, $a0
jp z, .breakSetStep
cp a, $b0
jp z, .setSpeed
cp a, $c0
jp z, .event
; and now a bunch of code for handling all those cases!
.updateVolume:
inc de
add hl, bc
ld a, 0
ld [channel_1_trigger], a
ld a, [hl]
cp a, 0
jp nz, :+
; if the volume is zero to start with,
; and the new volume is nonzero,
ld a, [de]
; set the trigger flag
cp a, 0
jp z, :+
ld a, AUDHIGH_RESTART
ld [channel_1_trigger], a
:
ld a, [de]
ld [hl], a
inc de
ld h, d
ld l, e
jp .packetExamine
.updateValue:
inc de
add hl, bc
: ld a, [de]
ld [hl], a
inc de
ld h, d
ld l, e
jp .packetExamine
.noteCut:
inc de
add hl, bc
ld [hl], 0
ld a, AUDHIGH_RESTART
ld [channel_1_trigger], a
inc de
ld h, d
ld l, e
jp .packetExamine
.patternJump:
; TODO
inc de
inc de
ld h, d
ld l, e
jp .packetExamine
.breakSetStep:
; TODO
inc de
inc de
ld h, d
ld l, e
jp .packetExamine
.setSpeed:
inc de
ld a, [de]
ld [speed], a
inc de
ld h, d
ld l, e
jp .packetExamine
.event:
; TODO
inc de
inc de
ld h, d
ld l, e
jp .packetExamine
.doneAllPackets:
call Channel_1_Update
call Channel_2_Update
call Channel_3_Update
call Channel_4_Update
ld a, [cvCardBank]
ld [rROMB0], a
ret
Channel_1_Update:
ld a, [channel_1_duty]
or a, $0a
ld [rNR11], a
ld a, [channel_1_volume]
sla a
sla a
sla a
sla a
and a, $F0
ld [rNR12], a
ld a, [channel_1_note]
ld hl, note_periods
ld b, 0
ld c, a
add hl, bc
add hl, bc ; double width values
ld a, [hl+]
ld [rNR13], a
; File created by mod2gbt
; s3m2gbt modified by shoofle to output rgbds-compatible asm files
SECTION "pythonrangetest_0", ROMX
pythonrangetest_0:
db $BF,$00,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$01,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$02,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$03,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$04,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$05,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$06,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$07,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$08,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$09,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0F,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$10,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$11,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$12,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$13,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$14,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$15,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$16,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$17,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$18,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$19,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1F,$30,$00,$00,$00,
db $00,$00,$00,$00,
SECTION "pythonrangetest_1", ROMX
pythonrangetest_1:
db $BF,$20,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$21,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$22,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$23,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$24,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$25,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$26,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$27,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$28,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$29,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2F,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$30,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$31,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$32,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$33,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$34,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$35,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$36,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$37,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$38,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$39,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3F,$30,$00,$00,$00,
db $00,$00,$00,$00,
SECTION "pythonrangetest_2", ROMX
pythonrangetest_2:
db $BF,$40,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$41,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$42,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$43,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$44,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$45,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$46,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$47,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
pythonrangetest_init_state:
db 0x01,0x06,0x02,0x11,0x22,0x44,0x88,0x00,
SECTION "pythonrangetest", ROMX
pythonrangetest:
db BANK(pythonrangetest_0)
dw pythonrangetest_0
db BANK(pythonrangetest_1)
dw pythonrangetest_1
db BANK(pythonrangetest_2)
dw pythonrangetest_2
db $00
dw $0000
SECTION "instr_test_0", ROMX
instr_test_0:
DB $98, $1F, $20, $20, $4A, $07
DB $2F, $00, $00, $20
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $98, $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $98, $3F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $98, $0F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $2F, $00, $00, $00
DB $20, $00, $98, $10, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $11, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $12, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $13, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
SECTION "instr_test_1", ROMX
instr_test_1:
DB $00, $00, $98, $14, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $15, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $16, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $98, $17, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $21, $00
DB $00, $00, $20, $80, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $81, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $82, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $83, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
SECTION "instr_test_2", ROMX
instr_test_2:
DB $00, $00, $00, $84, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $85, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $86, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $87, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $88, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $89, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $8A, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $8B, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
SECTION "instr_test_3", ROMX
instr_test_3:
DB $00, $00, $00, $8C, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $8D, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $8E, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $8F, $0F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $2F
DB $00, $00, $00, $20
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
DB $00, $00, $00, $00
SECTION "instr_test_data", ROM0
instr_test_data::
DB BANK(instr_test_0)
DW instr_test_0
DB BANK(instr_test_1)
DW instr_test_1
DB BANK(instr_test_2)
DW instr_test_2
DB BANK(instr_test_3)
DW instr_test_3
DB $00
DW $0000
ld a, [channel_1_trigger]
or a, [hl]
or a, AUDHIGH_LENGTH_OFF
ld [rNR14], a
ld a, 0
ld [channel_1_trigger], a
ret
Channel_2_Update:
Channel_3_Update:
Channel_4_Update:
ret
note_periods:
dw 44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986, ; C3 to B3
dw 1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517, ; C4 to B4
dw 1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783, ; C5 to B5
dw 1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915, ; C6 to B6
dw 1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982, ; C7 to B7
dw 1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015 ; C8 to B8
INCLUDE "theme.inc"

View File

@ -1,6 +1,8 @@
; CARD_HELPER_VARS_START defines the beginning of the $100 bytes set aside for system usage
def cvCardBank equ 0
def cvCardAddress equ 1 ; location of the current card's struct
PUSHS "Card Helper Vars", WRAM0[CARD_HELPER_VARS_START]
cvCardBank: db
cvCardAddress: dw
POPS
LoadCardData:
LoadCardDataAsync:
@ -33,14 +35,14 @@ LoadCardDataAsync:
add hl, bc ; triple add bc entries are bank, addr, addr
ld a, [hl+]
ld [CARD_HELPER_VARS_START + cvCardBank], a
ld [cvCardBank], a
ld [rROMB0], a ; select the specified bank
; follow the pointer we're looking at, and write it to cvCardAddress
ld a, [hl+]
ld [CARD_HELPER_VARS_START + cvCardAddress], a
ld [cvCardAddress], a
ld c, a
ld a, [hl+]
ld [CARD_HELPER_VARS_START + cvCardAddress + 1], a
ld [cvCardAddress + 1], a
ld b, a
ld h, b
@ -82,9 +84,9 @@ LoadCardDataAsync:
call CopyTilesToMap
ld a, [CARD_HELPER_VARS_START + cvCardAddress]
ld a, [cvCardAddress]
ld l, a
ld a, [CARD_HELPER_VARS_START + cvCardAddress + 1]
ld a, [cvCardAddress + 1]
ld h, a ; hl now contains the address of the card data.
ld b, 0
@ -110,9 +112,9 @@ LoadCardDataAsync:
call CardInit
ld a, [CARD_HELPER_VARS_START + cvCardAddress]
ld a, [cvCardAddress]
ld l, a
ld a, [CARD_HELPER_VARS_START + cvCardAddress + 1]
ld a, [cvCardAddress + 1]
ld h, a ; hl now contains the address of the card data.
ld b, 0
@ -135,9 +137,9 @@ LoadCardDataAsync:
call CopyRange
ld a, [CARD_HELPER_VARS_START + cvCardAddress]
ld a, [cvCardAddress]
ld l, a
ld a, [CARD_HELPER_VARS_START + cvCardAddress + 1]
ld a, [cvCardAddress + 1]
ld h, a ; hl now contains the address of the card data.
ld b, 0

View File

@ -1,18 +1,3 @@
; variables for safe transfer async function
DEF vSafeCopySource EQU $ff80
DEF vSafeCopyDest EQU vSafeCopySource + 2
DEF vSafeCopyCount EQU vSafeCopyDest + 2 ; check this for safe transfer being complete
DEF vSafeCopyOriginalCount EQU vSafeCopyCount + 2
; stash previous interrupt state before using the interrupts
DEF vSafeCopyLYC EQU vSafeCopyOriginalCount + 2 ; stashes $FF45, the LYC register
DEF vSafeCopySTAT EQU vSafeCopyLYC + 1 ; stashes $FF41, the STAT register
DEF vSafeCopyInterrupt EQU vSafeCopySTAT + 1 ; stashes the previous LCD interrupt
DEF vSafeCopyInterruptEnable EQU vSafeCopyInterrupt + 2 ; stashes $FFFF, which interrupts are enabled
DEF SAFE_TRANSFER_START EQU 145
DEF SAFE_TRANSFER_END EQU 153
CopyRange:
CopyRangeUnsafe:
; hl is source

View File

@ -102,7 +102,6 @@ RandomUnderD: ; takes d as limit (inclusive)
ret c
jr .inOne
SwapCards: ; takes hl as array, c and e as indices to swap (uses b and d)
inc hl ; first element of array
push hl ; save it for later
@ -122,10 +121,3 @@ SwapCards: ; takes hl as array, c and e as indices to swap (uses b and d)
add hl, bc
ld [hl], d ; put old [hl+0e] in [hl+0c]
ret

View File

@ -1,5 +1,4 @@
; screen variables shared with screencardread
;DEF vPreviousCardIndex EQU VARIABLES_START
ScreenCardBrowse:
dw CardBrowseSetup

View File

@ -1,5 +1,7 @@
; screen variables
DEF vPreviousCardIndex EQU SCREEN_VARS_START+16
PUSHS UNION "Screen Variables", WRAM0[SCREEN_VARS_START]
ds 16 ; why are we putting this so far in?
vPreviousCardIndex: db
POPS
ScreenCardRead:
dw CardReadSetup

View File

@ -1,20 +1,22 @@
; global variables at the start of hram
DEF vCurrentSpread EQU GLOBAL_VARS_START ; 16bit address of current spread, ff90
DEF vSelectedSpreadIndex equ vCurrentSpread + 2 ; ff92
def vSelectedSpreadCard equ vSelectedSpreadIndex + 1 ; ff93
def vSelectedCardIndex equ vSelectedSpreadCard+1 ; ff94
DEF vFrameCountSquares EQU vSelectedCardIndex+1 ; ff95
DEF vTime EQU vFrameCountSquares+1 ; 16bit ; ff96
def vBlocked EQU vTime+2
PUSHS UNION "Global Variables", WRAM0[GLOBAL_VARS_START]
vCurrentSpread: dw
vSelectedSpreadIndex: db
vSelectedSpreadCard: db
vSelectedCardIndex: db
vFrameCountSquares: db
vTime: dw
vBlocked: db
println "vBlocked is ", vBlocked ;
POPS
; screen-specific variables
DEF vFrameCount1 EQU SCREEN_VARS_START
DEF vFrameCount2 equ vFrameCount1+1
DEF vFrameCount3 EQU vFrameCount2+1
DEF vMenuIndex equ vFrameCount3+1
DEF vMenuIndexPrevious equ vMenuIndex + 1
PUSHS UNION "Screen Variables", WRAM0[SCREEN_VARS_START]
vFrameCount1: db
vFrameCount2: db
vFrameCount3: db
vMenuIndex: db
vMenuIndexPrevious: db
POPS
ScreenMainMenu:
dw MainMenuSetup

View File

@ -1,11 +1,10 @@
; screen variables already ddefined in screencardread
;DEF vPreviousCardIndex EQU VARIABLES_START
;def vBlocked equ vPreviousCardIndex + 1
def vAnimationFrame EQU SCREEN_VARS_START
def vState EQU vAnimationFrame+1
def vCurrentAnimation EQU vState+1 ; 2 bytes
def vShuffleIndex equ vCurrentAnimation+2
def vShuffleTime equ vShuffleIndex+1 ; 2 bytes
PUSHS UNION "Screen Variables", WRAM0[SCREEN_VARS_START]
vAnimationFrame: db ;def vAnimationFrame EQU SCREEN_VARS_START
vState: db ;def vState EQU vAnimationFrame+1
vCurrentAnimation: dw ;def vCurrentAnimation EQU vState+1 ; 2 bytes
vShuffleIndex: db ;def vShuffleIndex equ vCurrentAnimation+2
vShuffleTime: dw ;def vShuffleTime equ vShuffleIndex+1 ; 2 bytes
POPS
def S_Center = 0
def S_RightOut = 1

View File

@ -1,5 +1,7 @@
DEF vPreviousSpreadIndex EQU SCREEN_VARS_START
def vPreviousSpreadCard equ vPreviousSpreadIndex + 1
PUSHS UNION "Screen Variables", WRAM0[SCREEN_VARS_START]
vPreviousSpreadIndex: db ; EQU SCREEN_VARS_START
vPreviousSpreadCard: db ; equ vPreviousSpreadIndex + 1
POPS
ScreenSpreadSelect:
dw SpreadSelectSetup

BIN
carillon.sav Normal file

Binary file not shown.

BIN
gb_tarot_theme_2.mod Normal file

Binary file not shown.

BIN
gb_tarot_theme_2.mod~ Normal file

Binary file not shown.

BIN
gb_tarot_theme_2.s3m Normal file

Binary file not shown.

643
gbt_player.inc Normal file
View File

@ -0,0 +1,643 @@
;###############################################################################
;
; GBT Player v3.1.0
;
; SPDX-License-Identifier: MIT
;
; Copyright (c) 2009-2021, Antonio Niño Díaz <antonio_nd@outlook.com>
;
;###############################################################################
INCLUDE "hardware.inc"
;###############################################################################
;
; GBT Player v3.1.0
;
; SPDX-License-Identifier: MIT
;
; Copyright (c) 2009-2020, Antonio Niño Díaz <antonio_nd@outlook.com>
;
;###############################################################################
IF !DEF(GBT_PLAYER_INC)
DEF GBT_PLAYER_INC = 1
;###############################################################################
EXPORT gbt_play ; Starts playing a song.
; de = pointer to song data
; a = default speed. Careful, 0 = 256!
; bc = data bank (b ignored if ROM with < 256 banks)
; THIS WILL CHANGE ROM BANK!!!
EXPORT gbt_pause ; Pauses or unpauses the song.
; a = 0 to pause, anything else to unpause.
EXPORT gbt_loop ; Enables/disables looping at the end of the song.
; a = 0 to stop at the end, anything else to loop
EXPORT gbt_stop ; Stops the song.
EXPORT gbt_enable_channels ; Enables given channels.
; a = channel flags ORed:
; channel 1 = 1
; channel 2 = 2
; channel 3 = 4
; channel 4 = 8
EXPORT gbt_update ; Updates the player, must be called each VBL.
; Note: This will change the active ROM bank!
; - If the following value is uncomented, the total of banks allowed is 512
; (or more), but it's a bit slower. MBC5 ONLY, DOESN'T WORK WITH OTHERS!!!
; YOU MUST USE THE -512-banks OPTION WHEN CONVERTING A SONG WITH mod2gbt!!!
; - If it's commented, only 256 banks are allowed, it's a little bit faster
; and saves a few bytes. MBC1, MBC3 and MBC5 (and others).
; DEF GBT_USE_MBC5_512BANKS = 1
;###############################################################################
ENDC ; GBT_PLAYER_INC
;###############################################################################
;###############################################################################
SECTION "GBT_VAR_1",WRAMX[$d800]
;-------------------------------------------------------------------------------
gbt_playing: DS 1
; pointer to the pattern pointer array
gbt_pattern_array_ptr: DS 2 ; LSB first
IF DEF(GBT_USE_MBC5_512BANKS)
gbt_pattern_array_bank: DS 2 ; LSB first
ELSE
gbt_pattern_array_bank: DS 1
ENDC
; playing speed
gbt_speed:: DS 1
; Up to 12 bytes per step are copied here to be handled in functions in bank 1
gbt_temp_play_data:: DS 12
gbt_loop_enabled: DS 1
gbt_ticks_elapsed:: DS 1
gbt_current_step:: DS 1
gbt_current_pattern:: DS 1
gbt_current_step_data_ptr:: DS 2 ; pointer to next step data - LSB first
IF DEF(GBT_USE_MBC5_512BANKS)
gbt_current_step_data_bank:: DS 2 ; bank of current pattern data - LSB first
ELSE
gbt_current_step_data_bank:: DS 1 ; bank of current pattern data
ENDC
gbt_channels_enabled:: DS 1
gbt_pan:: DS 4*1 ; Ch 1-4
gbt_vol:: DS 4*1 ; Ch 1-4
gbt_instr:: DS 4*1 ; Ch 1-4
gbt_freq:: DS 3*2 ; Ch 1-3
gbt_channel3_loaded_instrument:: DS 1 ; current loaded instrument ($FF if none)
; Arpeggio -> Ch 1-3
gbt_arpeggio_freq_index:: DS 3*3 ; {base index, base index+x, base index+y} * 3
gbt_arpeggio_enabled:: DS 3*1 ; if 0, disabled
gbt_arpeggio_tick:: DS 3*1
; Cut note
gbt_cut_note_tick:: DS 4*1 ; If tick == gbt_cut_note_tick, stop note.
; Last step of last pattern this is set to 1
gbt_have_to_stop_next_step:: DS 1
gbt_update_pattern_pointers:: DS 1 ; set to 1 by jump effects
;###############################################################################
SECTION "GBT_BANK0",ROM0
;-------------------------------------------------------------------------------
gbt_get_pattern_ptr:: ; a = pattern number
; loads a pointer to pattern a into gbt_current_step_data_ptr and
; gbt_current_step_data_bank
ld e,a
ld d,0
IF DEF(GBT_USE_MBC5_512BANKS)
ld a,[gbt_pattern_array_bank+0]
ld [rROMB0],a ; MBC5 - Set bank
ld a,[gbt_pattern_array_bank+1]
ld [rROMB1],a ; MBC5 - Set bank
ELSE
ld a,[gbt_pattern_array_bank]
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank
ENDC
ld hl,gbt_pattern_array_ptr
ld a,[hl+]
ld h,[hl]
ld l,a
; hl = pointer to list of pointers
; de = pattern number
IF DEF(GBT_USE_MBC5_512BANKS)
add hl,de
ENDC
add hl,de
add hl,de
add hl,de
; hl = pointer to pattern bank
ld a,[hl+]
ld [gbt_current_step_data_bank+0],a
IF DEF(GBT_USE_MBC5_512BANKS)
ld a,[hl+]
ld [gbt_current_step_data_bank+1],a
ENDC
; hl = pointer to pattern data
ld a,[hl+]
ld h,[hl]
ld l,a
ld a,l
ld [gbt_current_step_data_ptr],a
ld a,h
ld [gbt_current_step_data_ptr+1],a
ret
;-------------------------------------------------------------------------------
gbt_get_pattern_ptr_banked:: ; a = pattern number
push de
call gbt_get_pattern_ptr
pop de
ld hl,gbt_current_step_data_ptr
ld a,[hl+]
ld b,a
ld a,[hl]
or a,b
jr nz,.dont_loop
xor a,a
ld [gbt_current_pattern], a
.dont_loop:
IF DEF(GBT_USE_MBC5_512BANKS)
xor a,a
ld [rROMB1],a
ENDC
ld a,$01
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank 1
ret
;-------------------------------------------------------------------------------
gbt_play:: ; de = data, bc = bank, a = speed
ld hl,gbt_pattern_array_ptr
ld [hl],e
inc hl
ld [hl],d
ld [gbt_speed],a
ld a,c
ld [gbt_pattern_array_bank+0],a
IF DEF(GBT_USE_MBC5_512BANKS)
ld a,b
ld [gbt_pattern_array_bank+1],a
ENDC
ld a,0
call gbt_get_pattern_ptr
xor a,a
ld [gbt_current_step],a
ld [gbt_current_pattern],a
ld [gbt_ticks_elapsed],a
ld [gbt_loop_enabled],a
ld [gbt_have_to_stop_next_step],a
ld [gbt_update_pattern_pointers],a
ld a,$FF
ld [gbt_channel3_loaded_instrument],a
ld a,$0F
ld [gbt_channels_enabled],a
ld hl,gbt_pan
ld a,$11 ; L and R
ld [hl+],a
add a,a
ld [hl+],a
add a,a
ld [hl+],a
add a,a
ld [hl],a
ld hl,gbt_vol
ld a,$F0 ; 100%
ld [hl+],a
ld [hl+],a
ld a,$20 ; 100%
ld [hl+],a
ld a,$F0 ; 100%
ld [hl+],a
ld a,0
ld hl,gbt_instr
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld hl,gbt_freq
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [gbt_arpeggio_enabled+0],a
ld [gbt_arpeggio_enabled+1],a
ld [gbt_arpeggio_enabled+2],a
ld a,$FF
ld [gbt_cut_note_tick+0],a
ld [gbt_cut_note_tick+1],a
ld [gbt_cut_note_tick+2],a
ld [gbt_cut_note_tick+3],a
ld a,$80
ld [rNR52],a
ld a,$00
ld [rNR51],a
ld a,$00 ; 0%
ld [rNR50],a
xor a,a
ld [rNR10],a
ld [rNR11],a
ld [rNR12],a
ld [rNR13],a
ld [rNR14],a
ld [rNR21],a
ld [rNR22],a
ld [rNR23],a
ld [rNR24],a
ld [rNR30],a
ld [rNR31],a
ld [rNR32],a
ld [rNR33],a
ld [rNR34],a
ld [rNR41],a
ld [rNR42],a
ld [rNR43],a
ld [rNR44],a
ld a,$77 ; 100%
ld [rNR50],a
ld a,$01
ld [gbt_playing],a
ret
;-------------------------------------------------------------------------------
gbt_pause:: ; a = pause/unpause
ld [gbt_playing],a
or a,a
jr nz,.gbt_pause_unmute
; Silence all channels
xor a,a
ld [rNR51],a
ret
.gbt_pause_unmute: ; Unmute sound if playback is resumed
; Restore panning status
ld hl,gbt_pan
ld a,[hl+]
or a,[hl]
inc hl
or a,[hl]
inc hl
or a,[hl]
ld [rNR51],a
ret
;-------------------------------------------------------------------------------
gbt_loop:: ; a = loop/don't loop
ld [gbt_loop_enabled],a
ret
;-------------------------------------------------------------------------------
gbt_stop::
xor a,a
ld [gbt_playing],a
ld [rNR50],a
ld [rNR51],a
ld [rNR52],a
ret
;-------------------------------------------------------------------------------
gbt_enable_channels:: ; a = channel flags (channel flag = (1<<(channel_num-1)))
ld [gbt_channels_enabled],a
ret
;-------------------------------------------------------------------------------
EXPORT gbt_update_bank1
gbt_update::
ld a,[gbt_playing]
or a,a
ret z ; If not playing, return
; Handle tick counter
ld hl,gbt_ticks_elapsed
ld a,[gbt_speed] ; a = total ticks
ld b,[hl] ; b = ticks elapsed
inc b
ld [hl],b
cp a,b
jr z,.dontexit
; Tick != Speed, update effects and exit
IF DEF(GBT_USE_MBC5_512BANKS)
xor a,a
ld [rROMB1],a
ENDC
ld a,$01
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank 1
; Call update function in bank 1 (in gbt_player_bank1.s)
call gbt_update_effects_bank1
ret
.dontexit:
ld [hl],$00 ; reset tick counter
; Clear tick-based effects
; ------------------------
xor a,a
ld hl,gbt_arpeggio_enabled ; Disable arpeggio
ld [hl+],a
ld [hl+],a
ld [hl],a
dec a ; a = $FF
ld hl,gbt_cut_note_tick ; Disable cut note
ld [hl+],a
ld [hl+],a
ld [hl+],a
ld [hl],a
; Update effects
; --------------
IF DEF(GBT_USE_MBC5_512BANKS)
xor a,a
ld [rROMB1],a
ENDC
ld a,$01
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank 1
; Call update function in bank 1 (in gbt_player_bank1.s)
call gbt_update_effects_bank1
; Check if last step
; ------------------
ld a,[gbt_have_to_stop_next_step]
or a,a
jr z,.dont_stop
call gbt_stop
ld a,0
ld [gbt_have_to_stop_next_step],a
ret
.dont_stop:
; Get this step data
; ------------------
; Change to bank with song data
IF DEF(GBT_USE_MBC5_512BANKS)
ld a,[gbt_current_step_data_bank+0]
ld [rROMB0],a ; MBC5 - Set bank
ld a,[gbt_current_step_data_bank+1]
ld [rROMB1],a ; MBC5 - Set bank
ELSE
ld a,[gbt_current_step_data_bank]
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank
ENDC
; Get step data
ld a,[gbt_current_step_data_ptr]
ld l,a
ld a,[gbt_current_step_data_ptr+1]
ld h,a ; hl = pointer to data
ld de,gbt_temp_play_data
ld b,4
.copy_loop: ; copy as bytes as needed for this step
ld a,[hl+]
ld [de],a
inc de
bit 7,a
jr nz,.more_bytes
bit 6,a
jr z,.no_more_bytes_this_channel
jr .one_more_byte
.more_bytes:
ld a,[hl+]
ld [de],a
inc de
bit 7,a
jr z,.no_more_bytes_this_channel
.one_more_byte:
ld a,[hl+]
ld [de],a
inc de
.no_more_bytes_this_channel:
dec b
jr nz,.copy_loop
ld a,l
ld [gbt_current_step_data_ptr],a
ld a,h
ld [gbt_current_step_data_ptr+1],a ; save pointer to data
; Increment step/pattern
; ----------------------
; Increment step
ld a,[gbt_current_step]
inc a
ld [gbt_current_step],a
cp a,64
jr nz,.dont_increment_pattern
; Increment pattern
ld a,0
ld [gbt_current_step],a ; Step 0
ld a,[gbt_current_pattern]
inc a
ld [gbt_current_pattern],a
call gbt_get_pattern_ptr
ld a,[gbt_current_step_data_ptr]
ld b,a
ld a,[gbt_current_step_data_ptr+1]
or a,b
jr nz,.not_ended ; if pointer is 0, song has ended
ld a,[gbt_loop_enabled]
and a,a
jr z,.loop_disabled
; If loop is enabled, jump to pattern 0
ld a,0
ld [gbt_current_pattern],a
call gbt_get_pattern_ptr
jr .end_handling_steps_pattern
.loop_disabled:
; If loop is disabled, stop song
; Stop it next step, if not this step won't be played
ld a,1
ld [gbt_have_to_stop_next_step],a
.not_ended:
.dont_increment_pattern:
.end_handling_steps_pattern:
IF DEF(GBT_USE_MBC5_512BANKS)
xor a,a
ld [rROMB1],a ; MBC5
ENDC
ld a,$01
ld [rROMB0],a ; MBC1, MBC3, MBC5 - Set bank 1
; Call update function in bank 1 (in gbt_player_bank1.s)
call gbt_update_bank1
; Check if any effect has changed the pattern or step
ld a,[gbt_update_pattern_pointers]
and a,a
ret z
; if any effect has changed the pattern or step, update
xor a,a
ld [gbt_update_pattern_pointers],a ; clear update flag
ld [gbt_have_to_stop_next_step],a ; clear stop flag
ld a,[gbt_current_pattern]
call gbt_get_pattern_ptr ; set ptr to start of the pattern
; Search the step
; Change to bank with song data
IF DEF(GBT_USE_MBC5_512BANKS)
ld a,[gbt_pattern_array_bank+1]
ld [rROMB1],a ; MBC5
ENDC
ld a,[gbt_pattern_array_bank+0]
ld [rROMB0],a ; MBC1, MBC3, MBC5
ld a,[gbt_current_step_data_ptr]
ld l,a
ld a,[gbt_current_step_data_ptr+1]
ld h,a ; hl = pointer to data
ld a,[gbt_current_step]
and a,a
ret z ; if changing to step 0, exit
add a,a
add a,a
ld b,a ; b = iterations = step * 4 (number of channels)
.next_channel:
ld a,[hl+]
bit 7,a
jr nz,.next_channel_more_bytes
bit 6,a
jr z,.next_channel_no_more_bytes_this_channel
jr .next_channel_one_more_byte
.next_channel_more_bytes:
ld a,[hl+]
bit 7,a
jr z,.next_channel_no_more_bytes_this_channel
.next_channel_one_more_byte:
ld a,[hl+]
.next_channel_no_more_bytes_this_channel:
dec b
jr nz,.next_channel
ld a,l
ld [gbt_current_step_data_ptr],a
ld a,h
ld [gbt_current_step_data_ptr+1],a ; save pointer to data
ret
;###############################################################################

1389
gbt_player_bank1.inc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +0,0 @@
; original export script by gabriel reis, modified by shoofle
KeyArtTiles:
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$60,$20,$5c,$3c,$43,$3c,$43,$20,$5c,$00,$60,$00,$00
db $3f,$40,$3f,$40,$3f,$40,$3f,$40,$3f,$40,$3f,$40,$3f,$40,$3f,$40
db $ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00
db $fc,$02,$fc,$02,$fc,$02,$fc,$02,$fc,$02,$fc,$02,$fc,$02,$fc,$02
db $00,$00,$00,$06,$04,$3a,$3c,$c2,$3c,$c2,$04,$3a,$00,$06,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$03,$03
db $1f,$20,$0f,$30,$07,$18,$03,$0c,$01,$06,$00,$03,$00,$01,$00,$00
db $ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$7f,$80,$00,$ff
db $ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$fc,$03,$00,$ff
db $f8,$04,$f0,$0c,$e0,$18,$c0,$30,$80,$60,$00,$c0,$00,$80,$00,$00
db $04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04
db $80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80,$80
db $00,$04,$00,$1c,$08,$74,$78,$84,$38,$44,$08,$34,$00,$0c,$00,$04
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0f,$0f
db $00,$00,$00,$18,$08,$14,$0c,$12,$1c,$22,$18,$27,$00,$38,$c1,$c1
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$0f,$3f,$f8,$ff
db $00,$00,$00,$00,$00,$00,$00,$00,$03,$03,$fe,$ff,$03,$ff,$1c,$fc
db $00,$00,$00,$00,$00,$00,$00,$00,$f8,$f8,$18,$f8,$f8,$f8,$08,$08
db $18,$1f,$33,$3c,$67,$78,$43,$7c,$4f,$71,$7f,$7f,$7f,$7f,$78,$7f
db $67,$ef,$f3,$33,$f3,$13,$f3,$13,$f3,$f3,$f3,$f3,$f4,$f4,$f4,$34
db $1f,$ff,$fa,$fb,$82,$83,$02,$03,$01,$01,$01,$01,$81,$81,$81,$81
db $f0,$f0,$10,$f0,$10,$f0,$10,$f0,$10,$f0,$10,$f0,$10,$f0,$10,$f0
db $14,$14,$14,$14,$14,$14,$14,$14,$14,$14,$22,$22,$22,$22,$22,$22
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$01
db $7f,$60,$5f,$60,$38,$27,$2f,$30,$17,$18,$08,$0f,$00,$0f,$00,$ff
db $f4,$14,$f4,$14,$78,$98,$d8,$38,$a8,$68,$48,$c8,$10,$d0,$1f,$ff
db $81,$81,$80,$80,$40,$40,$40,$40,$40,$40,$40,$40,$20,$20,$e0,$e0
db $10,$f0,$90,$f0,$90,$f0,$90,$f0,$50,$70,$50,$70,$48,$78,$48,$78
db $22,$22,$21,$21,$41,$41,$41,$41,$7f,$7f,$41,$7f,$3e,$3e,$00,$00
db $04,$04,$04,$04,$3c,$3c,$3f,$3f,$07,$07,$02,$1d,$0f,$30,$18,$27
db $80,$80,$80,$80,$f0,$f0,$f0,$f0,$80,$80,$00,$c0,$00,$c0,$80,$40
db $00,$07,$00,$04,$00,$08,$00,$08,$00,$18,$00,$10,$00,$10,$00,$30
db $10,$1f,$0f,$0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $20,$f0,$c0,$dc,$00,$04,$00,$06,$00,$02,$00,$02,$00,$03,$00,$03
db $48,$78,$28,$38,$28,$38,$28,$38,$28,$38,$24,$3c,$14,$1c,$14,$1c
db $1f,$20,$18,$27,$1f,$20,$18,$27,$0f,$10,$00,$0f,$07,$07,$07,$07
db $80,$40,$80,$40,$00,$e3,$00,$ff,$70,$8f,$fc,$83,$ff,$80,$ff,$80
db $00,$20,$00,$40,$00,$c1,$00,$81,$00,$e1,$c0,$21,$c0,$21,$c0,$31
db $00,$01,$00,$21,$00,$21,$00,$21,$00,$21,$00,$23,$01,$22,$01,$22
db $14,$1c,$14,$9c,$14,$9c,$12,$9e,$0a,$de,$00,$ff,$8f,$70,$c1,$3e
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$80,$00,$c0
db $7f,$80,$3f,$c0,$3f,$c0,$3f,$c0,$0f,$f0,$00,$ff,$00,$7f,$00,$7f
db $e0,$11,$e0,$11,$e0,$11,$e0,$11,$c0,$31,$00,$f1,$00,$f1,$00,$f2
db $01,$22,$01,$22,$01,$22,$01,$22,$01,$22,$00,$23,$00,$23,$00,$23
db $9f,$60,$83,$7c,$bf,$40,$c2,$3d,$de,$23,$0a,$fe,$0a,$fe,$0a,$fe
db $80,$40,$80,$40,$00,$c0,$00,$80,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$7f,$00,$7f,$00,$7f,$00,$7f,$00,$7f,$00,$3f,$00,$3f,$00,$3f
db $00,$f2,$00,$e2,$00,$e6,$00,$ec,$00,$dc,$00,$f4,$00,$e4,$00,$c4
db $00,$33,$00,$31,$00,$39,$00,$2d,$00,$25,$00,$27,$00,$23,$00,$23
db $0a,$fe,$0a,$fe,$0a,$fe,$0a,$fe,$09,$ff,$09,$ff,$09,$ff,$09,$ff
db $00,$04,$00,$04,$00,$04,$00,$04,$00,$04,$00,$0c,$00,$08,$00,$08
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$60,$00,$30
db $00,$21,$00,$20,$00,$31,$00,$11,$00,$10,$00,$10,$00,$10,$00,$10
db $09,$ff,$09,$ff,$09,$ff,$09,$8f,$0f,$0f,$00,$06,$00,$00,$00,$00
db $00,$08,$00,$08,$00,$08,$00,$08,$00,$18,$00,$10,$00,$10,$00,$10
db $00,$1c,$00,$06,$00,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$80,$00,$c0,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$08,$00,$08,$00,$08,$00,$0c,$00,$04,$00,$06,$00,$0e,$00,$1a
db $00,$10,$00,$10,$00,$30,$00,$38,$00,$24,$00,$22,$00,$21,$00,$21
db $00,$00,$00,$00,$00,$01,$00,$07,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$32,$00,$c2,$00,$82,$00,$03,$00,$01,$00,$01,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$80,$00,$80,$00,$c0
db $00,$21,$00,$20,$00,$20,$00,$20,$00,$20,$00,$20,$00,$40,$00,$40
db $00,$80,$00,$80,$00,$40,$00,$60,$00,$30,$00,$18,$00,$07,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$c0,$00,$00
db $00,$80,$00,$80,$00,$80,$00,$40,$00,$40,$00,$40,$00,$60,$00,$20
db $00,$40,$00,$40,$00,$40,$00,$40,$00,$60,$00,$3c,$00,$3f,$00,$39
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$c0,$00,$ff
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$ff,$00,$01
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$07,$00,$fc,$00,$bf
db $00,$20,$00,$10,$00,$10,$00,$10,$00,$f0,$00,$b0,$00,$30,$00,$f0
db $00,$00,$00,$7f,$00,$30,$00,$18,$00,$0c,$00,$06,$00,$03,$00,$03
db $00,$00,$00,$ff,$00,$1a,$00,$32,$00,$62,$00,$c2,$00,$82,$00,$82
db $00,$03,$00,$06,$00,$0c,$00,$18,$00,$30,$00,$20,$00,$7f,$00,$00
db $00,$82,$00,$c2,$00,$62,$00,$32,$00,$12,$00,$1a,$00,$ff,$00,$00
BackgroundCopy: ; tiles start at 27
db $1b, $1c, $1d, $1e, $1e, $1f, $20, $1b
db $21, $1b, $22, $23, $24, $25, $1b, $1b
db $26, $27, $28, $29, $2a, $2b, $2c, $2d
db $26, $27, $1b, $2e, $2f, $30, $31, $32
db $26, $27, $33, $34, $35, $36, $37, $38
db $39, $3a, $3b, $1b, $3c, $3d, $3e, $1b
db $3f, $40, $41, $1b, $1b, $42, $43, $44
db $1b, $45, $46, $1b, $1b, $47, $48, $49
db $1b, $4a, $4b, $1b, $1b, $4c, $4d, $1b
db $1b, $1b, $4e, $4f, $1b, $50, $51, $1b
db $1b, $1b, $52, $53, $54, $55, $1b, $1b
db $1b, $1b, $56, $1b, $57, $58, $59, $1b
db $1b, $1b, $5a, $5b, $5c, $1b, $5d, $1b
db $1b, $1b, $5e, $5f, $60, $61, $62, $1b
db $1b, $1b, $1b, $63, $64, $1b, $1b, $1b
db $1b, $1b, $1b, $65, $66, $1b, $1b, $1b

View File

@ -42,16 +42,20 @@ def ZEROES equ $D000
def ONES equ $D200
; allocating $8 spaces for system variables, currently only using $4 bytes
DEF rMYBTN EQU SYSTEM_VARS_START
DEF rMYBTNP EQU rMYBTN + 1
DEF rDELTAT EQU rMYBTNP + 1 ; delta_t where $1000 = 1 second
def rLFSR equ rDELTAT + 1 ; 16 bit
PUSHS "System Variables", WRAM0[SYSTEM_VARS_START]
rMYBTN: db ; EQU SYSTEM_VARS_START
rMYBTNP: db ;EQU rMYBTN + 1
rDELTAT: db ; EQU rMYBTNP + 1 ; delta_t where $1000 = 1 second
rLFSR: dw ; equ rDELTAT + 1 ; 16 bit
POPS
def SAFE_DMA_LOCATION equ $ffc0
def VARIABLE_TILES_START equ 26
def VARIABLE_TILES_START equ 26 ; where in VRAM the variable tiles start
; (i.e. we allocate VARIABLE_TILES_START-1 slots out of that block of 128
; for ever-present ui tiles)
@ -558,8 +562,8 @@ LetterTiles:
INCLUDE "Async.inc"
INCLUDE "Audio.inc"
INCLUDE "Random.inc"
INCLUDE "CopyRangeSafe.inc"
INCLUDE "CopyTilesSafe.inc"
INCLUDE "CopyRange.inc"
INCLUDE "CopyTiles.inc"
INCLUDE "ScreenMainMenu.inc"
INCLUDE "ScreenSpreadSelect.inc"
INCLUDE "CardHelpers.inc"
@ -569,3 +573,4 @@ INCLUDE "ScreenShuffle.inc"
INCLUDE "CardLibrary.inc"
include "gbt_player_bank1.inc"

BIN
mod2gbt Executable file

Binary file not shown.

217
pythonrangetest.c Normal file
View File

@ -0,0 +1,217 @@
; s3m2gbt modified by shoofle to output rgbds-compatible asm filespythonrangetest_0:
db 0xBF,0x00,0x30,0x10,0x10,0x10,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x01,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x02,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x03,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x04,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x05,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x06,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x07,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x08,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x09,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0A,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0B,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0C,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0D,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0E,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x0F,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x10,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x11,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x12,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x13,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x14,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x15,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x16,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x17,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x18,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x19,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1A,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1B,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1C,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1D,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1E,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x1F,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
pythonrangetest_1:
db 0xBF,0x20,0x30,0x10,0x10,0x10,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x21,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x22,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x23,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x24,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x25,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x26,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x27,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x28,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x29,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2A,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2B,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2C,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2D,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2E,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x2F,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x30,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x31,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x32,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x33,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x34,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x35,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x36,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x37,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x38,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x39,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3A,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3B,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3C,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3D,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3E,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x3F,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
pythonrangetest_2:
db 0xBF,0x40,0x30,0x10,0x10,0x10,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x41,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x42,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x43,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x44,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x45,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x46,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0xBF,0x47,0x30,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
db 0x00,0x00,0x00,0x00,
pythonrangetest_init_state:
db 0x01,0x06,0x02,0x11,0x22,0x44,0x88,0x00,
pythonrangetest:
db BANK(pythonrangetest_0)
dw pythonrangetest_0
db BANK(pythonrangetest_1)
dw pythonrangetest_1
db BANK(pythonrangetest_2)
dw pythonrangetest_2
db $00
dw $0000

222
pythonrangetest.inc Normal file
View File

@ -0,0 +1,222 @@
; s3m2gbt modified by shoofle to output rgbds-compatible asm files
SECTION "pythonrangetest_0", ROMX
pythonrangetest_0:
db $BF,$00,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$01,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$02,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$03,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$04,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$05,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$06,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$07,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$08,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$09,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0F,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$10,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$11,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$12,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$13,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$14,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$15,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$16,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$17,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$18,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$19,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$1F,$30,$00,$00,$00,
db $00,$00,$00,$00,
SECTION "pythonrangetest_1", ROMX
pythonrangetest_1:
db $BF,$20,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$21,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$22,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$23,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$24,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$25,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$26,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$27,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$28,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$29,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$2F,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$30,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$31,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$32,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$33,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$34,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$35,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$36,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$37,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$38,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$39,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3A,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3B,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3C,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3D,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3E,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$3F,$30,$00,$00,$00,
db $00,$00,$00,$00,
SECTION "pythonrangetest_2", ROMX
pythonrangetest_2:
db $BF,$40,$30,$10,$10,$10,
db $00,$00,$00,$00,
db $BF,$41,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$42,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$43,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$44,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$45,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$46,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$47,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
db $00,$00,$00,$00,
pythonrangetest_init_state:
db 0x01,0x06,0x02,0x11,0x22,0x44,0x88,0x00,
SECTION "pythonrangetest", ROMX
pythonrangetest:
db BANK(pythonrangetest_0)
dw pythonrangetest_0
db BANK(pythonrangetest_1)
dw pythonrangetest_1
db BANK(pythonrangetest_2)
dw pythonrangetest_2
db $00
dw $0000

BIN
range_test.s3m Normal file

Binary file not shown.

BIN
rangetest.mod Normal file

Binary file not shown.

BIN
rangetest.s3m Normal file

Binary file not shown.

369
s3m2gbt.py Normal file → Executable file
View File

@ -1,39 +1,11 @@
#!/usr/bin/env python3
# i (shoofle) have heavily modified this script to output a totally different format
# in order to use it with my tarot project. much credit to the original author,
# whose intro title/copyright block is preserved below.
# sourced this from https://github.com/AntonioND/gbt-player/tree/master/gba/s3m2gbt
# in april of 2025
# s3m2gbt v4.4.1 (Part of GBT Player)
#
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2022 Antonio Niño Díaz <antonio_nd@outlook.com>
"""
SAMPLE PERIOD LUT - MOD values
C C# D D# E F F# G G# A A# B
Octave 0:1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907 // C3 to B3
Octave 1: 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453 // C4 to B4
Octave 2: 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226 // C5 to B5
Octave 3: 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 // C6 to B6
Octave 4: 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57 // C7 to B7
Octave 5: 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28 // C8 to B8
//From C3 to B8 | A5 = 1750 = 440.00Hz | C5 = 1546
const UWORD GB_frequencies[] = {
44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986, // C3 to B3
1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517, // C4 to B4
1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783, // C5 to B5
1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915, // C6 to B6
1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982, // C7 to B7
1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015 // C8 to B8
};
"""
class RowConversionError(Exception):
def __init__(self, message, pattern = -1, row = -1, channel = -1):
self.pattern = pattern
@ -313,13 +285,8 @@ def s3m_note_to_gb(note):
# Note cut with ^^
if note == 0xFE:
return 0xFE
if note == 0xFF:
return 0xFF
# Note off and ^^ note cut should be handled before reaching this point
#note = note & 0x7F
if note > 0x7F:
print(note)
assert note <= 0x7F
note -= 32
@ -351,16 +318,15 @@ def s3m_pan_to_gb(pan, channel):
return val
# masks for how to define an effect
EFFECT_PAN = 0x40
EFFECT_ARPEGGIO = 0x50
EFFECT_VIBRATO = 0x60
EFFECT_VOLUME_SLIDE = 0x70
EFFECT_NOTE_CUT = 0x80
EFFECT_PATTERN_JUMP = 0x90
EFFECT_BREAK_SET_STEP = 0xA0
EFFECT_SPEED = 0xB0
EFFECT_EVENT = 0xC0
EFFECT_PAN = 0
EFFECT_ARPEGGIO = 1
EFFECT_NOTE_CUT = 2
EFFECT_VIBRATO = 3
EFFECT_VOLUME_SLIDE = 4
EFFECT_PATTERN_JUMP = 8
EFFECT_BREAK_SET_STEP = 9
EFFECT_SPEED = 10
EFFECT_EVENT = 15
# Returns (converted_num, converted_params) if there was a valid effect. If
# there is none, it returns (None, None). Note that it is needed to pass the
@ -443,31 +409,229 @@ def effect_s3m_to_gb(channel, effectnum, effectparams):
raise RowConversionError(f"Unsupported effect: {effectnum}{effectparams:02X}")
def convert_channel(channel, note_index, samplenum, volume, effectnum, effectparams):
commands = []
HAS_VOLUME = 1 << 4
HAS_INSTRUMENT = 1 << 5
HAS_EFFECT = 1 << 6
HAS_NOTE = 1 << 7
HAS_KIT = 1 << 7
def convert_channel1(note_index, samplenum, volume, effectnum, effectparams):
command = [ 0, 0, 0, 0 ] # NOP
command_ptr = 1
# Check if it's needed to add a note
if note_index != -1 and note_index != 0xFF and note_index != 0xFE:
if note_index != -1:
note_index = s3m_note_to_gb(note_index)
commands.append([0x10 | channel, note_index])
if note_index == 0xFF or note_index == 0xFE:
commands.append([0x80 | channel, note_index])
if volume > -1:
commands.append([0x20 | channel, s3m_volume_to_gb(volume) & 0x0F])
command[0] |= HAS_NOTE
command[command_ptr] = note_index
command_ptr = command_ptr + 1
# Check if there is a sample defined
if samplenum > 0:
instrument = samplenum & 3
commands.append([0x30 | channel, instrument << 6])
command[0] |= HAS_INSTRUMENT
command[command_ptr] = (instrument << 4) & 0x30
if effectnum is not None:
[num, params] = effect_s3m_to_gb(1, effectnum, effectparams)
if num is not None:
commands.append([num | channel, params])
command[0] |= HAS_EFFECT
command[command_ptr] |= num & 0x0F
command_ptr += 1
command[command_ptr] = params & 0xFF
return commands
# Check if it's needed to add a volume
if volume > -1:
command[0] |= HAS_VOLUME
command[0] |= s3m_volume_to_gb(volume) & 0x0F
# Note: The volume bit doesn't affect the final size.
sizes = [ 1, 2, 3, 3, 2, 3, 4, 4 ]
command_size = sizes[command[0] >> 5]
return command[:command_size]
def convert_channel2(note_index, samplenum, volume, effectnum, effectparams):
command = [ 0, 0, 0, 0 ] # NOP
command_ptr = 1
# Check if it's needed to add a note
if note_index != -1:
note_index = s3m_note_to_gb(note_index)
command[0] |= HAS_NOTE
command[command_ptr] = note_index
command_ptr = command_ptr + 1
# Check if there is a sample defined
if samplenum > 0:
instrument = samplenum & 3
command[0] |= HAS_INSTRUMENT
command[command_ptr] = (instrument << 4) & 0x30
if effectnum is not None:
[num, params] = effect_s3m_to_gb(2, effectnum, effectparams)
if num is not None:
command[0] |= HAS_EFFECT
command[command_ptr] |= num & 0x0F
command_ptr += 1
command[command_ptr] = params & 0xFF
# Check if it's needed to add a volume
if volume > -1:
command[0] |= HAS_VOLUME
command[0] |= s3m_volume_to_gb(volume) & 0x0F
# Note: The volume bit doesn't affect the final size.
sizes = [ 1, 2, 3, 3, 2, 3, 4, 4 ]
command_size = sizes[command[0] >> 5]
return command[:command_size]
def convert_channel3(note_index, samplenum, volume, effectnum, effectparams):
command = [ 0, 0, 0, 0 ] # NOP
command_ptr = 1
# Check if it's needed to add a note
if note_index != -1:
note_index = s3m_note_to_gb(note_index)
command[0] |= HAS_NOTE
command[command_ptr] = note_index
command_ptr = command_ptr + 1
# Check if there is a sample defined
if samplenum > 0:
instrument = samplenum & 7
command[0] |= HAS_INSTRUMENT
command[command_ptr] = (instrument << 4) & 0xF0
if effectnum is not None:
[num, params] = effect_s3m_to_gb(3, effectnum, effectparams)
if num is not None:
command[0] |= HAS_EFFECT
command[command_ptr] |= num & 0x0F
command_ptr += 1
command[command_ptr] = params & 0xFF
# Check if it's needed to add a volume
if volume > -1:
command[0] |= HAS_VOLUME
command[0] |= s3m_volume_to_gb_ch3(volume) & 0x0F
# Note: The volume bit doesn't affect the final size.
sizes = [ 1, 2, 3, 3, 2, 3, 4, 4 ]
command_size = sizes[command[0] >> 5]
return command[:command_size]
def convert_channel4(note_index, samplenum, volume, effectnum, effectparams):
command = [ 0, 0, 0, 0 ] # NOP
command_ptr = 1
# Note cut using ^^ as note
if note_index == 0xFE:
if samplenum > 0:
# This limitation is only for channel 4. It should never happen in a
# regular song.
raise("Note cut + Sample in same row: Not supported in channel 4")
samplenum = 0xFE
# Check if there is a sample defined
if samplenum > 0:
if samplenum == 0xFE:
kit = 0xFE;
else:
kit = samplenum & 0xF;
command[0] |= HAS_KIT
command[command_ptr] = kit
command_ptr += 1
if effectnum is not None:
[num, params] = effect_s3m_to_gb(4, effectnum, effectparams)
if num is not None:
command[0] |= HAS_EFFECT
command[command_ptr] |= num & 0x0F
command_ptr += 1
command[command_ptr] = params & 0xFF
# Check if it's needed to add a volume
if volume > -1:
command[0] |= HAS_VOLUME
command[0] |= s3m_volume_to_gb(volume) & 0x0F
# Note: The volume bit doesn't affect the final size.
sizes = [ 1, 2, 3, 3, 2, 3, 4, 4 ]
command_size = sizes[command[0] >> 5]
return command[:command_size]
STARTUP_CMD_DONE = 0
STARTUP_CMD_SPEED = 1
STARTUP_CMD_PANING = 2
STARTUP_CMD_CHANNEL3_INSTRUMENT = 3
SAMPLE_64_ENTRIES = 1 << 7
def initial_state_array(speed, panning_array, instruments):
array = []
# Initial speed
# -------------
array.extend([STARTUP_CMD_SPEED, speed])
# Initial panning
# ---------------
array.extend([STARTUP_CMD_PANING])
array.extend(panning_array)
# Channel 3 instruments
# ---------------------
if instruments is not None:
print("Exporting instruments...")
count = 0
for inst in instruments:
# In the tracker, instruments start at index 1, but they start at
# index 0 in the S3M file.
count += 1
# Only handle instruments assigned to channel 3
if count < 8 or count > 15:
continue
name = inst.sample_name
size = inst.length
if size != 32 and size != 64:
raise S3MFormatError(f"Sample '{name}': Invalid sample length: {size}")
else:
flags = count - 8 # The low bits are the instrument index
if size == 64:
flags |= SAMPLE_64_ENTRIES
array.extend([STARTUP_CMD_CHANNEL3_INSTRUMENT, flags])
# Convert from 8 bit to 4 bit
for i in range(0, size, 2):
sample_hi = inst.sample_data[i + 0] >> 4
sample_lo = inst.sample_data[i + 1] >> 4
value = (sample_hi << 4) | sample_lo
array.extend([value])
# End commands
# ------------
array.extend([STARTUP_CMD_DONE])
return array
def convert_file(module_path, song_name, output_path, export_instruments):
@ -481,7 +645,7 @@ def convert_file(module_path, song_name, output_path, export_instruments):
with open(output_path, "w") as fileout:
fileout.write("; File created by shoofle's s3m2gbt edit\n\n")
fileout.write("; s3m2gbt modified by shoofle to output rgbds-compatible asm files\n")
# Export patterns
# ---------------
@ -498,15 +662,18 @@ def convert_file(module_path, song_name, output_path, export_instruments):
print(f"Pattern {pattern} not exported: Not in the order list")
continue
fileout.write(f"SECTION \"{song_name}_{pattern}\", ROMX\n")
fileout.write(f"{song_name}_{pattern}:\n")
row = 0
commands = [[0x00, 0x00]]
cmd1 = [0]
cmd2 = [0]
cmd3 = [0]
cmd4 = [0]
for c in p.cells:
# If an end of row marker is reached, print the previous row.
# Trust that the S3M file is generated in a valid way and it
# doesn't have markers at weird positions, and that there is one
@ -514,19 +681,21 @@ def convert_file(module_path, song_name, output_path, export_instruments):
if c.empty:
# Write row
fileout.write(" ")
for cmd in commands:
fileout.write(" db ")
cmd = cmd1 + cmd2 + cmd3 + cmd4
for b in cmd:
fileout.write(f"${b:02X},")
fileout.write("\n")
fileout.write("\n")
row = row + 1
commands = [[0x0F, row & 0xFF]]
# Clear commands
cmd1 = [0]
cmd2 = [0]
cmd3 = [0]
cmd4 = [0]
# Next iteration
continue
@ -557,11 +726,19 @@ def convert_file(module_path, song_name, output_path, export_instruments):
channel = c.channel + 1
try:
commands.extend(convert_channel(channel,
note, instrument, volume,
effectnum, effectparams))
if channel > 4:
if channel == 1:
cmd1 = convert_channel1(note, instrument, volume,
effectnum, effectparams)
elif channel == 2:
cmd2 = convert_channel2(note, instrument, volume,
effectnum, effectparams)
elif channel == 3:
cmd3 = convert_channel3(note, instrument, volume,
effectnum, effectparams)
elif channel == 4:
cmd4 = convert_channel4(note, instrument, volume,
effectnum, effectparams)
else:
raise S3MFormatError(f"Too many channels: {channel}")
except RowConversionError as e:
e.row = row
@ -569,27 +746,77 @@ def convert_file(module_path, song_name, output_path, export_instruments):
e.channel = channel
raise e
fileout.write("\n")
fileout.write("\n")
# Export initial state
# --------------------
print(f"Exporting initial state... or not...")
print(f"Exporting initial state...")
fileout.write(f"{song_name}_init_state:\n")
default_pan = [8, 8, 8, 8]
for i in range(0, 4):
default_pan[i] = s3m.channel_pan[i]
gb_default_pan = [
s3m_pan_to_gb(default_pan[0], 1),
s3m_pan_to_gb(default_pan[1], 2),
s3m_pan_to_gb(default_pan[2], 3),
s3m_pan_to_gb(default_pan[3], 4)
]
instr = None
if export_instruments:
instr = s3m.instruments
state_array = initial_state_array(s3m.initial_speed, gb_default_pan, instr)
# Write rows of 8 bytes until the end of the array
while True:
left = len(state_array)
write = []
if left == 0:
break
elif left <= 8:
write = state_array
state_array = []
else:
write = state_array[0:8]
state_array = state_array[8:]
fileout.write(" db ")
for s in write:
fileout.write(f"0x{s:02X},")
fileout.write("\n")
fileout.write("\n")
# Export orders
# -------------
print(f"Exporting orders...")
fileout.write(f"SECTION \"{song_name}\", ROMX\n")
fileout.write(f"{song_name}:\n")
fileout.write(f"\tdb {len(s3m.song_orders)}\n")
#fileout.write(f" {song_name}_init_state,")
#fileout.write("\n")
for o in s3m.song_orders:
pattern = int(o)
if pattern >= s3m.num_patterns:
# TODO: Warn if the pattern goes over the limit?
continue
fileout.write(f"\tdw {song_name}_{pattern}\n")
fileout.write(f" db BANK({song_name}_{pattern})\n")
fileout.write(f" dw {song_name}_{pattern}\n")
fileout.write("\n")
fileout.write(" db $00\n")
fileout.write(" dw $0000\n")
fileout.write("\n")
if __name__ == "__main__":

627
s3m2shoofmt.py Normal file
View File

@ -0,0 +1,627 @@
#!/usr/bin/env python3
# i (shoofle) have heavily modified this script to output a totally different format
# in order to use it with my tarot project. much credit to the original author,
# whose intro title/copyright block is preserved below.
# sourced this from https://github.com/AntonioND/gbt-player/tree/master/gba/s3m2gbt
# in april of 2025
# s3m2gbt v4.4.1 (Part of GBT Player)
#
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2022 Antonio Niño Díaz <antonio_nd@outlook.com>
"""
SAMPLE PERIOD LUT - MOD values
C C# D D# E F F# G G# A A# B
Octave 0:1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907 // C3 to B3
Octave 1: 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453 // C4 to B4
Octave 2: 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226 // C5 to B5
Octave 3: 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 // C6 to B6
Octave 4: 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57 // C7 to B7
Octave 5: 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28 // C8 to B8
//From C3 to B8 | A5 = 1750 = 440.00Hz | C5 = 1546
const UWORD GB_frequencies[] = {
44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986, // C3 to B3
1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517, // C4 to B4
1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783, // C5 to B5
1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915, // C6 to B6
1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982, // C7 to B7
1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015 // C8 to B8
};
"""
class RowConversionError(Exception):
def __init__(self, message, pattern = -1, row = -1, channel = -1):
self.pattern = pattern
self.row = row
self.channel = channel + 1
self.message = message
def __str__(self):
return f"Pattern {self.pattern} | Row {self.row} | Channel {self.channel} | {self.message}"
class S3MFormatError(Exception):
pass
class S3MFormatReader:
def read_u8(self):
offset = self.read_ptr
self.read_ptr += 1
return int(self.data[offset])
def read_u16(self):
offset = self.read_ptr
self.read_ptr += 2
return int((self.data[offset + 1] << 8) | self.data[offset])
def read_memseg(self):
offset = self.read_ptr
self.read_ptr += 3
part1 = self.data[offset + 0]
part2 = self.data[offset + 1]
part3 = self.data[offset + 2]
return int((part1 << 16) | (part3 << 8) | part2)
def read_string(self, size):
offset = self.read_ptr
self.read_ptr += size
return self.data[offset:offset+size]
class S3MFileInstrument(S3MFormatReader):
def __init__(self, data, offset):
self.data = data
self.read_ptr = offset
instrument_type = self.read_u8()
if instrument_type != 1:
self.exists = False
return
self.exists = True
self.dos_filename = self.read_string(12).decode("utf-8")
self.sample_data_offset = self.read_memseg() * 16
self.length = self.read_u16()
self.length |= self.read_u16() << 16
self.read_ptr += 4 + 4 # Skip loop begin and loop end
self.default_volume = self.read_u8()
self.read_ptr = offset + 0x30
self.sample_name = self.read_string(28).decode("utf-8")
if self.read_string(4) != b'SCRS':
raise S3MFormatError("Invalid magic string in instrument")
start = self.sample_data_offset
end = start + self.length
self.sample_data = self.data[start:end]
class S3MFilePatternCell():
def __init__(self, header, channel, note, instrument, volume,
effect, effect_args):
if header == 0:
self.empty = True
return
self.empty = False
self.channel = channel
if (note != None) or (instrument != None):
self.has_note_and_instrument = True
self.note = note
self.instrument = instrument
else:
self.has_note_and_instrument = False
if volume != None:
self.has_volume = True
self.volume = volume
else:
self.has_volume = False
if (effect != None) or (effect_args != None):
self.has_effect = True
self.effect = effect
self.effect_args = effect_args
else:
self.has_effect = False
class S3MFilePattern(S3MFormatReader):
def __init__(self, data, offset):
# Check if we have asked to generate an empty pattern
if data == None:
cell = S3MFilePatternCell(0, 0, 0, 0, 0, 0, 0)
self.cells = []
for i in range(0, 64):
self.cells.append(cell)
return
self.data = data
self.read_ptr = offset
length = self.read_u16() - 2
self.cells = []
while length > 0:
header = self.read_u8()
length -= 1
channel = header & 31
note = None
instrument = None
volume = None
effect = None
effect_args = None
if (header & (1 << 5)) != 0: # Has note and instrument
note = self.read_u8()
instrument = self.read_u8()
length -= 2
if (header & (1 << 6)) != 0: # Has volume
volume = self.read_u8()
length -= 1
if (header & (1 << 7)) != 0: # Has effect
effect = self.read_u8()
effect_args = self.read_u8()
length -= 2
cell = S3MFilePatternCell(header, channel, note, instrument, volume,
effect, effect_args)
self.cells.append(cell)
class S3MFile(S3MFormatReader):
def __init__(self, data):
# Save data for now
self.data = data
self.read_ptr = 0
self.name = self.read_string(28).decode("utf-8")
print(f"Song Name: '{self.name}'")
self.read_ptr += 1 + 1 + 2 # Ignore fields
self.song_length = self.read_u16()
print(f"Song Length: {self.song_length}")
self.num_instruments = self.read_u16()
self.num_patterns = self.read_u16()
self.read_ptr += 6 # Ignore fields
if self.read_string(4) != b'SCRM':
raise S3MFormatError("Invalid magic string in file")
self.read_ptr += 1 # Ignore global volume
self.initial_speed = self.read_u8()
if self.read_u8() != 150:
raise S3MFormatError("Invalid tempo: It must be 150")
self.read_ptr += 2 # Ignore master volume and ultraclick removal
# Save this for later
has_custom_pan = False
if self.read_u8() == 252:
has_custom_pan = True
self.read_ptr = 0x40
channel_settings = self.read_string(4)
if channel_settings[0] >= 16 or channel_settings[1] >= 16 or \
channel_settings[2] >= 16 or channel_settings[3] >= 16:
raise S3MFormatError("Invalid channel settings: Channels 0-3 must be enabled")
# Read orders
self.read_ptr = 0x60
self.song_orders = self.read_string(self.song_length)
if self.song_length % 2 == 1:
self.read_ptr += 1 # Align to 2
# Read instrument parapointers
self.instrument_offsets = [None] * self.num_instruments
for i in range(0, self.num_instruments):
self.instrument_offsets[i] = self.read_u16() * 16
# Read pattern parapointers
self.pattern_offsets = [None] * self.num_patterns
for i in range(0, self.num_patterns):
self.pattern_offsets[i] = self.read_u16() * 16
# Read default panning
if has_custom_pan:
self.channel_pan = [b & 0xF for b in self.read_string(4)]
else:
self.channel_pan = [8, 8, 8, 8]
# Load instruments
self.instruments = [None] * self.num_instruments
for i in range(0, len(self.instrument_offsets)):
offset = self.instrument_offsets[i]
if offset != 0:
instr = S3MFileInstrument(self.data, offset)
if instr.exists:
self.instruments[i] = instr
# Load patterns
self.patterns = [None] * self.num_patterns
for i in range(0, len(self.pattern_offsets)):
offset = self.pattern_offsets[i]
if offset != 0:
self.patterns[i] = S3MFilePattern(self.data, offset)
else:
# A NULL pointer means that the pattern is empty
self.patterns[i] = S3MFilePattern(None, 0)
# The file data is no longer needed
self.data = []
# Channels 1, 2, 4
def s3m_volume_to_gb(s3m_vol):
if s3m_vol >= 64:
return 15
else:
return s3m_vol >> 2;
# Channel 3
def s3m_volume_to_gb_ch3(s3m_vol):
vol = s3m_volume_to_gb(s3m_vol)
if vol >= 0 and vol <= 3:
return 0 # 0%
elif vol >= 4 and vol <= 6:
return 3 # 25%
elif vol >= 7 and vol <= 9:
return 2 # 50%
elif vol >= 10 and vol <= 12:
return 4 # 75%
elif vol >= 13 and vol <= 15:
return 1 # 100%
else:
return 0
def s3m_note_to_gb(note):
# Note cut with ^^
if note == 0xFE:
return 0xFE
if note == 0xFF:
return 0xFF
# Note off and ^^ note cut should be handled before reaching this point
#note = note & 0x7F
if note > 0x7F:
print(note)
assert note <= 0x7F
note -= 32
if note < 0:
raise RowConversionError("Note too low")
elif note > 32 + 16 * 6:
raise RowConversionError("Note too high")
note = (note & 0xF) + ((note & 0xF0) >> 4) * 12
return note
def s3m_pan_to_gb(pan, channel):
left = False
right = False
if pan >= 0 and pan <= 3:
left = True
elif pan >= 4 and pan <= 11:
left = True
right = True
elif pan >= 12 and pan <= 15:
right = True
val = 0
if left:
val |= 1 << (3 + channel)
if right:
val |= 1 << (channel - 1)
return val
# masks for how to define an effect
EFFECT_PAN = 0x40
EFFECT_ARPEGGIO = 0x50
EFFECT_VIBRATO = 0x60
EFFECT_VOLUME_SLIDE = 0x70
EFFECT_NOTE_CUT = 0x80
EFFECT_PATTERN_JUMP = 0x90
EFFECT_BREAK_SET_STEP = 0xA0
EFFECT_SPEED = 0xB0
EFFECT_EVENT = 0xC0
# Returns (converted_num, converted_params) if there was a valid effect. If
# there is none, it returns (None, None). Note that it is needed to pass the
# channel to this function because some effects behave differently depending on
# the channel (like panning).
def effect_s3m_to_gb(channel, effectnum, effectparams):
if effectnum == 'A': # Set Speed
if effectparams == 0:
raise RowConversionError("Speed must not be zero")
return (EFFECT_SPEED, effectparams)
if effectnum == 'B': # Pattern jump
# TODO: Fail if this jumps out of bounds
return (EFFECT_PATTERN_JUMP, effectparams)
elif effectnum == 'C': # Break + Set row
# Effect value is BCD, convert to integer
val = (((effectparams & 0xF0) >> 4) * 10) + (effectparams & 0x0F)
return (EFFECT_BREAK_SET_STEP, val)
elif effectnum == 'D': # Volume Slide
if channel == 3:
raise RowConversionError("Volume slide not supported in channel 3")
if effectparams == 0:
# Ignore volume slide commands that just continue the effect,
# they are only needed for the S3M player.
return (None, None)
upper = (effectparams >> 4) & 0xF
lower = effectparams & 0xF
if upper == 0xF or lower == 0xF:
raise RowConversionError("Fine volume slide not supported")
elif lower == 0: # Volume goes up
params = 1 << 3 # Increase
delay = 7 - upper + 1
if delay <= 0:
raise RowConversionError("Volume slide too steep")
params |= delay
return (EFFECT_VOLUME_SLIDE, params)
elif upper == 0: # Volume goes down
params = 0 << 3 # Decrease
delay = 7 - lower + 1
if delay <= 0:
raise RowConversionError("Volume slide too steep")
params = delay
return (EFFECT_VOLUME_SLIDE, params)
else:
raise RowConversionError("Invalid volume slide arguments")
return (EFFECT_VOLUME_SLIDE, effectparams)
elif effectnum == 'H': # Vibrato
return (EFFECT_VIBRATO, effectparams)
elif effectnum == 'J': # Arpeggio
return (EFFECT_ARPEGGIO, effectparams)
elif effectnum == 'S': # This effect is subdivided into many
subeffectnum = (effectparams & 0xF0) >> 4
subeffectparams = effectparams & 0x0F
if subeffectnum == 0x8: # Pan position
val = s3m_pan_to_gb(subeffectparams, channel)
return (EFFECT_PAN, val)
elif subeffectnum == 0xC: # Notecut
return (EFFECT_NOTE_CUT, subeffectparams)
elif subeffectnum == 0xF: # Funkrepeat? Set active macro?
# This effect is either unused, or it's the "set active macro"
# command, which doesn't have any effect if you don't use the macro
# afterwards. It can safely be overloaded for event callbacks.
return (EFFECT_EVENT, subeffectparams)
raise RowConversionError(f"Unsupported effect: {effectnum}{effectparams:02X}")
def convert_channel(channel, note_index, samplenum, volume, effectnum, effectparams):
commands = []
# Check if it's needed to add a note
if note_index != -1 and note_index != 0xFF and note_index != 0xFE:
note_index = s3m_note_to_gb(note_index)
commands.append([0x10 | channel, note_index])
if note_index == 0xFF or note_index == 0xFE:
commands.append([0x80 | channel, note_index])
if volume > -1:
commands.append([0x20 | channel, s3m_volume_to_gb(volume) & 0x0F])
# Check if there is a sample defined
if samplenum > 0:
instrument = samplenum & 3
commands.append([0x30 | channel, instrument << 6])
if effectnum is not None:
[num, params] = effect_s3m_to_gb(1, effectnum, effectparams)
if num is not None:
commands.append([num | channel, params])
return commands
def convert_file(module_path, song_name, output_path, export_instruments):
with open(module_path, "rb") as file:
file_byte_array = bytearray(file.read())
s3m = S3MFile(file_byte_array)
if output_path == None:
output_path = song_name + ".inc"
with open(output_path, "w") as fileout:
fileout.write("; File created by shoofle's s3m2gbt edit\n\n")
# Export patterns
# ---------------
print(f"Exporting patterns...")
pattern = -1
for p in s3m.patterns:
pattern += 1
# Check if pattern is actually used in the order list. If it isn't
# used, don't export it.
if pattern not in s3m.song_orders:
print(f"Pattern {pattern} not exported: Not in the order list")
continue
fileout.write(f"{song_name}_{pattern}:\n")
row = 0
commands = [[0x00, 0x00]]
for c in p.cells:
# If an end of row marker is reached, print the previous row.
# Trust that the S3M file is generated in a valid way and it
# doesn't have markers at weird positions, and that there is one
# marker right at the end of each pattern.
if c.empty:
# Write row
fileout.write(" ")
for cmd in commands:
fileout.write("db ")
for b in cmd:
fileout.write(f"${b:02X}, ")
fileout.write("\n")
fileout.write("\n")
row = row + 1
commands = [[0x0F, row & 0xFF]]
# Next iteration
continue
volume = -1
if c.has_volume:
volume = c.volume
note = -1
instrument = 0
if c.has_note_and_instrument:
note = c.note
instrument = c.instrument
# Rows with note and instrument but no volume use the
# default volume of the sample.
if instrument > 0 and volume == -1:
this_instr = s3m.instruments[instrument - 1]
volume = this_instr.default_volume
effectnum = None
effectparams = None
if c.has_effect:
# Convert type to ASCII to match the documentation
effectnum = chr(c.effect + ord('A') - 1)
effectparams = c.effect_args
channel = c.channel + 1
try:
commands.extend(convert_channel(channel,
note, instrument, volume,
effectnum, effectparams))
if channel > 4:
raise S3MFormatError(f"Too many channels: {channel}")
except RowConversionError as e:
e.row = row
e.pattern = pattern
e.channel = channel
raise e
# Export initial state
# --------------------
print(f"Exporting initial state... or not...")
# Export orders
# -------------
print(f"Exporting orders...")
fileout.write(f"{song_name}:\n")
fileout.write(f"\tdb {len(s3m.song_orders)}\n")
for o in s3m.song_orders:
pattern = int(o)
if pattern >= s3m.num_patterns:
# TODO: Warn if the pattern goes over the limit?
continue
fileout.write(f"\tdw {song_name}_{pattern}\n")
if __name__ == "__main__":
import argparse
import sys
print("s3m2gbt v4.4.1 (part of GBT Player)")
print("Copyright (c) 2022 Antonio Niño Díaz <antonio_nd@outlook.com>")
print("All rights reserved")
print("")
parser = argparse.ArgumentParser(description='Convert S3M files into GBT format.')
parser.add_argument("--input", default=None, required=True,
help="input file")
parser.add_argument("--name", default=None, required=True,
help="output song name")
parser.add_argument("--output", default=None, required=False,
help="output file")
parser.add_argument("--instruments", default=False, required=False,
action='store_true', help="export channel 3 instruments")
args = parser.parse_args()
try:
convert_file(args.input, args.name, args.output, args.instruments)
except RowConversionError as e:
print("ERROR: " + str(e))
sys.exit(1)
except S3MFormatError as e:
print("ERROR: Invalid S3M file: " + str(e))
sys.exit(1)
print("Done!")
sys.exit(0)

Binary file not shown.