Skip to content

Instantly share code, notes, and snippets.

@sealgair
Last active August 20, 2016 02:53
Show Gist options
  • Save sealgair/7dc17f4d62665c91f0d089344230c618 to your computer and use it in GitHub Desktop.
Save sealgair/7dc17f4d62665c91f0d089344230c618 to your computer and use it in GitHub Desktop.
pico 8 runtime music editing
--------------------------------
-- music
--[[ memory format:
0x3100 - song
each song is 4 bytes
the first bit of each byte is
a flag:
byte 1: loop start
byte 2: loop back
byte 3: stop
the remainder of the byte is
the index of each of the 4 sfx
making up the channels of the
song
0x3200 - sfx
each sfx is comprised of 64 two
byte notes, followed by two
bytes of metadata
note format: 2 bytes, backwards
byte 2:
0eeevvvi
byte 1:
iipppppp
eee: effect (0-7)
vvv: volume (0-7)
iii: instrument (0-7, split over bytes)
pppppp: pitch (0-63)
sfx metadata:
byte 1 is probably loop start & end
byte 2 is probably volume
]]
--------------------------------
notenames={
c=0,
cs=1,
d=2,
ds=3,
e=4,
f=5,
fs=6,
g=7,
gs=8,
a=9,
as=10,
b=11,
}
function getmusic(m)
local l=4
local b=0x3100+m*l
return {
i=m, b=b,
loopstart=band(peek(b),128)!=0,
loopback=band(peek(b+1),128)!=0,
stop=band(peek(b+2),128)!=0,
}
end
function getmusicsounds(m)
local music=getmusic(m)
if sounds==nil then
sounds={}
end
for i=0,3 do
local s=band(peek(music.b+i),127)
if s!=0x42 then
add(sounds, s)
end
end
if not music.loopback and not music.stop then
sounds = concat(a, getmusicsounds(m+1))
end
return sounds
end
function setmusic(m, args)
-- TODO
end
function getsound(s)
local l=68
local b=0x3200+s*l
return {
speed=peek(b+65),
--TODO: loop start / end
}
end
function setsound(s, args)
local l=68
local b=0x3200+s*l
if args.speed!=nil then
poke(b+65, args.speed)
end
--TODO: loop start / end
end
function getnote(s, n)
local sl=68
local nl=2
local b=0x3200+s*sl+n*nl
local b1=peek(b+1)
local b2=peek(b)
return {
b=b, s=s, n=n,
pitch=band(b2, 63),
instrument=shl(band(b1, 1), 2)+shr(band(b2, 192), 6),
volume=shr(band(b1, 14), 1),
effect=shr(band(b1, 112), 4),
}
end
function setnote(s, n, args)
local sl=68
local nl=2
local b=0x3200+s*sl+n*nl
local b1=peek(b+1)
local b2=peek(b)
if args.pitch!=nil then
poke(b, bor(band(192, b2), args.pitch))
end
if args.volume!=nil then
poke(b+1, bor(band(241, b1), shl(args.volume, 1)))
end
-- TODO: instrument, effect
end
function reloadmusic(m)
for s in all(getmusicsounds(m)) do
reloadsfx(s)
end
end
function reloadsfx(s)
local l=68
local b=0x3200+s*l
reload(b, l)
end
function altmusic(m, fn)
for s in all(getmusicsounds(m)) do
for n=0,63 do
local nc=fn(getnote(s, n))
if nc!=nil then
setnote(s, n, nc)
end
end
end
end
function minorize(m, base)
base=base%12
local minors={
[(base+4)%12]=true,
[(base+9)%12]=true,
[(base+11)%12]=true,
}
altmusic(m, function(note)
local pc=note.pitch%12
if minors[pc] then
return {
pitch=note.pitch-1
}
end
end)
end
function settempo(m, speed)
for s in all(getmusicsounds(m)) do
setsound(s, {
speed=flr(speed)
})
end
end
function altvolume(m, av)
altmusic(m, function(note)
return {
volume=bound(round(note.volume*av), 7, 0)
}
end)
end
@kvognar
Copy link

kvognar commented Aug 20, 2016

This looks awesome! I'd like to hear it in action. Just a couple notes:

  • You should use more descriptive variable names, especially if you want this to be a library for others to use. There's no need for so many 1- and 2-letter names - those should be left mostly for indexing loops.
  • bound and round don't seem to be functions in Pico-8 - did you leave them out, or are they part of the Lua library that you assumed were included?
  • I'm curious about what you're doing with shr and shl, exactly - a few comments there might be nice, though maybe you're not setting out to give a lesson with this :p

I like the handy tables you're passing around to represent sounds and music. Overall this looks pretty slick!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment