ZzFX for Playdate
June 30, 2026 · View on GitHub
Tiny real-time sound effects for Playdate, powered by ZzFX.
Design a sound in the ZzFX sound designer, paste the params into Lua, and play it instantly. No audio asset files needed.
This repo includes a playable demo app.

Make sounds with the ZzFX editor
Open the ZzFX sound editor:
https://killedbyapixel.github.io/ZzFX/
Quick workflow:
- Build a sound in the editor.
- Select the Lua radio button.
- Copy the generated Lua-style output.
- Paste it directly into your Lua code.
Quick start
import "zzfx"
-- Coin
zzfx({nil,0,988,nil,nil,.4,nil,33,nil,nil,331,.1})
-- Simple beep
zzfx({nil, nil, 220})
-- Note helper
local f = zzfxGetNote(7, 220)
zzfx({nil, nil, f, nil, .04, .12})
The editor's Lua output is ready to paste as-is — it already uses nil for
empty slots. (If you copy the JS output instead, replace each empty ,, slot
with nil.)
Faster loading: bake to WAV for release (optional)
While building your game, generating sounds from params is the easy path: no asset files to manage, and you tweak a sound just by changing numbers.
When you're ready to ship, you can trade that flexibility for faster startup. The
ZzFX editor can also export a sound as a
WAV file. Export your finished sounds (with randomness set to 0), drop the WAVs in
Source/, and hand the filename to zzfxSound instead of a params table:
-- dev: synthesized from params
local coin = zzfxSound({nil,nil,1675,nil,.06,.24,1,nil,nil,nil,837,.06}, .05)
-- release: the baked WAV. Just swap the argument, nothing else changes.
local coin = zzfxSound("coin", .05)
coin:play() -- same API
coin:playNote(7) -- and the .05 randomness is still applied at play time
zzfxSound takes either a params table or a WAV filename and returns the same
object either way, so swapping is a one-line change. Loading a file is faster
than synthesizing, so a game with a lot of sounds boots quicker. And because the
randomness is applied at play time instead of baked in, the WAV still varies per
play just like the synthesized version.
Build
Requires the Playdate SDK with PLAYDATE_SDK_PATH set.
Building for device additionally needs the
Arm GNU Toolchain
(arm-none-eabi).
macOS / Linux / MinGW:
make # Simulator -> ZzFX.pdx
make device # Device build (needs arm-none-eabi-gcc)
Windows:
$env:PLAYDATE_SDK_PATH = "C:\Program Files (x86)\Playdate"
# Simulator only:
cmake -S . -B build -G "Visual Studio 17 2022"
cmake --build build --config Release
# Simulator + device, bundled into ZzFX.pdx (edit the toolchain paths inside first):
powershell -ExecutionPolicy Bypass -File build.ps1
Note: a simulator-only build produces a
.pdxwith no ARM binary — it runs in the Simulator but crashes on hardware. Usemake device/build.ps1for the device build.
Open ZzFX.pdx in the Simulator, or upload to hardware from its Device menu.
Use it in your own project
Copy Source/zzfx.lua and the synth (src/zzfx.c, src/zzfx.h) into your
project. From your own eventHandler, on kEventInitLua, call zzfx_init(pd)
and zzfx_register_lua(pd) — see src/main.c for the ~10-line example. Then in
Lua, import "zzfx" and call zzfx({...}). (The synth is C, so the toolchain
above is required.)
Source/ is the Lua/assets folder the SDK builds; src/ is the native C synth —
the normal split for a mixed Lua + C Playdate project.
API
zzfx(paramsTable)plays one sound from ZzFX-style params (21-slot array format).zzfxSound(paramsTableOrWavPath, randomness?)creates a sound object, from params (synthesized + cached) or a WAV filename (loaded). Same:play/:playNote/:freeinterface and same per-play randomness either way.- Create cached
zzfxSoundobjects at startup and reuse them during gameplay. sound:play(pitch?, randomnessScale?)plays the cached sound.sound:playNote(semitoneOffset?)plays the cached sound as a note.sound:free()releases cached sample memory.zzfxGetNote(semitoneOffset, rootFrequency)returns note frequency.- C API is available in
src/zzfx.h(zzfx_init,zzfx_register_lua,zzfx_play).