Log In  


Inspired by https://www.lexaloffle.com/bbs/?tid=2341, here's a description of how sfx and music are stored in Picotron. (Well, there's not much description here yet, just some helpful code. I'll add to this over time)

The data can be in unusual custom formats. The header data is supposed to help in these cases and is accounted for in this code (with some asserts to crash if the data format is unknown). But for most people using this code, the data will be in the standard format.

Library

Code:

-- 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

Sources:

Demo cart

Here's a tiny music "visualizer" that uses this library:

Cart #datufbuyi-0 | 2024-08-02 | Embed ▽ | No License
5

License

This library is licensed as CC BY 4.0 (basically, credit me and link back to this post, and you can use this code for anything)

(This does not include the music playing in the demo cart. The music is by @iaoth and is licensed as CC BY-NC-SA 4.0 -- see https://www.lexaloffle.com/bbs/?pid=51460)

5



[Please log in to post a comment]