attempt to write my own audio engine. didnt work well.
This commit is contained in:
parent
e44d7e492b
commit
35bb4dd60b
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.s3m~
|
||||||
|
347
Audio.inc
Normal file
347
Audio.inc
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
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, 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
|
||||||
|
|
||||||
|
ret
|
||||||
|
SoundUpdate:
|
||||||
|
ld a, [speed]
|
||||||
|
ld b, a
|
||||||
|
ld a, [currentTickNumber]
|
||||||
|
inc a
|
||||||
|
call ArrayClampLoopingB
|
||||||
|
ld [currentTickNumber], a
|
||||||
|
|
||||||
|
cp a, 0
|
||||||
|
jp nz, .doneAllPackets ; if we're not zero, then just update the things
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
|||||||
;def vBlocked equ vPreviousCardIndex + 1
|
;def vBlocked equ vPreviousCardIndex + 1
|
||||||
def vAnimationFrame EQU SCREEN_VARS_START
|
def vAnimationFrame EQU SCREEN_VARS_START
|
||||||
def vState EQU vAnimationFrame+1
|
def vState EQU vAnimationFrame+1
|
||||||
println "vState is ", vState
|
|
||||||
def vCurrentAnimation EQU vState+1 ; 2 bytes
|
def vCurrentAnimation EQU vState+1 ; 2 bytes
|
||||||
def vShuffleIndex equ vCurrentAnimation+2
|
def vShuffleIndex equ vCurrentAnimation+2
|
||||||
def vShuffleTime equ vShuffleIndex+1 ; 2 bytes
|
def vShuffleTime equ vShuffleIndex+1 ; 2 bytes
|
||||||
@ -40,11 +39,6 @@ ShuffleSetup:
|
|||||||
ld a, S_Center
|
ld a, S_Center
|
||||||
ld [vState], a
|
ld [vState], a
|
||||||
|
|
||||||
ld a, LOW(ShuffleAnimationRight)
|
|
||||||
ld [vCurrentAnimation], a
|
|
||||||
ld a, HIGH(ShuffleAnimationRight)
|
|
||||||
ld [vCurrentAnimation+1], a
|
|
||||||
|
|
||||||
ld hl, .asyncTask
|
ld hl, .asyncTask
|
||||||
call Async_Spawn_HL
|
call Async_Spawn_HL
|
||||||
|
|
||||||
@ -60,84 +54,14 @@ ShuffleSetup:
|
|||||||
ld c, 20 ; width
|
ld c, 20 ; width
|
||||||
call CopyTilesToMap
|
call CopyTilesToMap
|
||||||
|
|
||||||
; manually drawing the Big Card
|
ld hl, Shuffle.BigCard
|
||||||
.drawBigCard
|
ld de, _SCRN0 + 32*5 + 8
|
||||||
ld hl, _SCRN0 + 32*5 + 8
|
ld b, 8
|
||||||
ld a, VARIABLE_TILES_START
|
ld c, 4
|
||||||
ld [hl+], a
|
call CopyTilesToMap
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld b, 0
|
|
||||||
ld c, 32 - 4
|
|
||||||
add hl, bc
|
|
||||||
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
sub a, 2
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
sub a, 2
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
sub a, 2
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
sub a, 2
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
sub a, 2
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
add hl, bc
|
|
||||||
inc a
|
|
||||||
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
ld [hl+], a
|
|
||||||
inc a
|
|
||||||
ld [hl+], a
|
|
||||||
|
|
||||||
ld hl, Shuffle.UITileData
|
ld hl, Shuffle.UITileData
|
||||||
ld de, $9000 + VARIABLE_TILES_START*16
|
ld de, _VRAM + $1000 + VARIABLE_TILES_START*16
|
||||||
ld bc, Shuffle.UITileDataEnd - Shuffle.UITileData
|
ld bc, Shuffle.UITileDataEnd - Shuffle.UITileData
|
||||||
call CopyRange
|
call CopyRange
|
||||||
|
|
||||||
@ -554,3 +478,14 @@ Shuffle.UITileData:
|
|||||||
db $aa,$55,$55,$aa,$aa,$55,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff,$00,$ff ; bottom-middle
|
db $aa,$55,$55,$aa,$aa,$55,$ff,$ff,$00,$ff,$ff,$ff,$ff,$ff,$00,$ff ; bottom-middle
|
||||||
db $b6,$5f,$56,$bf,$b6,$5f,$f6,$ff,$06,$ff,$fe,$ff,$fe,$ff,$00,$ff ; bottom-right
|
db $b6,$5f,$56,$bf,$b6,$5f,$f6,$ff,$06,$ff,$fe,$ff,$fe,$ff,$00,$ff ; bottom-right
|
||||||
Shuffle.UITileDataEnd:
|
Shuffle.UITileDataEnd:
|
||||||
|
|
||||||
|
Shuffle.BigCard:
|
||||||
|
def VTS = VARIABLE_TILES_START
|
||||||
|
db VTS, VTS+1, VTS+1, VTS+2
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+3, VTS+4, VTS+4, VTS+5
|
||||||
|
db VTS+6, VTS+7, VTS+7, VTS+8
|
BIN
gb_tarot_theme.s3m
Normal file
BIN
gb_tarot_theme.s3m
Normal file
Binary file not shown.
203
gbtarottheme.inc
Normal file
203
gbtarottheme.inc
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
; File created by shoofle's s3m2gbt edit
|
||||||
|
|
||||||
|
gbtarottheme_0:
|
||||||
|
db $00, $00,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
db $22, $00,
|
||||||
|
db $23, $00,
|
||||||
|
db $24, $00,
|
||||||
|
|
||||||
|
db $0F, $01,
|
||||||
|
|
||||||
|
db $0F, $02,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $03,
|
||||||
|
|
||||||
|
db $0F, $04,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $05,
|
||||||
|
|
||||||
|
db $0F, $06,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $07,
|
||||||
|
|
||||||
|
db $0F, $08,
|
||||||
|
|
||||||
|
db $0F, $09,
|
||||||
|
|
||||||
|
db $0F, $0A,
|
||||||
|
|
||||||
|
db $0F, $0B,
|
||||||
|
|
||||||
|
db $0F, $0C,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $0D,
|
||||||
|
|
||||||
|
db $0F, $0E,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $0F,
|
||||||
|
|
||||||
|
db $0F, $10,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $11,
|
||||||
|
|
||||||
|
db $0F, $12,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $13,
|
||||||
|
|
||||||
|
db $0F, $14,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $15,
|
||||||
|
|
||||||
|
db $0F, $16,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $17,
|
||||||
|
|
||||||
|
db $0F, $18,
|
||||||
|
|
||||||
|
db $0F, $19,
|
||||||
|
|
||||||
|
db $0F, $1A,
|
||||||
|
|
||||||
|
db $0F, $1B,
|
||||||
|
|
||||||
|
db $0F, $1C,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $1D,
|
||||||
|
|
||||||
|
db $0F, $1E,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $1F,
|
||||||
|
|
||||||
|
db $0F, $20,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $21,
|
||||||
|
|
||||||
|
db $0F, $22,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $23,
|
||||||
|
|
||||||
|
db $0F, $24,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $25,
|
||||||
|
|
||||||
|
db $0F, $26,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $27,
|
||||||
|
|
||||||
|
db $0F, $28,
|
||||||
|
|
||||||
|
db $0F, $29,
|
||||||
|
|
||||||
|
db $0F, $2A,
|
||||||
|
|
||||||
|
db $0F, $2B,
|
||||||
|
|
||||||
|
db $0F, $2C,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $2D,
|
||||||
|
|
||||||
|
db $0F, $2E,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $2F,
|
||||||
|
|
||||||
|
db $0F, $30,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $31,
|
||||||
|
|
||||||
|
db $0F, $32,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $33,
|
||||||
|
|
||||||
|
db $0F, $34,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $35,
|
||||||
|
|
||||||
|
db $0F, $36,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $37,
|
||||||
|
|
||||||
|
db $0F, $38,
|
||||||
|
|
||||||
|
db $0F, $39,
|
||||||
|
|
||||||
|
db $0F, $3A,
|
||||||
|
|
||||||
|
db $0F, $3B,
|
||||||
|
|
||||||
|
db $0F, $3C,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $3D,
|
||||||
|
|
||||||
|
db $0F, $3E,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $3F,
|
||||||
|
|
||||||
|
gbtarottheme:
|
||||||
|
db 4
|
||||||
|
dw gbtarottheme_0
|
||||||
|
dw gbtarottheme_0
|
||||||
|
dw gbtarottheme_0
|
12
main.asm
12
main.asm
@ -36,6 +36,7 @@ def CARD_HELPER_VARS_START equ $c600
|
|||||||
def CARD_VARS_START equ $c700 ; variables for animation of individual cards
|
def CARD_VARS_START equ $c700 ; variables for animation of individual cards
|
||||||
def CVS equ CARD_VARS_START
|
def CVS equ CARD_VARS_START
|
||||||
def SHUFFLED_DECK equ $c800 ; location for the shuffled deck
|
def SHUFFLED_DECK equ $c800 ; location for the shuffled deck
|
||||||
|
def AUDIO_VARS_START equ $c900
|
||||||
|
|
||||||
def ZEROES equ $D000
|
def ZEROES equ $D000
|
||||||
def ONES equ $D200
|
def ONES equ $D200
|
||||||
@ -63,8 +64,6 @@ SECTION "Interrupts", ROM0[$0]
|
|||||||
call INTERRUPT_LCD - 1
|
call INTERRUPT_LCD - 1
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SECTION "Header", ROM0[$100]
|
SECTION "Header", ROM0[$100]
|
||||||
|
|
||||||
jp EntryPoint
|
jp EntryPoint
|
||||||
@ -225,6 +224,8 @@ EntryPoint:
|
|||||||
ld hl, ScreenMainMenu
|
ld hl, ScreenMainMenu
|
||||||
call ChangeScene
|
call ChangeScene
|
||||||
|
|
||||||
|
call SoundSetup
|
||||||
|
|
||||||
Loop:
|
Loop:
|
||||||
; okay this is kinda sketchy. we want a delta time variable.
|
; okay this is kinda sketchy. we want a delta time variable.
|
||||||
; we've got two eight-bit counters, one at 4096hz and one at 16384hz
|
; we've got two eight-bit counters, one at 4096hz and one at 16384hz
|
||||||
@ -286,18 +287,14 @@ println "scene update is ", SCENE_UPDATE - 1
|
|||||||
|
|
||||||
println "scene draw is ", SCENE_DRAW - 1
|
println "scene draw is ", SCENE_DRAW - 1
|
||||||
call SCENE_DRAW - 1 ; hope this takes fewer than 9 scanlines!
|
call SCENE_DRAW - 1 ; hope this takes fewer than 9 scanlines!
|
||||||
println "scenee draw call is at ", @
|
|
||||||
; either way it's going to eat into the update timing
|
; either way it's going to eat into the update timing
|
||||||
; at this point we want to make sure that scanline 153 has passed
|
; at this point we want to make sure that scanline 153 has passed
|
||||||
; we should check if we're past there and skip this await if necessary
|
; we should check if we're past there and skip this await if necessary
|
||||||
ld b, 10
|
ld b, 5
|
||||||
call AwaitLine
|
call AwaitLine
|
||||||
|
|
||||||
jp Loop
|
jp Loop
|
||||||
|
|
||||||
SoundUpdate:
|
|
||||||
ret
|
|
||||||
|
|
||||||
ChangeScene: ; hl should be a pointer to, in sequence, setup update draw teardown
|
ChangeScene: ; hl should be a pointer to, in sequence, setup update draw teardown
|
||||||
;call SCENE_TEARDOWN - 1
|
;call SCENE_TEARDOWN - 1
|
||||||
|
|
||||||
@ -559,6 +556,7 @@ LetterTiles:
|
|||||||
.end
|
.end
|
||||||
|
|
||||||
INCLUDE "Async.inc"
|
INCLUDE "Async.inc"
|
||||||
|
INCLUDE "Audio.inc"
|
||||||
INCLUDE "Random.inc"
|
INCLUDE "Random.inc"
|
||||||
INCLUDE "CopyRangeSafe.inc"
|
INCLUDE "CopyRangeSafe.inc"
|
||||||
INCLUDE "CopyTilesSafe.inc"
|
INCLUDE "CopyTilesSafe.inc"
|
||||||
|
627
s3m2gbt.py
Normal file
627
s3m2gbt.py
Normal 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)
|
BIN
source.zip
BIN
source.zip
Binary file not shown.
203
theme.inc
Normal file
203
theme.inc
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
; File created by shoofle's s3m2gbt edit
|
||||||
|
|
||||||
|
gbtarottheme_0:
|
||||||
|
db $00, $00,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
db $22, $00,
|
||||||
|
db $23, $00,
|
||||||
|
db $24, $00,
|
||||||
|
|
||||||
|
db $0F, $01,
|
||||||
|
|
||||||
|
db $0F, $02,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $03,
|
||||||
|
|
||||||
|
db $0F, $04,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $05,
|
||||||
|
|
||||||
|
db $0F, $06,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $07,
|
||||||
|
|
||||||
|
db $0F, $08,
|
||||||
|
|
||||||
|
db $0F, $09,
|
||||||
|
|
||||||
|
db $0F, $0A,
|
||||||
|
|
||||||
|
db $0F, $0B,
|
||||||
|
|
||||||
|
db $0F, $0C,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $0D,
|
||||||
|
|
||||||
|
db $0F, $0E,
|
||||||
|
db $11, $0C,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $0F,
|
||||||
|
|
||||||
|
db $0F, $10,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $11,
|
||||||
|
|
||||||
|
db $0F, $12,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $13,
|
||||||
|
|
||||||
|
db $0F, $14,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $15,
|
||||||
|
|
||||||
|
db $0F, $16,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $17,
|
||||||
|
|
||||||
|
db $0F, $18,
|
||||||
|
|
||||||
|
db $0F, $19,
|
||||||
|
|
||||||
|
db $0F, $1A,
|
||||||
|
|
||||||
|
db $0F, $1B,
|
||||||
|
|
||||||
|
db $0F, $1C,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $1D,
|
||||||
|
|
||||||
|
db $0F, $1E,
|
||||||
|
db $11, $10,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $1F,
|
||||||
|
|
||||||
|
db $0F, $20,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $21,
|
||||||
|
|
||||||
|
db $0F, $22,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $23,
|
||||||
|
|
||||||
|
db $0F, $24,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $25,
|
||||||
|
|
||||||
|
db $0F, $26,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $27,
|
||||||
|
|
||||||
|
db $0F, $28,
|
||||||
|
|
||||||
|
db $0F, $29,
|
||||||
|
|
||||||
|
db $0F, $2A,
|
||||||
|
|
||||||
|
db $0F, $2B,
|
||||||
|
|
||||||
|
db $0F, $2C,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $2D,
|
||||||
|
|
||||||
|
db $0F, $2E,
|
||||||
|
db $11, $12,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $2F,
|
||||||
|
|
||||||
|
db $0F, $30,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $40,
|
||||||
|
|
||||||
|
db $0F, $31,
|
||||||
|
|
||||||
|
db $0F, $32,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $80,
|
||||||
|
|
||||||
|
db $0F, $33,
|
||||||
|
|
||||||
|
db $0F, $34,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $C0,
|
||||||
|
|
||||||
|
db $0F, $35,
|
||||||
|
|
||||||
|
db $0F, $36,
|
||||||
|
db $81, $FE,
|
||||||
|
|
||||||
|
db $0F, $37,
|
||||||
|
|
||||||
|
db $0F, $38,
|
||||||
|
|
||||||
|
db $0F, $39,
|
||||||
|
|
||||||
|
db $0F, $3A,
|
||||||
|
|
||||||
|
db $0F, $3B,
|
||||||
|
|
||||||
|
db $0F, $3C,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $3D,
|
||||||
|
|
||||||
|
db $0F, $3E,
|
||||||
|
db $11, $11,
|
||||||
|
db $21, $0F,
|
||||||
|
db $31, $00,
|
||||||
|
|
||||||
|
db $0F, $3F,
|
||||||
|
|
||||||
|
gbtarottheme:
|
||||||
|
db 4
|
||||||
|
dw gbtarottheme_0
|
||||||
|
dw gbtarottheme_0
|
||||||
|
dw gbtarottheme_0
|
Loading…
Reference in New Issue
Block a user