Log In  


I didn't see this documented anywhere, and I had to work it out for my drumbeat cart, so I thought I'd save everyone else the trouble and publish what I know.

SFX:

A sound effect is stored in 68 bytes. Sfx 0 starts at address 0x3200, sfx 1 at address 0x3244, and so on.

The first 64 bytes (offset 0..63) store the 32 notes of the sound effect as described below.

Offset 64 is a byte that determines the editor mode: 0 for graph mode, nonzero for tracker mode. This has no effect on playback, just on the mode opened in the editor by default.

Offset 65 stores the playback speed. (Higher values correspond to slower playback, so "speed" isn't exactly the right name, but that is what is used in the editor.)

Offsets 66 and 67 store the loop begin and loop end times. A sound effect contains only 32 notes, but these times may be set to any byte value. All times past 32 simply play silence.

Note format:

A note is stored in 16 bits, two bytes little-endian.

Bits 0..5 store the pitch, ranging from 0 (C in octave 0) to 63 (D# in octave 5). The tracker mode editor can only enter pitches up to 60 (C 5), but it can display and play higher pitches.

Bits 6..8 store the instrument; the values are the same as in the tracker mode editor.

Bits 9..11 store the volume. Volume 0 corresponds to silence.

Bits 12..14 store the effect; the values are the same as in the tracker mode editor.

Bit 15 appears to be unused.

Example code:

-- This code is public domain, feel free to copy, use, and modify however you'd like

function make_note(pitch, instr, vol, effect)
  return { pitch + 64*(inst%4) , 16*effect + 2*vol + flr(instr/4) } -- flr may be redundant when this is poke'd into memory
end

function get_note(sfx, time)
  local addr = 0x3200 + 68*sfx + 2*time
  return { peek(addr) , peek(addr + 1) }
end

function set_note(sfx, time, note)
  local addr = 0x3200 + 68*sfx + 2*time
  poke(addr, note[1])
  poke(addr+1, note[2])
end

function get_speed(sfx)
  return peek(0x3200 + 68*sfx + 65)
end

function set_speed(sfx, speed)
  poke(0x3200 + 68*sfx + 65, speed)
end

function get_loop_start(sfx)
  return peek(0x3200 + 68*sfx + 66)
end

function get_loop_end(sfx)
  return peek(0x3200 + 68*sfx + 67)
end

function set_loop(sfx, start, end)
  local addr = 0x3200 + 68*sfx
  poke(addr + 66, start)
  poke(addr + 67, end)
end

Music format:

A music pattern consists of four channels, each of which may have a sound effect played on it, plus some playback control flags.

The patterns are stored in four bytes, with pattern 0 at address 0x3100. Each byte describes one channel.

Bits 0..5 are the sfx to be played on the channel.

Bit 6 is mute. If it is 0, the channel is played; if it is 1, the channel is silenced.

Bit 7 contains the playback control flags:
Byte 0 bit 7 is the loop start flag
Byte 1 bit 7 is the loop end flag
Byte 2 bit 7 is the stop flag
Byte 3 bit 7 is unused

Playback control is as follows (I haven't tested all possibilities here):
When a pattern reaches the end, if the stop flag is set on this pattern, playback stops.
Otherwise, if the loop end flag is set on this pattern, move back until a pattern with the loop start flag set is found, and continue playback from there.
Otherwise, continue playback with the next pattern.

62


thanks for posting this!

I worked out the sfx layout too and posted in another thread, but it probably wasn't easy to find.

The music docs will be handy =)


Should we have a wiki?


A wiki would be amazing.


1

Thank you for documenting this (and for your little copy-paste ready API for lazy people like me)

A wiki would definitely be a great idea for stuff like this. This doc could also be added to the cheat sheet in the meantime.


1

Thank you as well, especially for the functions. Yes we need a wiki.



Thanks matt :) Are there plans to make it more prominent, eg. put it in the menu bar next to "Forum"?

Should I add this to the sound page?
http://pico-8.wikia.com/wiki/Sfx

Or the code snippit page?
http://pico-8.wikia.com/wiki/Category:Code_snippets


It's not my wiki, but @zep has said this is as good as we will get until he adds something on here eventually


I'm actively building out the wiki at the moment. I have a ToC in progress including a Memory page that would include this stuff. I'll gladly accept help but I'd like to get the bones in place, if that's OK. (I was going to make an announcement when finished, and had assumed nobody was looking at the wiki. :) )

I have complete memory and file format reference docs in the comments of my picotool library here, including sfx and music: https://github.com/dansanderson/picotool/tree/master/pico8


this will be so useful when designing sfx for games! and dynamic music. extremely appreciated <3



You can look at my code here:
https://www.lexaloffle.com/bbs/?pid=20797&tid=3362&autoplay=1#pp

which do a quasi real time read of the currently played note (and more) on each channels.


Bits 0..5 store the pitch, ranging from 0 (C in octave 0) to 63 (D# in octave 5).

Someone correct me if I'm wrong—but isn't the Pico-8 range actually from C in octave 2 (~65.5Hz) to D# in octave 7 (~2489Hz)?

Compare middle C (C4) to this:

c2=0
c4=24
c7=60
eb7=63

note=make_note(c4,0,7,0)
for t=0,31 do
set_note(0,t,note)
end
set_speed(0,10)

--play middle c
sfx(0)

function _update()
end

thanks!

This is exactly what i needed for making some generative music for my game.


1

http://pico-8.wikia.com/wiki/Memory

(Written since my last post to this thread. :) )


