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 ld hl, MY_OAM
; go through MY_OAM and, for any which aren't still visible, set their ; go through MY_OAM and, for any which aren't still visible, set their
; y value to zero. ; 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 .cleanUpLoop
ld a, [hl] ; a is the y value and hl points to y ld a, [hl] ; a is the y value and hl points to y
: cp a, (2+1+5)*8 : cp a, (2+1+5)*8

View File

@ -1,15 +1,13 @@
; async variables somewhere in RAM ; async variables somewhere in RAM
def vAsyncAF equ ASYNC_VARS_START PUSHS "Async Variables", WRAM0[ASYNC_VARS_START]
def vAsyncHL EQU vAsyncAF+2 vAsyncAF: dw
def vAsyncDE EQU vAsyncHL+2 vAsyncHL: dw
def vAsyncBC equ vAsyncDE+2 vAsyncDE: dw
def vAsyncPC equ vAsyncBC+2 vAsyncBC: dw
def vAsyncNext equ vAsyncPC+2 vAsyncPC: dw
def vAsyncAfter equ vAsyncNext+2 vAsyncMainSP: dw
def vAsyncProgress equ vAsyncAfter+2 vAsyncThreadSP: dw
def vAsyncMainSP equ vAsyncProgress+2 POPS
def vAsyncThreadSP equ vAsyncMainSP+2
; canonical ordering to push should be: AF, BC, DE, HL, ; canonical ordering to push should be: AF, BC, DE, HL,
def ASYNC_STACK_TOP equ $ffc0 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 ; 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 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_START EQU 148
def SAFE_ASYNC_END EQU 153 def SAFE_ASYNC_END EQU 153

861
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: SoundSetup:
ld a, LOW(gbtarottheme) ld a, BANK(gbt_play)
ld [ordersStart], a ld [rROMB0], a
ld a, HIGH(gbtarottheme)
ld [ordersStart+1], a
ld a, 0
ld [currentOrderNumber], a
ld [currentRowNumber], a
ld [currentTickNumber], a
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 de,pythonrangetest
ld bc,BANK(instr_test_data)
ld a,$05
call gbt_play ; Play song
ld a, 1
call gbt_loop
ld a, [cvCardBank]
ld [rROMB0], a
ret ret
SoundUpdate: SoundUpdate:
ld a, [speed] ld a, BANK(gbt_play)
ld b, a ld [rROMB0], a
ld a, [currentTickNumber]
inc a
call ArrayClampLoopingB
ld [currentTickNumber], a
cp a, 0 call gbt_update
jp nz, .doneAllPackets ; if we're not zero, then just update the things
ld b, 64 ld a, [cvCardBank]
ld a, [currentRowNumber] ld [rROMB0], a
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
ret ret
; File created by mod2gbt
Channel_1_Update: ; s3m2gbt modified by shoofle to output rgbds-compatible asm files
ld a, [channel_1_duty] SECTION "pythonrangetest_0", ROMX
or a, $0a pythonrangetest_0:
ld [rNR11], a db $BF,$00,$30,$10,$10,$10,
db $00,$00,$00,$00,
ld a, [channel_1_volume] db $BF,$01,$30,$00,$00,$00,
sla a db $00,$00,$00,$00,
sla a db $BF,$02,$30,$00,$00,$00,
sla a db $00,$00,$00,$00,
sla a db $BF,$03,$30,$00,$00,$00,
and a, $F0 db $00,$00,$00,$00,
ld [rNR12], a db $BF,$04,$30,$00,$00,$00,
db $00,$00,$00,$00,
ld a, [channel_1_note] db $BF,$05,$30,$00,$00,$00,
ld hl, note_periods db $00,$00,$00,$00,
ld b, 0 db $BF,$06,$30,$00,$00,$00,
ld c, a db $00,$00,$00,$00,
add hl, bc db $BF,$07,$30,$00,$00,$00,
add hl, bc ; double width values db $00,$00,$00,$00,
db $BF,$08,$30,$00,$00,$00,
ld a, [hl+] db $00,$00,$00,$00,
ld [rNR13], a db $BF,$09,$30,$00,$00,$00,
db $00,$00,$00,$00,
db $BF,$0A,$30,$00,$00,$00,
ld a, [channel_1_trigger] db $00,$00,$00,$00,
or a, [hl] db $BF,$0B,$30,$00,$00,$00,
or a, AUDHIGH_LENGTH_OFF db $00,$00,$00,$00,
ld [rNR14], a db $BF,$0C,$30,$00,$00,$00,
db $00,$00,$00,$00,
ld a, 0 db $BF,$0D,$30,$00,$00,$00,
ld [channel_1_trigger], a db $00,$00,$00,$00,
ret db $BF,$0E,$30,$00,$00,$00,
db $00,$00,$00,$00,
Channel_2_Update: db $BF,$0F,$30,$00,$00,$00,
Channel_3_Update: db $00,$00,$00,$00,
Channel_4_Update: db $BF,$10,$30,$00,$00,$00,
ret db $00,$00,$00,$00,
db $BF,$11,$30,$00,$00,$00,
note_periods: db $00,$00,$00,$00,
dw 44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986, ; C3 to B3 db $BF,$12,$30,$00,$00,$00,
dw 1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517, ; C4 to B4 db $00,$00,$00,$00,
dw 1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783, ; C5 to B5 db $BF,$13,$30,$00,$00,$00,
dw 1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915, ; C6 to B6 db $00,$00,$00,$00,
dw 1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982, ; C7 to B7 db $BF,$14,$30,$00,$00,$00,
dw 1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015 ; C8 to B8 db $00,$00,$00,$00,
db $BF,$15,$30,$00,$00,$00,
INCLUDE "theme.inc" 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

View File

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

View File

@ -101,8 +101,7 @@ RandomUnderD: ; takes d as limit (inclusive)
ret z ret z
ret c ret c
jr .inOne jr .inOne
SwapCards: ; takes hl as array, c and e as indices to swap (uses b and d) SwapCards: ; takes hl as array, c and e as indices to swap (uses b and d)
inc hl ; first element of array inc hl ; first element of array
push hl ; save it for later 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 add hl, bc
ld [hl], d ; put old [hl+0e] in [hl+0c] ld [hl], d ; put old [hl+0e] in [hl+0c]
ret ret

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
DEF vPreviousSpreadIndex EQU SCREEN_VARS_START PUSHS UNION "Screen Variables", WRAM0[SCREEN_VARS_START]
def vPreviousSpreadCard equ vPreviousSpreadIndex + 1 vPreviousSpreadIndex: db ; EQU SCREEN_VARS_START
vPreviousSpreadCard: db ; equ vPreviousSpreadIndex + 1
POPS
ScreenSpreadSelect: ScreenSpreadSelect:
dw SpreadSelectSetup 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 def ONES equ $D200
; allocating $8 spaces for system variables, currently only using $4 bytes ; allocating $8 spaces for system variables, currently only using $4 bytes
DEF rMYBTN EQU SYSTEM_VARS_START PUSHS "System Variables", WRAM0[SYSTEM_VARS_START]
DEF rMYBTNP EQU rMYBTN + 1 rMYBTN: db ; EQU SYSTEM_VARS_START
DEF rDELTAT EQU rMYBTNP + 1 ; delta_t where $1000 = 1 second rMYBTNP: db ;EQU rMYBTN + 1
def rLFSR equ rDELTAT + 1 ; 16 bit 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 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 "Async.inc"
INCLUDE "Audio.inc" INCLUDE "Audio.inc"
INCLUDE "Random.inc" INCLUDE "Random.inc"
INCLUDE "CopyRangeSafe.inc" INCLUDE "CopyRange.inc"
INCLUDE "CopyTilesSafe.inc" INCLUDE "CopyTiles.inc"
INCLUDE "ScreenMainMenu.inc" INCLUDE "ScreenMainMenu.inc"
INCLUDE "ScreenSpreadSelect.inc" INCLUDE "ScreenSpreadSelect.inc"
INCLUDE "CardHelpers.inc" INCLUDE "CardHelpers.inc"
@ -569,3 +573,4 @@ INCLUDE "ScreenShuffle.inc"
INCLUDE "CardLibrary.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.

375
s3m2gbt.py Normal file → Executable file
View File

