-- sfx/music data wrangler
-- by pancelor
-- The docs call this the "index", I call it the "header"
-- https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index
function sfxheader_read()
local num_instruments, num_tracks, num_patterns, flags = peek2(0x30000, 4)
-- ...8 unused bytes here...
local insts_addr, tracks_addr, patterns_addr, unused1 = peek4(0x30010, 4)
assert(unused1==0,"bad sfx header 1")
local tick_len, def_len, def_spd = peek2(0x30020, 3)
local def_spd2, unused2, unused3, unused4 = peek(0x30026, 4)
-- TODO def_spd2 v. def_spd?
assert(unused2==0,"bad sfx header 2")
assert(unused3==0,"bad sfx header 3")
assert(unused4==0,"bad sfx header 4")
return {
num_instruments = num_instruments,
num_tracks = num_tracks,
num_patterns = num_patterns,
flags = flags, --0x1 use default track indexing (base+0x20000, increments of 328 bytes)
insts_addr = insts_addr, --relative address of instruments
tracks_addr = tracks_addr, --relative address of track index
patterns_addr = patterns_addr, --relative address of pattern data
tick_len = tick_len, --in 1/16ths of a sample at 44100Hz [0 means 5880 -- 120 ticks / second]
def_len = def_len, --used by patterns that do not have a default length specified
def_spd = def_spd, --used by patterns that do not have a default speed specified
def_spd2 = def_spd2, -- ??
}
end
function pattern_read(i)
local base = 0x30100 + sfxheader_read().patterns_addr -- normally 0x30100
local addr = base + i*20
local tracks = { peek(addr, 8) }
local flow_flags, channel_mask = peek(addr+8, 2)
local len = peek2(addr+10)
-- ...8 unused bytes here...
return {
tracks = tracks, -- array with 8 track ids
flow_flags = flow_flags, -- 0x1 loop forward, 0x2 loop backward, 0x4 stop
channel_mask = channel_mask, -- 0x1 is tracks[1] unmuted? 0x2 is tracks[2] unmuted? 0x4 => tracks[3], 0x8 => tracks[4], ... 0x80 => tracks[8]
len = len, --TODO: how does this interact with track length and header def_len/def_len2?
}
end
function track_read(i)
local header = sfxheader_read()
assert(header.flags&1==1,"unknown track format")
local base = 0x30000 + header.tracks_addr -- normally 0x50000
local addr = base + i*328
local len = peek2(addr)
local spd, loop0, loop1, delay, flags, unused = peek(addr+2, 6)
assert(unused==0,"bad sfx track")
-- note: if len<64 then some of this data is irrelevant:
local pitches = { peek(addr+8,64) }
local instruments = { peek(addr+72,64) }
local volumes = { peek(addr+136,64) }
local effects = { peek(addr+200,64) }
local effect_params = { peek(addr+264,64) }
return {
len = len,
spd = spd,
loop0 = loop0,
loop1 = loop1,
delay = delay,
flags = flags, --0x1 mute
-- 64-length arrays, 1 entry per note:
pitches = pitches,
instruments = instruments,
volumes = volumes, -- a 0xFF entry means muted
effects = effects, -- stored as their ascii code -- retrieve with ord()
effect_params = effect_params,
}
end
-- an alternate version of track_read that extracts a single row
-- ti: track index
-- ri: row index
function track_row_read(ti,ri)
local header = sfxheader_read()
assert(header.flags&1==1,"unknown track format")
local base = 0x30000 + header.tracks_addr -- normally 0x50000
local addr = base + ti*328 + ri
return {
pitch = peek(addr+8),
instrument = peek(addr+72),
volume = peek(addr+136),
effect = peek(addr+200),
effect_params = peek(addr+264),
}
end
function instrument_read(i)
local base = 0x30000 + sfxheader_read().tracks_addr -- normally 0x40000
local addr = base + i*0x200
assert(false,"not implemented")
-- see https://www.lexaloffle.com/dl/docs/picotron_synth.html#Index
-- or read /system/apps/sfx.p64/data.lua:clear_instrument() for info
end
-- i: channel index 0..7
function channel_current_track(i)
return stat(464,0)>>i&1~=0 and stat(400+i,12) or -1
end
-- i: channel index 0..7
function channel_current_row(i)
return stat(464,0)>>i&1~=0 and stat(400+i,9) or -1
end
-- number of ticks played on current pattern
function channel_ticks_played(i)
return stat(400+i,11)
end
--currently playing pattern, or -1
function current_pattern()
return stat(466)
end