Is there a way to modify assigned notes with strings instead of one byte at a time? Highly relevant info for my DDR-inspired project, which may require quite a bit of on-the-fly editing.

The song select screen is broken up into 7 song chunks - so each page has a sample set, as well as typical music menu for "Course Mode" (which really, is just playing 4 of the songs in sequence of appearance so far) or "Marathon Mode" (same thing, but now all seven).

After selecting/launching a song though; the idea is to break the song into 4 parts - an "Outro/Intro" chunk (both composed in the first set, and the "begin" flag set to where the intro begins), a verse and a chorus (which I'm planning a different sequence script with, so it can do stuff like "intro, verse, chorus, verse, chorus, outro), and the fourth section is mostly for buildup or some other unique part of the song.

THIS part may also have to be recomposed/arranged mid-stage by an invisible flag hitting the stepzone - hence why I'd much rather use a one-shot string to do so than bytes. Or, at least the composition can be prearranged in the SFX editor, but the assigned SFX would have to be swapped for the fourth part in this case, or in the event of a "Game Over."

As far as the speed thing goes, it's just "programming steps between notes," really. Do you know if it accepts double/decimal values, or just integer ones? Most of the songs I want to make on it are doable, but there's a few icky BPM ranges that don't line up well (most notably, 170~340, 190~380, and 400). The backup plan so far is to compose them in lower BPMs (higher speed), and then artifically multiply the arrow speed with a variable, but this will make them very simplified compositions.


It sounds like you might prefer editing in the sound/music editor then using memcpy() to copy the sound and music data regions to an unused portion of gfx memory or similar. If you really need to store this data as a string, you can base64-encode it and write a little decoder routine. If you're looking to make your own string representation of sound data such that you can edit songs directly in the source code, that's also possible with a similar technique. (You'd use sub() calls to access chars in the string.)

Note speed is an integer between 1 and 255, a multiple of 1/128ths of a second. Of course, how that translates to BPM depends on how you're using the sound pattern.


I made a lil cart using this! Thanks for sharing!


not sure how to build a splitter function

like the inverse of make_note

that'd be handy!


This is so great - i´m building a sequencer with your help. Thanks!


1

Hi everyone. I'm trying to decode this from a .p8 file into a Love2d sound data.

I've already done the work to decode gfx/gff/map data in this thread https://www.lexaloffle.com/bbs/?tid=45366

But sound programming is completely new to me. I don't understand any of the terminology in that page, and how it relates to the code in the beep function example at https://love2d.org/wiki/love.sound.newSoundData

And even once I figure that out, there's data-less terminology on https://pico-8.fandom.com/wiki/P8FileFormat such as where it says waveform=2 is "sawtooth".

How can we work together to figure this out? Does anyone know any of this info and can help?

My goal is to finish writing this love2d lib for .p8 files, so that my son can make love2d games using all the contents in .p8 files that my other kids edit/create. And a game with only graphics and no sound just feels so empty.

I know that this thread has so far been about in-memory data formats of sound/music, and I'm asking about .p8 file format. But that's not the hard part. I can easily decode the .p8 file data according to the wiki page I linked to above. The problem is understanding it well enough to port it to love2d SoundData. That's what I need help with.


@catholic I remember telling you on discord and I'll say it again but this page (or at least the very top) is just how the data is stored and read by pico to get the info needed to play the music/sfx.
If you want to actually turn that data into sound you'll need the synth, which is part of pico so you'll either need to crack pico open which I have no idea how to, to see or reverse-engineer it.
Hope that helps clear that up.

(So you'll need to read this data but also put it through Pico's synth and then que that to the qsource.)


First of all I've never been on the discord, you must be thinking of someone else.

But surely there is another way to do this without "cracking" anything? Maybe experimenting with different patterns and trying to see what sounds right?


1

The code at the top has some small syntax errors. This is a corrected version.

function make_note(pitch,instr,vol,effect)
 return {pitch+64*(instr%4),16*effect+2*vol+flr(instr/4)}
end

function get_note(sfx_index,time_index)
 local addr=0x3200+68*sfx_index+2*time_index
 return {peek(addr),peek(addr+1)}
end

function set_note(sfx_index,time_index,note)
 local addr=0x3200+68*sfx_index+2*time_index
 poke(addr,note[1])
 poke(addr+1,note[2])
end

function get_speed(sfx_index)
 return peek(0x3200+68*sfx_index+65)
end

function set_speed(sfx_index,speed)
 poke(0x3200+68*sfx_index+65,speed)
end

function get_loop_start(sfx_index)
 return peek(0x3200+68*sfx_index+66)
end

function get_loop_end(sfx_index)
 return peek(0x3200+68*sfx_index+67)
end

function set_loop(sfx_index,start,ending)
 local addr=0x3200+68*sfx_index
 poke(addr+66,start)
 poke(addr+67,ending)
end

2

This is how you can set the global fx (noiz, buzz, detune, reverb and dampen).

--Values of fx: noiz(0,1),buzz(0,1),detune(0,1,2),reverb(0,1,2),dampen(0,1,2)
function make_globalfx(noiz,buzz,detune,reverb,dampen)
 local effect = 0
 effect |= noiz and 2 or 0
 effect |= buzz and 4 or 0
 effect += detune * 8
 effect += reverb * 24
 effect += dampen * 72
 return effect
end

function set_globalfx(sfx_index,effect)
 local addr=0x3200+68*sfx_index
 poke(addr+64,effect)
end


[Please log in to post a comment]