@ -1,39 +1,11 @@
#!/usr/bin/env python3 #!/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) # s3m2gbt v4.4.1 (Part of GBT Player)
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# Copyright (c) 2022 Antonio Niño Díaz <antonio_nd@outlook.com> # 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): class RowConversionError(Exception):
def __init__(self, message, pattern = -1, row = -1, channel = -1): def __init__(self, message, pattern = -1, row = -1, channel = -1):
self.pattern = pattern self.pattern = pattern
@ -313,13 +285,8 @@ def s3m_note_to_gb(note):
# Note cut with ^^ # Note cut with ^^
if note == 0xFE: if note == 0xFE:
return 0xFE return 0xFE
if note == 0xFF:
return 0xFF
# Note off and ^^ note cut should be handled before reaching this point # Note off and ^^ note cut should be handled before reaching this point
#note = note & 0x7F
if note > 0x7F:
print(note)
assert note <= 0x7F assert note <= 0x7F
note -= 32 note -= 32
@ -351,16 +318,15 @@ def s3m_pan_to_gb(pan, channel):
return val return val
# masks for how to define an effect EFFECT_PAN = 0
EFFECT_PAN = 0x40 EFFECT_ARPEGGIO = 1
EFFECT_ARPEGGIO = 0x50 EFFECT_NOTE_CUT = 2
EFFECT_VIBRATO = 0x60 EFFECT_VIBRATO = 3
EFFECT_VOLUME_SLIDE = 0x70 EFFECT_VOLUME_SLIDE = 4
EFFECT_NOTE_CUT = 0x80 EFFECT_PATTERN_JUMP = 8
EFFECT_PATTERN_JUMP = 0x90 EFFECT_BREAK_SET_STEP = 9
EFFECT_BREAK_SET_STEP = 0xA0 EFFECT_SPEED = 10
EFFECT_SPEED = 0xB0 EFFECT_EVENT = 15
EFFECT_EVENT = 0xC0
# Returns (converted_num, converted_params) if there was a valid effect. If # 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 # 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}") raise RowConversionError(f"Unsupported effect: {effectnum}{effectparams:02X}")
def convert_channel(channel, note_index, samplenum, volume, effectnum, effectparams): HAS_VOLUME = 1 << 4
commands = [] 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 # 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) note_index = s3m_note_to_gb(note_index)
commands.append([0x10 | channel, note_index]) command[0] |= HAS_NOTE
if note_index == 0xFF or note_index == 0xFE: command[command_ptr] = note_index
commands.append([0x80 | channel, note_index]) command_ptr = command_ptr + 1
if volume > -1:
commands.append([0x20 | channel, s3m_volume_to_gb(volume) & 0x0F])
# Check if there is a sample defined # Check if there is a sample defined
if samplenum > 0: if samplenum > 0:
instrument = samplenum & 3 instrument = samplenum & 3
commands.append([0x30 | channel, instrument << 6])
command[0] |= HAS_INSTRUMENT
command[command_ptr] = (instrument << 4) & 0x30
if effectnum is not None: if effectnum is not None:
[num, params] = effect_s3m_to_gb(1, effectnum, effectparams) [num, params] = effect_s3m_to_gb(1, effectnum, effectparams)
if num is not None: 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): 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: 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 # 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") print(f"Pattern {pattern} not exported: Not in the order list")
continue continue
fileout.write(f"SECTION \"{song_name}_{pattern}\", ROMX\n")
fileout.write(f"{song_name}_{pattern}:\n") fileout.write(f"{song_name}_{pattern}:\n")
row = 0 row = 0
commands = [[0x00, 0x00]] cmd1 = [0]
cmd2 = [0]
cmd3 = [0]
cmd4 = [0]
for c in p.cells: for c in p.cells:
# If an end of row marker is reached, print the previous row. # 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 # 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 # 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: if c.empty:
# Write row # Write row
fileout.write(" ") fileout.write(" db ")
for cmd in commands: cmd = cmd1 + cmd2 + cmd3 + cmd4
fileout.write("db ") for b in cmd:
for b in cmd: fileout.write(f"${b:02X},")
fileout.write(f"${b:02X}, ")
fileout.write("\n")
fileout.write("\n") fileout.write("\n")
row = row + 1 row = row + 1
commands = [[0x0F, row & 0xFF]] # Clear commands
cmd1 = [0]
cmd2 = [0]
cmd3 = [0]
cmd4 = [0]
# Next iteration # Next iteration
continue continue
@ -557,11 +726,19 @@ def convert_file(module_path, song_name, output_path, export_instruments):
channel = c.channel + 1 channel = c.channel + 1
try: try:
commands.extend(convert_channel(channel, if channel == 1:
note, instrument, volume, cmd1 = convert_channel1(note, instrument, volume,
effectnum, effectparams)) effectnum, effectparams)
elif channel == 2:
if channel > 4: 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}") raise S3MFormatError(f"Too many channels: {channel}")
except RowConversionError as e: except RowConversionError as e:
e.row = row e.row = row
@ -569,27 +746,77 @@ def convert_file(module_path, song_name, output_path, export_instruments):
e.channel = channel e.channel = channel
raise e raise e
fileout.write("\n")
fileout.write("\n")
# Export initial state # 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 # Export orders
# ------------- # -------------
print(f"Exporting orders...") print(f"Exporting orders...")
fileout.write(f"SECTION \"{song_name}\", ROMX\n")
fileout.write(f"{song_name}:\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: for o in s3m.song_orders:
pattern = int(o) pattern = int(o)
if pattern >= s3m.num_patterns: if pattern >= s3m.num_patterns:
# TODO: Warn if the pattern goes over the limit? # TODO: Warn if the pattern goes over the limit?
continue 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__": 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.