- 1. About the C API
- 2. Prerequisites
- 3. IDEs
- 4. Command line
- 5. Building on Windows
- 6. Game Initialization
- 7. API reference
- 8. Performance Considerations
- 9. Getting Help
- 10. Legal information
1. About the C API
Playdate’s C API lets you use native C code in your game, using the same code to build both an OS specific library for the Simulator and a bin file for the Playdate hardware. Most games will use Playdate’s Lua API (documented in Inside Playdate with Lua). We include several example C programs in the SDK found at C_API/Examples
. Read on to learn why and how to take advantage of the C API.
1.1. Why use the C API?
Performance. Lua is garbage collected. Its garbage collector’s performance varies wildly from game-to-game and even frame-to-frame within the same game. The collector’s performance is dependent on the amount of garbage generated per frame and, perhaps counterintuitively, the amount of persistent, non-garbage present in a frame (which it has to crawl to discover new garbage).
1.2. Before porting to C
Depending on how you’ve used Lua, porting part or all of your existing game from Lua to C can be quite involved. Make sure you’ve squeezed every last bit of performance out of Lua before refactoring or completely rewriting your game.
-
flatten loops as much as possible
-
localize frequently used global tables and functions, especially those used in loops
-
disk access is slow on the hardware, preload any external assets like images, fonts, and sound effects
-
pre-compute and cache the result of expensive computations
-
pre-render and cache the result of expensive drawing routines
-
pre-allocate and reuse tables
-
move table allocations out of loops
-
avoid excessive string concatenation with
..
, instead build a table of strings and usetable.concat()
It’s worth mentioning that some of these suggestions are directly opposed with minimizing the amount of objects in active memory.
Also, be sure to profile on the hardware, the Playdate Simulator is often considerably faster than the hardware.
1.3. How to use the C API
There are two primary use cases for the C API:
-
extending the Lua runtime by adding native functions your Lua game can use (see playdate->lua->addFunction() and playdate->lua->registerClass())
-
bypassing the Lua runtime completely by replacing the Lua update callback with your own native function and building your entire game in C (see playdate->system->setUpdateCallback())
2. Prerequisites
To build games for the Playdate in C you will need to use a native C compiler for the Simulator and the ARM embedded compiler for the Playdate hardware. Native C compilers come with the standard development tools on the platform: Xcode, Visual Studio, Linux Development packages. However, the embedded ARM compiler will also need to be installed.
-
MacOS: The Playdate SDK installer installs the ARM embedded compiler in
/Applications/ARM
automatically. -
Linux: Install the
arm-none-eabi-newlib
package (naming varies based on distro). -
Windows: Install the Windows ARM embedded compiler
gcc-arm-none-eabi
from developer.arm.com.
3. IDEs
3.1. Xcode/CMake
To use Xcode to build the example C projects, you will first need to generate the Xcode project file from the CMake file. To do this, you’ll need CMake installed, we recommend installing it using Brew. Once installed, open the Terminal and navigate into an example project and run the following commands:
mkdir build
cd build
cmake .. -G "Xcode"
This will create an Xcode project in the build
directory. This Xcode project will produce a .pdx that’s ready to run and debug in the Simulator. When you want to build for the device, delete the contents of the build
folder (or create a second folder), then run these commands which build an ARM binary:
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=<path to SDK>/C_API/buildsupport/arm.cmake ..
make
3.2. Xcode/Make
Using Xcode for Playdate development requires a few project settings in order to get in-Simulator debugging working.
-
Begin by making sure you have a working Makefile in your project root. The Makefile in the
Hello World
example project can serve as a starting template. -
Create a new Xcode Project with a macos Library target and set it up as a plain C library. We recommend you add the word 'Simulator' to the target name
-
Add your source files to the target’s "compile sources" build phase
-
Add the full path to the SDK/C_API folder to the Header Search Paths build setting
-
Add
PLAYDATE_SIMULATOR=1
andTARGET_EXTENSION=1
to the Preprocessor Macros build setting -
Add a new script build phase called
Compile PDX
with the following in the body
cp "${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}" Source/pdex.dylib
touch Source/pdex.bin
make pdc
Assuming you don’t have any syntax errors in your code your game should now build successfully. Next we need to hook up the Simulator to run your game in the Xcode debugger.
-
Edit the Scheme for your library target
-
In the Run configuration set the Executable to the Playdate Simulator
-
Switch to the Arguments tab of the Run configuration and add the full path to the compiled .pdx bundle. It’s recommended that you put double-quotes around the path in case there are spaces in it.
-
Close the scheme and Run your game
In order to build for the device you’ll need to do the following:
-
Create a new External Build System target with 'Device' in the name to differentiate it
-
Select the Info section of the new target and set the Arguments to
device pdc
Building the new Device target will add the cross-compiled binary to the .pdx bundle to run on the device.
3.3. CLion/CMake
CLion uses CMake files for most of it’s project configuration so project setup is straightforward
-
Add a
CMakeLists.txt
file to your project. The CMake file in theHello World
example project can serve as a starting template. -
Open your project folder in CLion. It will detect the names of the Simulator and device targets automatically.
-
Open Preferences→Build→CMake and add a new build configuration with the following settings
-
Name:
Device
-
Build Type:
Debug
-
CMake Options:
-DCMAKE_TOOLCHAIN_FILE=<path to SDK>/C_API/buildsupport/arm.cmake
-
Once CLion reloads you’ll see that when you toggle between your Simulator and device target CLion select the appropriate build configuration. Next, you’ll need to tell CLion how to run the Simulator
-
Open the Edit Configurations menu
-
Select your Simulator target and set the Executable to the Playdate Simulator executable. The binary executable is at
SDK/bin/Playdate Simulator/Contents/Macos/Playdate Simulator
-
Set the Program arguments to the path of the compiled pdx bundle. Use
^-F
to access a file picker.
4. Command line
4.1. Set PLAYDATE_SDK_PATH
Environment Variable
-
On macOS, it is not required, but recommended
-
On Linux, it is required for CMake and Make files
-
On Windows, it is required for CMake (see the Building on Windows section for instructions)
- zsh
-
edit ~/.zprofile
- bash
-
edit ~/.bash_profile
oredit ~/.bashrc
Add the following line — export PLAYDATE_SDK_PATH=<path to SDK>
— replacing the placeholder text with your SDK location.
Note
|
The pdc compiler will also use this value for the default location of the SDK if it is not specified using the -sdkpath flag.
|
Tip
|
You may also want to add <path to SDK>/bin to your shell $PATH variable. This allows running pdc , pdutil and the Simulator from any location without a fully qualified path.
|
4.2. CMake
To build with CMake using Make run the following commands from the root of the project:
mkdir build
cd build
cmake ..
make
This will produce a PDX that’s ready to run on the Simulator. When you want to build for the device, delete the contents of the build
folder (or create a second folder), then run these commands which build an ARM binary:
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=<path to SDK>/C_API/buildsupport/arm.cmake ..
make
4.3. Make
To build Playdate games using the example Makefiles you’ll need to install arm-none-eabi-newlib
packages (naming may vary based on distro) as well as the usual C dev packages. Once these are installed, building is as simple as:
cd <project folder>
make simulator
This will produce a build folder with a .so file that’s ready to run on the Simulator. Make options are:
simulator
-
builds a .so file for the Simulator
pdc
-
builds a .pdx bundle that will run in the Simulator
device
-
builds a .bin file for the device
- <none>
-
builds a .pdx bundle suitable for both the Simulator and device
5. Building on Windows
5.1. Visual Studio/CMake
These instructions will show you how to build the example C projects included with the Playdate SDK which include CMakeLists. You can use our CMake files as a jumping off point for your own projects. Make is not supported in our example projects on Windows.
5.2. Install Development Tools
-
Install Visual Studio 2019 or 2022 with C tools
-
Install the GNU Arm Embedded Toolchain compiler
gcc-arm-none-eabi
from developer.arm.com, when prompted add to WindowsPATH
environment variable -
Install CMake for Windows from cmake.org, when prompted add to Windows
PATH
environment variable. You may also use the CMake that comes with Visual Studio.
5.3. Set PLAYDATE_SDK_PATH
Environment Variable
The CMake build files find the Playdate SDK location by using an environment variable. To set it do the following:
-
From the Start Menu type
Environment Variables
and open the System Properties panel -
Press the Environment Variables… button
-
Press New… and add
PLAYDATE_SDK_PATH
and select the location of your Playdate SDK directory
Tip
|
You may also want to add <path to SDK>/bin to your $PATH variable. This allows running pdc , pdutil and the Simulator from any location without a fully qualified path.
|
Building
The Simulator only supports 64 bit binary files. To ensure building 64 bit you’ll need to use the 64 bit Developer Command Prompt. To open this, select the Start Menu and find the application "x64 Native Tools Command Prompt for VS 2019". The name may vary based on which version of Visual Studio you have. It also can be opened from the Visual Studio Tools menu.
5.4. Building for the Simulator using Visual Studio
-
Navigate into the project directory and create a
build
folder, this is where CMake will generate its project files -
Open a Visual Studio Developer Command Prompt from the Start Menu or from within Visual Studio
-
In the developer command prompt window, navigate into
build
directory and typecmake ..
-
Open the Visual Studio project generated in the
build
directory with the project name -
In the Solutions Explorer within Visual Studio, select the target with the project name, not a meta target.
-
Right-click and select "Set as Startup Project"
-
Build and Run will create a .pdx file at the root level of the project directory and start a debugging session with the active .pdx by opening the Simulator.
5.5. Building for the Simulator using NMake
If you’d like to use VSCode or a different IDE you can also build using NMake.
-
Navigate into the project directory and create a
build
folder, this is where cmake will generate its project files -
Open a Visual Studio Developer Command Prompt from the Start Menu or from within Visual Studio
-
In the developer command prompt window, navigate into build directory and type
cmake .. -G "NMake Makefiles"
-
Type
nmake
to build the project. This will create a .pdx file at the root level of the project directory which can be run in the Simulator.
5.6. Building for the Playdate using NMake
-
Navigate into the project directory and create a
build
folder, this is where cmake will generate its project files -
Open a Visual Studio Developer Command Prompt from the Start Menu or from within Visual Studio
-
In the developer command prompt window, navigate into build directory and type
cmake .. -G "NMake Makefiles" --toolchain=<path to SDK>/C_API/buildsupport/arm.cmake
-
Type
nmake
to build the project. This will create a .pdx file at the root level of the project directory which can be run on the Playdate.
Building for Release
When you’re ready to do a release build, regenerate the build targets by passing -DCMAKE_BUILD_TYPE=Release
argument to CMake.
Note
|
This does an optimized build with the current tool chain. To release build for Playdate hardware, you will need to use the ARM tool chain listed above. In Visual Studio this argument is ignored; to do a release build with Visual Studio select Release from the build popup menu. |
6. Game Initialization
When building for the device a pdex.bin
file is created. The game launcher looks for this file in the compiled .pdx bundle and loads it into memory if it exists. (Because the Playdate OS doesn’t have a dynamic loader, the bin file is compiled for a specific location in memory; therefore, you can only have one bin/dylib per pdx bundle.).
Note
|
On Windows, your event handler must be exported, you do this by adding the attribute __declspec(dllexport) to the function.
|
Your code should implement the function:
int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg);
typedef enum { kEventInit, kEventInitLua, kEventLock, kEventUnlock, kEventPause, kEventResume, kEventTerminate, kEventKeyPressed, kEventKeyReleased, kEventLowPower } PDSystemEvent;
When your eventHandler
function is called, the playdate argument will contain a pointer to the PlaydateAPI struct. The PlaydateAPI struct is your game’s interface back to the Playdate runtime, containing functions for accessing the sound system, display, filesystem, etc.
After loading pdex.bin
into memory, the system calls your eventHandler()
with event set to kEventInit
. If your game is implemented entirely in C code, you can supply your own run loop update function by calling playdate->system->setUpdateCallback() here.
If you don’t provide an update callback, the system initializes a Lua context and calls eventHandler()
again with event equal to kEventInitLua
. At this point, you can use playdate->lua->addFunction() and playdate->lua->registerClass() to extend the Lua runtime. Note that this happens before main.lua is loaded and run.
When an arbitrary key is pressed or released in the Simulator this function is called with event set to kEventKeyPressed
or kEventKeyReleased
and the keycode of the key in the arg argument.
Finally, this function can also handle lifetime events like device lock and unlock, as well as game pause, resume, terminate, and low-power sleep.
7. API reference
7.1. Utility functions
Memory allocation
Allocates heap space if ptr is NULL, else reallocates the given pointer. If size is zero, frees the given pointer.
Logging
Calls the log function, outputting an error in red to the console, then pauses execution.
Calls the log function.
Equivalent to print()
in the Lua API.
Interacting with the System Menu
Your game can add up to three menu items to the system menu. Three types of menu items are supported: normal action menu items, checkmark menu items, and options menu items.
PDMenuItem *menuItem = pd->system->addMenuItem("Item 1", menuItemCallback, NULL);
PDMenuItem *checkMenuItem = pd->system->addCheckmarkMenuItem("Item 2", 1, menuCheckmarkCallback, NULL);
const char *options[] = {"one", "two", "three"};
PDMenuItem *optionMenuItem = pd->system->addOptionsMenuItem("Item 3", options, 3, menuOptionsCallback, NULL);
title will be the title displayed by the menu item.
Adds a new menu item to the System Menu. When invoked by the user, this menu item will:
-
Invoke your callback function.
-
Hide the System Menu.
-
Unpause your game and call eventHandler() with the kEventResume event.
Your game can then present an options interface to the player, or take other action, in whatever manner you choose.
Adds a new menu item that can be checked or unchecked by the player.
title will be the title displayed by the menu item.
value should be 0 for unchecked, 1 for checked.
If this menu item is interacted with while the system menu is open, callback will be called when the menu is closed.
Adds a new menu item that allows the player to cycle through a set of options.
title will be the title displayed by the menu item.
options should be an array of strings representing the states this menu item can cycle through. Due to limited horizontal space, the option strings and title should be kept short for this type of menu item.
optionsCount should be the number of items contained in options.
If this menu item is interacted with while the system menu is open, callback will be called when the menu is closed.
Removes the menu item from the system menu.
Removes all custom menu items from the system menu.
Gets or sets the display title of the menu item.
Gets or sets the integer value of the menu item.
For checkmark menu items, 1 means checked, 0 unchecked. For option menu items, the value indicates the array index of the currently selected option.
Gets or sets the userdata value associated with this menu item.
Time and Date
Returns the number of milliseconds since…some arbitrary point in time. This should present a consistent timebase while a game is running, but the counter will be disabled when the device is sleeping.
Returns the number of seconds (and sets milliseconds if not NULL) elapsed since midnight (hour 0), January 1, 2000.
Resets the high-resolution timer.
Returns the number of seconds since playdate.resetElapsedTime()
was called. The value is a floating-point number with microsecond accuracy.
Returns the system timezone offset from GMT, in seconds.
struct PDDateTime { uint16_t year; uint8_t month; // 1-12 uint8_t day; // 1-31 uint8_t weekday; // 1=monday-7=sunday uint8_t hour; uint8_t minute; uint8_t second; };
Converts the given epoch time to a PDDateTime.
Converts the given PDDateTime to an epoch time.
Returns 1 if the user has set the 24-Hour Time preference in the Settings program.
Miscellaneous
Returns 1 if the global "flipped" system setting is set, otherwise 0.
Returns 1 if the global "reduce flashing" system setting is set, otherwise 0.
Allocates a buffer ret and formats a string. Note that the caller is responsible for freeing ret.
A game can optionally provide an image to be displayed alongside the system menu. bitmap must be a 400x240 LCDBitmap. All important content should be in the left half of the image in an area 200 pixels wide, as the menu will obscure the rest. The right side of the image will be visible briefly as the menu animates in and out.
Optionally, a non-zero xoffset, can be provided. This must be a number between 0 and 200 and will cause the menu image to animate to a position offset left by xoffset pixels as the menu is animated in.
This function could be called in response to the kEventPause event in your implementation of eventHandler().
int PDCallbackFunction(void* userdata);
Replaces the default Lua run loop function with a custom update function. The update function should return a non-zero number to tell the system to update the display, or zero if update isn’t needed.
Calculates the current frames per second and draws that value at x, y.
Returns a value from 0-100 denoting the current level of battery charge. 0 = empty; 100 = full.
Returns the battery’s current voltage level.
Flush the CPU instruction cache, on the very unlikely chance you’re modifying instruction code on the fly. (If you don’t know what I’m talking about, you don’t need this. :smile:)
7.2. Audio
Returns the sound engine’s current time value, in units of sample frames, 44,100 per second.
Equivalent to playdate.sound.getCurrentTime()
in the Lua API.
Returns the default channel, where sound sources play if they haven’t been explicity assigned to a different channel.
Adds the given channel to the sound engine. Returns 1 if the channel was added, 0 if it was already in the engine.
The callback function you pass in will be called every audio render cycle.
int AudioSourceFunction(void* context, int16_t* left, int16_t* right, int len)
This function should fill the passed-in left buffer (and right if it’s a stereo source) with len samples each and return 1, or return 0 if the source is silent through the cycle.
Removes the given SoundSource object from its channel, whether it’s in the default channel or a channel created with playdate→sound→addChannel(). Returns 1 if a source was removed, 0 if the source wasn’t in a channel.
Removes the given channel from the sound engine. Returns 1 if the channel was successfully removed, 0 if the channel is the default channel or hadn’t been previously added.
The callback you pass in will be called every audio cycle.
int AudioInputFunction(void* context, int16_t* data, int len)
Your input callback will be called with the recorded audio data, a monophonic stream of samples. The function should return 1 to continue recording, 0 to stop recording. If forceInternal is set, the device microphone is used regardless of whether the headset has a microphone.
If headphone contains a pointer to an int, the value is set to 1 if headphones are currently plugged in. Likewise, mic is set if the headphones include a microphone. If changeCallback is provided, it will be called when the headset or mic status changes, and audio output will not automatically switch from speaker to headphones when headphones are plugged in (and vice versa). In this case, the callback should use playdate→sound→setOutputsActive()
to change the output if needed.
Equivalent to playdate.sound.getHeadphoneState()
in the Lua API.
Force audio output to the given outputs, regardless of headphone status.
Equivalent to playdate.sound.setOutputsActive()
in the Lua API.
Channels
Returns a new SoundChannel object.
Frees the given SoundChannel.
Adds a SoundSource to the channel. If a source is not assigned to a channel, it plays on the default global channel.
Removes a SoundSource to the channel. Returns 1 if the source was found in (and removed from) the channel, otherwise 0.
Adds a SoundEffect to the channel.
Removes a SoundEffect from the channel.
Sets the volume for the channel, in the range [0-1].
Gets the volume for the channel, in the range [0-1].
Sets a signal to modulate the channel volume.
Gets a signal modulating the channel volume.
Sets the pan parameter for the channel. Valid values are in the range [-1,1], where -1 is left, 0 is center, and 1 is right.
Sets a signal to modulate the channel pan.
Gets a signal modulating the channel pan.
Creates a new SoundSource using the given data provider callback and adds it to the channel.
int AudioSourceFunction(void* context, int16_t* left, int16_t* right, int len)
This function should fill the passed-in left buffer (and right if it’s a stereo source) with len samples each and return 1, or return 0 if the source is silent through the cycle. The caller takes ownership of the allocated SoundSource, and should free it with
playdate->system->realloc(source, NULL);
when it is not longer in use.
Returns a signal that follows the volume of the channel before effects are applied.
Returns a signal that follows the volume of the channel after effects are applied.
SoundSource
SoundSource is the parent class of FilePlayer, SamplePlayer, PDSynth, DelayLineTap, and PDSynthInstrument. Any objects of those types can be cast to SoundSource type and used in these functions.
Sets the playback volume (0.0 - 1.0) for left and right channels of the source.
Gets the playback volume (0.0 - 1.0) for left and right channels of the source.
Returns 1 if the source is currently playing.
int sndCallbackProc(SoundSource* c);
Sets a function to be called when the source has finished playing.
AudioSample
Allocates and returns a new AudioSample with a buffer large enough to load a file of length bytes.
Loads the sound data from the file at path into an existing AudioSample, sample.
Allocates and returns a new AudioSample, with the sound data loaded in memory. If there is no file at path, the function returns null.
Returns a new AudioSample referencing the given audio data. The sample keeps a pointer to the data instead of copying it, so the data must remain valid while the sample is active. format is one of the following values:
typedef enum { kSound8bitMono = 0, kSound8bitStereo = 1, kSound16bitMono = 2, kSound16bitStereo = 3, kSoundADPCMMono = 4, kSoundADPCMStereo = 5 } SoundFormat;
pd_api_sound.h
also provides some helper macros and functions:
#define SoundFormatIsStereo(f) ((f)&1)
#define SoundFormatIs16bit(f) ((f)>=kSound16bitMono)
static inline uint32_t SoundFormat_bytesPerFrame(SoundFormat fmt);
Retrieves the sample’s data, format, sample rate, and data length.
Frees the given sample.
Returns the length, in seconds, of sample.
FilePlayer
Allocates a new FilePlayer.
Frees the given player.
Prepares player to stream the file at path. Returns 1 if the file exists, otherwise 0.
Pauses the file player.
Starts playing the file player. If repeat is greater than one, it loops the given number of times. If zero, it loops endlessly until it is stopped with playdate->sound->fileplayer->stop().
Returns one if player is playing, zero if not.
Sets the buffer length of player to bufferLen seconds;
Returns the length, in seconds, of the file loaded into player.
Sets a function to be called when playback has completed. This is an alias for playdate→sound→source→setFinishCallback().
typedef void sndCallbackProc(SoundSource* c);
Returns one if player has underrun, zero if not.
Sets the start and end of the loop region for playback, in seconds. If end is omitted, the end of the file is used.
Sets the current offset in seconds.
Gets the current offset in seconds for player.
Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. Unlike sampleplayers, fileplayers can’t play in reverse (i.e., rate < 0).
Gets the playback rate for player.
If flag evaluates to true, the player will restart playback (after an audible stutter) as soon as data is available.
Sets the playback volume for left and right channels of player.
Gets the left and right channel playback volume for player.
Stops playing the file.
Changes the volume of the fileplayer to left and right over a length of len sample frames, then calls the provided callback (if set).
SamplePlayer
Returns the length, in seconds, of the sample assigned to player.
Returns one if player is playing a sample, zero if not.
Allocates and returns a new SamplePlayer.
Starts playing the sample in player.
If repeat is greater than one, it loops the given number of times. If zero, it loops endlessly until it is stopped with playdate->sound->sampleplayer->stop(). If negative one, it does ping-pong looping.
Sets the playback rate for the sample. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc.
Sets a function to be called when playback has completed. See sndCallbackProc.
Sets the current offset of the SamplePlayer, in seconds.
Gets the current offset in seconds for player.
When used with a repeat of -1, does ping-pong looping, with a start and end position in frames.
Pauses or resumes playback.
Sets the playback rate for the player. 1.0 is normal speed, 0.5 is down an octave, 2.0 is up an octave, etc. A negative rate produces backwards playback for PCM files, but does not work for ADPCM-encoded files.
Gets the playback rate for player.
Assigns sample to player.
Sets the playback volume for left and right channels.
Gets the current left and right channel volume of the sampleplayer.
Stops playing the sample.
Frees the given player.
PDSynth
Creates a new synth object.
Frees a synth object, first removing it from the sound engine if needed.
Sets the waveform of the synth. The SoundWaveform enum contains the following values:
typedef enum { kWaveformSquare, kWaveformTriangle, kWaveformSine, kWaveformNoise, kWaveformSawtooth, kWaveformPOPhase, kWaveformPODigital, kWaveformPOVosim } SoundWaveform;
typedef int (*synthRenderFunc)(void* userdata, int32_t* left, int32_t* right, int nsamples, uint32_t rate, int32_t drate); typedef void (*synthNoteOnFunc)(void* userdata, MIDINote note, float velocity, float len); // len == -1 if indefinite typedef void (*synthReleaseFunc)(void* userdata, int ended); typedef int (*synthSetParameterFunc)(void* userdata, int parameter, float value); typedef void (*synthDeallocFunc)(void* userdata);
Provides custom waveform generator functions for the synth. synthRenderFunc, the data provider callback, is the only required function. left (and right if setGenerator() was called with the stereo flag set) are sample buffers in Q8.24 format. rate is the amount to change a (Q32) phase accumulator each sample, and drate is the amount to change rate each sample. Custom synths can safely ignore this and use the note paramter in the noteOn function to handle pitch, but synth→setFrequencyModulator() won’t work as expected. These functions are called on the audio render thread, so they should return as quickly as possible.
Provides a sample for the synth to play. Sample data must be uncompressed PCM, not ADPCM.
Sets the parameters of the synth’s ADSR envelope.
Returns the synth’s envelope. The PDSynth object owns this envelope, so it must not be freed.
Transposes the synth’s output by the given number of half steps. For example, if the transpose is set to 2 and a C note is played, the synth will output a D instead.
Sets or gets a signal to modulate the synth’s frequency. The signal is scaled so that a value of 1 doubles the synth pitch (i.e. an octave up) and -1 halves it (an octave down).
Sets or gets a signal to modulate the synth’s output amplitude.
Returns the number of parameters advertised by the synth.
Sets the (1-based) parameter at position num to the given value. Returns 0 if num is not a valid parameter index.
Sets or gets a signal to modulate the parameter at index num.
Plays a note on the synth, at the given frequency. Specify len = -1 to leave the note playing until a subsequent noteOff() call. If when is 0, the note is played immediately, otherwise the note is scheduled for the given time. Use playdate→sound→getCurrentTime() to get the current time.
The same as playNote but uses MIDI note (where 60 = C4) instead of frequency.
Sends a note off event to the synth, either immediately (when = 0) or at the scheduled time.
Sets and gets the playback volume (0.0 - 1.0) for left and right channels of the synth. This is equivalent to
playdate->sound->source->setVolume((SoundSource*)synth, lvol, rvol); playdate->sound->source->getVolume((SoundSource*)synth, &lvol, &rvol);
Returns 1 if the synth is currently playing.
PDSynthInstrument
PDSynthInstrument collects a number of PDSynth objects together to provide polyphony.
Creates a new PDSynthInstrument object.
Frees the given instrument, first removing it from the sound engine if needed.
Adds the given PDSynth to the instrument. The synth will respond to playNote events between rangeState and rangeEnd, inclusive. The transpose argument is in half-step units, and is added to the instrument’s transpose parameter. The function returns 1 if successful, or 0 if the synth is already in another instrument or channel.
The instrument passes the playNote/playMIDINote() event to the synth in its collection that has been off for the longest, or has been playing longest if all synths are currently playing. See also playdate→sound→synth→playNote(). The PDSynth that received the playNote event is returned.
Forwards the noteOff() event to the synth currently playing the given note. See also playdate→sound→synth→noteOff().
Sets the pitch bend and pitch bend range to be applied to the voices in the instrument.
Sets the transpose parameter for all voices in the instrument.
Sends a noteOff event to all voices in the instrument.
Sets and gets the playback volume (0.0 - 1.0) for left and right channels of the synth. This is equivalent to
playdate->sound->source->setVolume((SoundSource*)instrument, lvol, rvol); playdate->sound->source->getVolume((SoundSource*)instrument, &lvol, &rvol);
Returns the number of voices in the instrument currently playing.
Signals
A PDSynthSignalValue represents a signal that can be used as a modulator. Its PDSynthSignal subclass is used for "active" signals that change their values automatically. PDSynthLFO and PDSynthEnvelope are subclasses of PDSynthSignal.
typedef float (*signalStepFunc)(void* userdata, int* iosamples, float* ifval); typedef void (*signalNoteOnFunc)(void* userdata, MIDINote note, float vel, float len); // len = -1 for indefinite typedef void (*signalNoteOffFunc)(void* userdata, int stopped, int offset); // ended = 0 for note release, = 1 when note stops playing typedef void (*signalDeallocFunc)(void* userdata);
Provides a custom implementation for the signal. signalStepFunc step is the only required function, returning the value at the end of the current frame. When called, the ioframes pointer contains the number of samples until the end of the frame. If the signal needs to provide a value in the middle of the frame (e.g. an LFO that needs to be sample-accurate) it should return the "interframe" value in ifval and set iosamples to the sample offset of the value. The functions are called on the audio render thread, so they should return as quickly as possible.
Frees a signal created with playdate→sound→signal→newSignal().
Returns the current output value of signal. The signal can be a custom signal created with newSignal(), or any of the PDSynthSignal subclasses.
Offsets the signal’s output by the given amount.
Scales the signal’s output by the given factor. The scale is applied before the offset.
LFO
Returns a new LFO object, which can be used to modulate sounds. The type argument is one of the following values:
typedef enum { kLFOTypeSquare, kLFOTypeTriangle, kLFOTypeSine, kLFOTypeSampleAndHold, kLFOTypeSawtoothUp, kLFOTypeSawtoothDown, kLFOTypeArpeggiator, kLFOTypeFunction } LFOType;
Frees the LFO.
Sets the LFO shape to one of the values given above.
Sets the LFO’s rate, in cycles per second.
Sets the LFO’s phase.
Sets the center or depth of the LFO.
Sets the LFO type to arpeggio, where the given values are in half-steps from the center note. For example, the sequence (0, 4, 7, 12) plays the notes of a major chord.
Provides a custom function for LFO values.
Sets an initial holdoff time for the LFO where the LFO remains at its center value, and a ramp time where the value increases linearly to its maximum depth. Values are in seconds.
If retrigger is on, the LFO’s phase is reset to 0 when a synth using the LFO starts playing a note.
Return the current output value of the LFO.
If global is set, the LFO is continuously updated whether or not it’s currently in use.
Envelope
Creates a new envelope with the given parameters.
Frees the envelope.
Sets the ADSR parameters of the envelope.
Smoothly changes the envelope’s shape from linear (amount=0) to exponential (amount=1).
Changes the amount by which note velocity scales output level. At the default value of 1, output is proportional to velocity; at 0 velocity has no effect on output level.
Scales the envelope rate according to the played note. For notes below start
, the envelope’s set rate is used; for notes above end
envelope rates are scaled by the scaling
parameter. Between the two notes the scaling factor is interpolated from 1.0 to scaling
.
Sets whether to use legato phrasing for the envelope. If the legato flag is set, when the envelope is re-triggered before it’s released, it remains in the sustain phase instead of jumping back to the attack phase.
If retrigger is on, the envelope always starts from 0 when a note starts playing, instead of the current value if it’s active.
Return the current output value of the envelope.
SoundEffect
SoundEffect is the parent class of the sound effect types TwoPoleFilter, OnePoleFilter, BitCrusher, RingModulator, Overdrive, and DelayLine
typedef int effectProc(SoundEffect* e, int32_t* left, int32_t* right, int nsamples, int bufactive);
Creates a new effect using the given processing function. bufactive is 1 if samples have been set in the left or right buffers. The function should return 1 if it changed the buffer samples, otherwise 0. left and right (if the effect is on a stereo channel) are sample buffers in Q8.24 format.
Frees the given effect.
Sets the wet/dry mix for the effect. A level of 1 (full wet) replaces the input with the effect output; 0 leaves the effect out of the mix (which is useful if you’re using a delay line with taps and don’t want to hear the delay line itself).
Sets or gets a signal to modulate the effect’s mix level.
Sets or gets a userdata value for the effect.
TwoPoleFilter
Creates a new two pole filter effect.
Frees the given filter.
typedef enum { kFilterTypeLowPass, kFilterTypeHighPass, kFilterTypeBandPass, kFilterTypeNotch, kFilterTypePEQ, kFilterTypeLowShelf, kFilterTypeHighShelf } TwoPoleFilterType;
Sets the type of the filter.
Sets the center/corner frequency of the filter. Value is in Hz.
Sets or gets a signal to modulate the effect’s frequency. The signal is scaled so that a value of 1.0 corresponds to half the sample rate.
Sets the filter gain.
Sets the filter resonance.
Sets or gets a signal to modulate the filter resonance.
OnePoleFilter
The one pole filter is a simple low/high pass filter, with a single parameter describing the cutoff frequency.
Creates a new one pole filter.
Frees the filter.
Sets the filter’s single parameter (cutoff frequency) to p. Values above 0 (up to 1) are high-pass, values below 0 (down to -1) are low-pass.
Sets or gets a signal to modulate the filter parameter.
BitCrusher
Returns a new BitCrusher effect.
Frees the given effect.
Sets the amount of crushing to amount. Valid values are 0 (no effect) to 1 (quantizing output to 1-bit).
Sets or gets a signal to modulate the crushing amount.
Sets the number of samples to repeat, quantizing the input in time. A value of 0 produces no undersampling, 1 repeats every other sample, etc.
Sets or gets a signal to modulate the undersampling amount.
RingModulator
Returns a new ring modulator effect.
[[f-sound.effect.ringModulator.freeRingmod] .void playdate->sound->effect->ringModulator->freeRingmod(RingModulator* filter)+*`
Frees the given effect.
Sets the frequency of the modulation signal.
Sets or gets a signal to modulate the frequency of the ring modulator.
Overdrive
Returns a new overdrive effect.
Frees the given effect.
Sets the gain of the overdrive effect.
Sets the level where the amplified input clips.
Sets or gets a signal to modulate the limit parameter.
Adds an offset to the upper and lower limits to create an asymmetric clipping.
Sets or gets a signal to modulate the offset parameter.
DelayLine
Creates a new delay line effect. The length parameter is given in samples.
Frees the delay line.
Changes the length of the delay line, clearing its contents.
Sets the feedback level of the delay line.
Returns a new tap on the delay line, at the given position. delay must be less than or equal to the length of the delay line.
DelayLineTap
Note that DelayLineTap is a SoundSource, not a SoundEffect. A delay line tap can be added to any channel, not only the channel the delay line is on.
Frees a tap previously created with playdate→sound→delayLine→addTap(), first removing it from the sound engine if needed.
Sets the position of the tap on the delay line, up to the delay line’s length.
Sets a signal to modulate the tap delay. If the signal is continuous (e.g. an envelope or a triangle LFO, but not a square LFO) playback is sped up or slowed down to compress or expand time.
If the delay line is stereo and flip is set, the tap outputs the delay line’s left channel to its right output and vice versa.
SoundSequence
Creates or destroys a SoundSequence object.
If the sequence is empty, attempts to load data from the MIDI file at path into the sequence. Returns 1 on success, 0 on failure.
Starts or stops playing the sequence. finishCallback
is an optional function to be called when the sequence finishes playing or is stopped.
typedef void (*SequenceFinishedCallback)(SoundSequence* seq, void* userdata);
Returns 1 if the sequence is currently playing, otherwise 0.
Gets or sets the current time in the sequence, in samples since the start of the file. Note that which step this moves the sequence to depends on the current tempo.
Sets the looping range of the sequence. If loops is 0, the loop repeats endlessly.
Sets or gets the tempo of the sequence, in steps per second.
Returns the length of the longest track in the sequence, in steps. See also playdate.sound.track.getLength().
Returns the number of tracks in the sequence.
Adds the given playdate.sound.track to the sequence.
Sets or gets the given SoundTrack object at position idx in the sequence.
Sends a stop signal to all playing notes on all tracks.
Returns the step number the sequence is currently at. If timeOffset is not NULL, its contents are set to the current sample offset within the step.
Set the current step for the sequence. timeOffset is a sample offset within the step. If playNotes is set, notes at the given step (ignoring timeOffset) are played.
ControlSignal
ControlSignal is a subclass of PDSynthSignal used for sequencing changes to parameters.
Creates a new control signal object.
Frees the given signal.
Clears all events from the given signal.
Adds a value to the signal’s timeline at the given step. If interpolate is set, the value is interpolated between the previous step+value and this one.
Removes the control event at the given step.
Returns the MIDI controller number for this ControlSignal, if it was created from a MIDI file via playdate→sound→sequence→loadMIDIFile().
SequenceTrack
A SequenceTrack comprises a PDSynthInstrument, a sequence of notes to play on that instrument, and any number of ControlSignal objects to control parameter changes.
Returns a new SequenceTrack.
Frees the SequenceTrack.
Sets or gets the PDSynthInstrument assigned to the track.
Adds a single note event to the track.
Removes the event at step playing note.
Clears all notes from the track.
Returns the length, in steps, of the track—that is, the step where the last note in the track ends.
Returns the internal array index for the first note at the given step.
If the given index is in range, sets the data in the out pointers and returns 1; otherwise, returns 0.
Returns the number of ControlSignal objects in the track.
Returns the ControlSignal at index idx.
Returns the ControlSignal for MIDI controller number controller, creating it if the create flag is set and it doesn’t yet exist.
Clears all ControlSignals from the track.
Returns the number of voices currently playing in the track’s instrument.
Returns the maximum number of simultaneously playing notes in the track. (Currently, this value is only set when the track was loaded from a MIDI file. We don’t yet track polyphony for user-created events.)
Mutes or unmutes the track.
7.3. Display
Returns the height of the display, taking the current scale into account; e.g., if the scale is 2, this function returns 120 instead of 240.
Equivalent to playdate.display.getHeight()
in the Lua API.
Returns the width of the display, taking the current scale into account; e.g., if the scale is 2, this function returns 200 instead of 400.
Equivalent to playdate.display.getWidth()
in the Lua API.
If flag evaluates to true, the frame buffer is drawn inverted—black instead of white, and vice versa.
Equivalent to playdate.display.setInverted()
in the Lua API.
Adds a mosaic effect to the display. Valid x and y values are between 0 and 3, inclusive.
Equivalent to playdate.display.setMosaic
in the Lua API.
Flips the display on the x or y axis, or both.
Equivalent to playdate.display.setFlipped()
in the Lua API.
Sets the nominal refresh rate in frames per second. Default is 20 fps, the maximum rate supported by the hardware for full-frame updates.
Equivalent to playdate.display.setRefreshRate()
in the Lua API.
Sets the display scale factor. Valid values for scale are 1, 2, 4, and 8.
The top-left corner of the frame buffer is scaled up to fill the display; e.g., if the scale is set to 4, the pixels in rectangle [0,100] x [0,60] are drawn on the screen as 4 x 4 squares.
Equivalent to playdate.display.setScale()
in the Lua API.
Offsets the display by the given amount. Areas outside of the displayed area are filled with the current background color.
Equivalent to playdate.display.setOffset()
in the Lua API.
7.4. Filesystem
Returns human-readable text describing the most recent error (usually indicated by a -1 return from a filesystem function).
Calls the given callback function for every file at path. Subfolders are indicated by a trailing slash '/' in filename. listfiles() does not recurse into subfolders. If showhidden is set, files beginning with a period will be included; otherwise, they are skipped. Returns 0 on success, -1 if no folder exists at path or it can’t be opened.
Equivalent to playdate.file.listFiles()
in the Lua API.
Deletes the file at path. Returns 0 on success, or -1 in case of error. If recursive is 1 and the target path is a folder, this deletes everything inside the folder (including folders, folders inside those, and so on) as well as the folder itself.
Creates the given path in the Data/<gameid> folder. It does not create intermediate folders. Returns 0 on success, or -1 in case of error.
Equivalent to playdate.file.mkdir()
in the Lua API.
Renames the file at from to to. It will overwrite the file at to without confirmation. It does not create intermediate folders. Returns 0 on success, or -1 in case of error.
Equivalent to playdate.file.rename()
in the Lua API.
Populates the FileStat stat with information about the file at path. Returns 0 on success, or -1 in case of error.
typedef struct { int isdir; unsigned int size; int m_year; int m_month; int m_day; int m_hour; int m_minute; int m_second; } FileStat;
Files
Closes the given file handle. Returns 0 on success, or -1 in case of error.
Equivalent to playdate.file.close()
in the Lua API.
Flushes the output buffer of file immediately. Returns the number of bytes written, or -1 in case of error.
Equivalent to playdate.file.flush()
in the Lua API.
Opens a handle for the file at path. The kFileRead mode opens a file in the game pdx, while kFileReadData searches the game’s data folder; to search the data folder first then fall back on the game pdx, use the bitwise combination kFileRead|kFileReadData.kFileWrite and kFileAppend always write to the data folder. The function returns NULL if a file at path cannot be opened, and playdate->file->geterr() will describe the error. The filesystem has a limit of 64 simultaneous open files.
typedef enum { kFileRead, kFileReadData, kFileWrite, kFileAppend } FileOptions;
Equivalent to playdate.file.open()
in the Lua API.
Reads up to len bytes from the file into the buffer buf. Returns the number of bytes read (0 indicating end of file), or -1 in case of error.
Equivalent to playdate.file.file:read()
in the Lua API.
Sets the read/write offset in the given file handle to pos, relative to the whence macro. SEEK_SET is relative to the beginning of the file, SEEK_CUR is relative to the current position of the file pointer, and SEEK_END is relative to the end of the file. Returns 0 on success, -1 on error.
Equivalent to playdate.file.file:seek()
in the Lua API.
Returns the current read/write offset in the given file handle, or -1 on error.
Equivalent to playdate.file.file:tell()
in the Lua API.
Writes the buffer of bytes buf to the file. Returns the number of bytes written, or -1 in case of error.
Equivalent to playdate.file.file:write()
in the Lua API.
7.5. Graphics
The drawing functions use a context stack to select the drawing target, for setting a stencil, changing the draw mode, etc. The stack is unwound at the beginning of each update cycle, with drawing restored to target the display.
Push a new drawing context for drawing into the given bitmap. If target is nil, the drawing functions will use the display framebuffer.
Equivalent to playdate.graphics.pushContext()
in the Lua API.
Pops a context off the stack (if any are left), restoring the drawing settings from before the context was pushed.
Equivalent to playdate.graphics.popContext()
in the Lua API.
Sets the stencil used for drawing. For a tiled stencil, use setStencilImage() instead.
Sets the stencil used for drawing. If the tile flag is set the stencil image will be tiled. Tiled stencils must have width equal to a multiple of 32 pixels.
Equivalent to playdate.graphics.setStencilImage()
in the Lua API.
Sets the mode used for drawing bitmaps. Note that text drawing uses bitmaps, so this affects how fonts are displayed as well.
typedef enum { kDrawModeCopy, kDrawModeWhiteTransparent, kDrawModeBlackTransparent, kDrawModeFillWhite, kDrawModeFillBlack, kDrawModeXOR, kDrawModeNXOR, kDrawModeInverted } LCDBitmapDrawMode;
Equivalent to playdate.graphics.setImageDrawMode()
in the Lua API.
Sets the current clip rect, using world coordinates—that is, the given rectangle will be translated by the current drawing offset. The clip rect is cleared at the beginning of each update.
Equivalent to playdate.graphics.setClipRect()
in the Lua API.
Sets the current clip rect in screen coordinates.
Equivalent to playdate.graphics.setScreenClipRect()
in the Lua API.
Clears the current clip rect.
Equivalent to playdate.graphics.clearClipRect()
in the Lua API.
Sets the end cap style used in the line drawing functions.
typedef enum { kLineCapStyleButt, kLineCapStyleSquare, kLineCapStyleRound } LCDLineCapStyle;
Equivalent to playdate.graphics.setLineCapStyle()
in the Lua API.
Sets the font to use in subsequent drawText calls.
Equivalent to playdate.graphics.setFont()
in the Lua API.
Sets the tracking to use when drawing text.
Equivalent to playdate.graphics.font:setTracking()
in the Lua API.
Sets the leading adjustment (added to the leading specified in the font) to use when drawing text.
Equivalent to playdate.graphics.font:setLeading()
in the Lua API.
Supporting types
Either an LCDSolidColor or an LCDPattern*.
typedef enum { kColorBlack, kColorWhite, kColorClear, kColorXOR } LCDSolidColor;
typedef uint8_t LCDPattern[16];
typedef enum { kBitmapUnflipped, kBitmapFlippedX, kBitmapFlippedY, kBitmapFlippedXY } LCDBitmapFlip;
typedef struct { int left; int right; int top; int bottom; } LCDRect;
typedef struct { float x; float y; float width; float height; } PDRect;
Bitmaps
Clears bitmap, filling with the given bgcolor.
Returns a new LCDBitmap that is an exact copy of bitmap.
Returns 1 if any of the opaque pixels in bitmap1 when positioned at x1, y1 with flip1 overlap any of the opaque pixels in bitmap2 at x2, y2 with flip2 within the non-empty rect, or 0 if no pixels overlap or if one or both fall completely outside of rect.
Draws the bitmap with its upper-left corner at location x, y, using the given flip orientation.
Draws the bitmap scaled to xscale and yscale with its upper-left corner at location x, y. Note that flip is not available when drawing scaled bitmaps but negative scale values will achieve the same effect.
Draws the bitmap scaled to xscale and yscale then rotated by degrees with its center as given by proportions centerx and centery at x, y; that is: if centerx and centery are both 0.5 the center of the image is at (x,y), if centerx and centery are both 0 the top left corner of the image (before rotation) is at (x,y), etc.
Frees the given bitmap.
Gets various info about bitmap including its width and height and raw pixel data. The data is 1 bit per pixel packed format, in MSB order; in other words, the high bit of the first byte in data
is the top left pixel of the image. If the bitmap has a mask, a pointer to its data is returned in mask, else NULL is returned.
Allocates and returns a new LCDBitmap from the file at path. If there is no file at path, the function returns null.
Loads the image at path into the previously allocated bitmap.
Allocates and returns a new width by height LCDBitmap filled with bgcolor.
Draws the bitmap with its upper-left corner at location x, y tiled inside a width by height rectangle.
Returns a new, rotated and scaled LCDBitmap based on the given bitmap.
Sets a mask image for the given bitmap. The set mask must be the same size as the target bitmap.
Gets a mask image for the given bitmap. If the image doesn’t have a mask, getBitmapMask returns NULL.
BitmapTables
Returns the idx bitmap in table, If idx is out of bounds, the function returns NULL.
Allocates and returns a new LCDBitmap from the file at path. If there is no file at path, the function returns null.
Loads the imagetable at path into the previously allocated table.
Allocates and returns a new LCDBitmapTable that can hold count width by height LCDBitmaps.
Fonts & Text
Draws the given text using the provided options. If no font has been set with setFont, the default system font Asheville Sans 14 Light is used.
Equivalent to playdate.graphics.drawText()
in the Lua API.
Returns the height of the given font.
Returns an LCDFontPage object for the given character code. Each LCDFontPage contains information for 256 characters; specifically, if (c1 & ~0xff) == (c2 & ~0xff)
, then c1 and c2 belong to the same page and the same LCDFontPage can be used to fetch the character data for both instead of searching for the page twice.
Returns an LCDFontGlyph object for character c in LCDFontPage page, and optionally returns the glyph’s bitmap and advance value.
Returns the kerning adjustment between characters c1 and c2 as specified by the font.
Returns the width of the given text in the given font.
typedef enum { kASCIIEncoding, kUTF8Encoding, k16BitLEEncoding } PDStringEncoding;
Returns the LCDFont object for the font file at path. In case of error, outErr points to a string describing the error.
Returns an LCDFont object wrapping the LCDFontData data comprising the contents (minus 16-byte header) of an uncompressed pft file. wide corresponds to the flag in the header indicating whether the font contains glyphs at codepoints above U+1FFFF.
Geometry
Draws an ellipse inside the rectangle {x, y, width, height} of width lineWidth (inset from the rectangle bounds). If startAngle != _endAngle, this draws an arc between the given angles. Angles are given in degrees, clockwise from due north.
Fills an ellipse inside the rectangle {x, y, width, height}. If startAngle != _endAngle, this draws a wedge/Pacman between the given angles. Angles are given in degrees, clockwise from due north.
Draws a line from x1, y1 to x2, y2 with a stroke width of width.
Equivalent to playdate.graphics.drawLine()
in the Lua API.
Draws a width by height rect at x, y.
Equivalent to playdate.graphics.drawRect()
in the Lua API.
Draws a filled width by height rect at x, y.
Equivalent to playdate.graphics.fillRect()
in the Lua API.
Draws a filled triangle with points at x1, y1, x2, y2, and x3, y3.
typedef enum { kPolygonFillNonZero, kPolygonFillEvenOdd } LCDPolygonFillRule;
Equivalent to playdate.graphics.fillTriangle()
in the Lua API.
Fills the polygon with vertices at the given coordinates (an array of 2*nPoints
ints containing alternating x and y values) using the given color and fill, or winding, rule. See https://en.wikipedia.org/wiki/Nonzero-rule for an explanation of the winding rule.
Equivalent to playdate.graphics.fillPolygon()
in the Lua API.
Miscellaneous
Clears the entire display, filling it with color.
Equivalent to playdate.graphics.clear()
in the Lua API.
Sets the background color shown when the display is offset or for clearing dirty areas in the sprite system.
Equivalent to playdate.graphics.setBackgroundColor()
in the Lua API.
Manually flushes the current frame buffer out to the display. This function is automatically called after each pass through the run loop, so there shouldn’t be any need to call it yourself.
Only valid in the Simulator, returns the debug framebuffer as a bitmap. Function is NULL on device.
Returns the raw bits in the display buffer, the last completed frame.
Returns a bitmap containing the contents of the display buffer. The system owns this bitmap—do not free it!
Returns the current display frame buffer. Rows are 32-bit aligned, so the row stride is 52 bytes, with the extra 2 bytes per row ignored. Bytes are MSB-ordered; i.e., the pixel in column 0 is the 0x80 bit of the first byte of the row.
Returns a copy the contents of the working frame buffer as a bitmap. The caller is responsible for freeing the returned bitmap with playdate->graphics->freeBitmap().
Returns the current language of the system.
typedef enum { kPDLanguageEnglish, kPDLanguageJapanese, kPDLanguageUnknown, } PDLanguage;
After updating pixels in the buffer returned by getFrame(), you must tell the graphics system which rows were updated. This function marks a contiguous range of rows as updated (e.g., markUpdatedRows(0,LCD_ROWS-1) tells the system to update the entire display). Both “start” and “end” are included in the range.
Offsets the origin point for all drawing calls to x, y (can be negative).
This is useful, for example, for centering a "camera" on a sprite that is moving around a world larger than the screen.
Equivalent to playdate.graphics.setDrawOffset()
in the Lua API.
Installs a native drawing function for the given sprite.
typedef void LCDSpriteDrawFunction(LCDSprite* sprite, PDRect bounds, PDRect drawrect);
bounds is the current bounds of the sprite, drawrect is the area to draw.
Sets color to an 8 x 8 pattern using the given bitmap. x, y indicates the top left corner of the 8 x 8 pattern.
7.6. Video
Opens the pdv file at path and returns a new video player object for rendering its frames.
Frees the given video player.
Sets the rendering destination for the video player to the given bitmap. If the function fails, it returns 0 and sets an error message that can be read via getError().
Gets the rendering destination for the video player. If no rendering context has been setallocates a context bitmap with the same dimensions as the vieo will be allocated.
Sets the rendering destination for the video player to the screen.
Renders frame number n into the current context. In case of error, the function returns 0 and sets an error message that can be read via getError().
Returns text describing the most recent error.
Retrieves information about the video, by passing in (possibly NULL) value pointers.
7.7. Input
By default, the accelerometer is disabled to save (a small amount of) power. To use a peripheral, it must first be enabled via this function. Accelerometer data is not available until the next update cycle after it’s enabled.
kNone kAccelerometer
Accelerometer
Returns the last-read accelerometer data.
Buttons
Sets the value pointed to by current to a bitmask indicating which buttons are currently down. pushed and released reflect which buttons were pushed or released over the previous update cycle—at the nominal frame rate of 50 ms, fast button presses can be missed if you just poll the instantaneous state.
kButtonLeft kButtonRight kButtonUp kButtonDown kButtonB kButtonA
Crank
Returns the current position of the crank, in the range 0-360. Zero is pointing up, and the value increases as the crank moves clockwise, as viewed from the right side of the device.
Returns the angle change of the crank since the last time this function was called. Negative values are anti-clockwise.
Returns 1 or 0 indicating whether or not the crank is folded into the unit.
7.8. Device Auto Lock
As of 0.10.3, the device will automatically lock if the user doesn’t press any buttons or use the crank for more than 60 seconds. In order for games that expect longer periods without interaction to continue to function, it is possible to manually disable the auto lock feature. Note that when disabling the timeout, developers should take care to re-enable the timeout when appropiate.
Disables or enables the 60 second auto lock feature. When called, the timer is reset to 60 seconds.
7.9. System Sounds
0.12 adds sound effects for various system events, such as the menu opening or closing, USB cable plugged or unplugged, and the crank docked or undocked. Since games can receive notification of the crank docking and undocking, and may incorporate this into the game, we’ve provided a function for muting the default sounds for these events:
The function returns the previous value for this setting.
7.10. JSON
Decoding
Equivalent to playdate.graphics.decode()
in the Lua API.
Decodes a JSON file or string with the given decoder. An instance of json_decoder must implement decodeError. The remaining functions are optional although you’ll probably want to implement at least didDecodeTableValue and didDecodeArrayValue. The outval pointer, if set, contains the value retured from the top-level didDecodeSublist callback.
typedef struct json_decoder { void (*decodeError)(json_decoder* decoder, const char* error, int linenum); void (*willDecodeSublist)(json_decoder* decoder, const char* name, json_value_type type); int (*shouldDecodeTableValueForKey)(json_decoder* decoder, const char* key); void (*didDecodeTableValue)(json_decoder* decoder, const char* key, json_value value); int (*shouldDecodeArrayValueAtIndex)(json_decoder* decoder, int pos); void (*didDecodeArrayValue)(json_decoder* decoder, int pos, json_value value); void* (*didDecodeSublist)(json_decoder* decoder, const char* name, json_value_type type); void* userdata; int returnString; const char* path; } json_decoder;
-
decodeError: Called when the decoder encounters an error.
-
willDecodeSublist: Called before attempting to decode a JSON object or array.
-
didDecodeSublist: Called after successfully decoding a JSON object or array. The returned value is passed to the corresponding didDecodeTableValue() or didDecodeArrayValue() callback one level up, or the calling decode() or decodeString() function if the list is the top-level json object
-
shouldDecodeTableValueForKey: Called before decoding a key/value pair from an object. Return 1 to proceed with decoding or return 0 to skip this pair.
-
shouldDecodeArrayValueAtIndex: Called before decoding the value at pos from an array (note that pos is base 1 not 0). Return 1 to proceed with decoding or return 0 to skip this index.
-
didDecodeTableValue: Called after successfully decoding a key/value pair from an object.
-
didDecodeArrayValue: Called after successfully decoding the value at pos from an array.
-
userdata: A storage slot for the API client
-
returnString: If set in willDecodeSublist, the sublist is returned as a string instead of parsed
-
path: The current path in the parse tree. The root scope is named
_root
, but this is not included in the path when parsing the root’s children
Note that the decoder saves and restores the decoder struct at each level of the tree. This lets you add unique handlers for different sections of the json structure:
void myParser_WillDecodeSublist(json_decoder* decoder, const char* name, json_value_type type) { if ( strcmp(name, "widget") == 0 ) { Widget* widget = pd_malloc(sizeof(Widget)); decoder->userdata = widget; decoder->didDecodeTableValue = setWidgetValue; decoder->didDecodeSublist = finishWidget; } }
void setWidgetValue(json_decoder* decoder, const char* key, json_value value) { Widget* widget = decoder->userdata; if ( strcmp(key, "floops") == 0 ) widget.floops = json_floatValue(value); // ... }
void* finishWidget(json_decoder* decoder, const char* name, json_value_type type) { widgetCompleted(decoder->userdata); // send to external function return decoder->userdata; // or return the widget in didDecodeTableValue()'s value.data.tableval field }
After finishWidget()
is called and the parser exits the scope, the previous decoder functions are restored.
typedef enum { kJSONNull, kJSONTrue, kJSONFalse, kJSONInteger, kJSONFloat, kJSONString, kJSONArray, kJSONTable } json_value_type;
typedef struct { char type; union { int intval; float floatval; char* stringval; void* arrayval; void* tableval; } data; } json_value;
Note that a whole number encoded to JSON as a float might be decoded as an int. The above convenience functions can be used to convert a json_value to the required type.
typedef struct { int (*read)(void* readud, uint8_t* buf, int bufsize); // fill buffer, return bytes written or 0 on end of data void* userdata; } json_reader;
json_reader’s read member provides data to the decoder. It should return 0 when it is done reading. Here’s an example implementation:
int readfile(void* readud, uint8_t* buf, int bufsize) { return playdate->file->read((SDFile*)readud, buf, bufsize); }
SDFile* file = pd->file->open("data.json", kFileRead); pd->json->decode(&decoder, (json_reader){ .read = readfile, .userdata = file }, NULL);
Encoding
Populates the given json_encoder encoder with the functions necessary to encode arbitrary data into a JSON string. userdata is passed as the first argument of the given writeFunc write. When pretty is 1 the string is written with human-readable formatting.
typedef struct json_encoder { void (*startArray)(struct json_encoder* encoder); void (*addArrayMember)(struct json_encoder* encoder); void (*endArray)(struct json_encoder* encoder); void (*startTable)(struct json_encoder* encoder); void (*addTableMember)(struct json_encoder* encoder, const char* name, int len); void (*endTable)(struct json_encoder* encoder); void (*writeNull)(struct json_encoder* encoder); void (*writeFalse)(struct json_encoder* encoder); void (*writeTrue)(struct json_encoder* encoder); void (*writeInt)(struct json_encoder* encoder, int num); void (*writeDouble)(struct json_encoder* encoder, double num); void (*writeString)(struct json_encoder* encoder, const char* str, int len); } json_encoder;
Note that the encoder doesn’t perform any validation. It is up to the caller to ensure they are generating valid JSON.
-
startArray: Opens a new JavaScript Array.
-
addArrayMember: Creates a new index in the current JavaScript Array.
-
endArray: Closes the current JavaScript Array.
-
startTable: Opens a new JavaScript Object.
-
addTableMember: Creates a new property in the current JavaScript Object.
-
endTable: Closes the current JavaScript Object.
-
writeNull: Writes the JavaScript primitive null.
-
writeFalse: Writes the JavaScript boolean value false.
-
writeTrue: Writes the JavaScript boolean value true.
-
writeInt: Writes num as a JavaScript number.
-
writeDouble: Writes num as a JavaScript number.
-
writeString: Writes str of length len as a JavaScript string literal.
typedef void (writeFunc)(void* userdata, const char* str, int len);
Here’s an example writeFunc implementation using playdate->file->write() to write the JSON string to a file:
void writefile(void* userdata, const char* str, int len) { playdate->file->write((SDFile*)userdata, str, len); }
7.11. Lua
Adding functions or tables
Adds the Lua function f to the Lua runtime, with name name. (name can be a table path using dots, e.g. if name = “mycode.myDrawingFunction” adds the function “myDrawingFunction” to the global table “myCode”.) Returns 1 on success or 0 with an error message in outErr.
typedef int (*lua_CFunction) (lua_State *L);
A lua_CFunction should return the number of return values it has pushed onto the stack. Returns 1 on success or 0 with an error message in outErr. See Returning values to Lua.
Adds the function list reg to the Lua runtime, with the class name name. As above, name can be a table path. The list is terminated with a {NULL, NULL} entry.
Creates a new "class" (i.e., a Lua metatable containing functions) with the given name and adds the given functions and constants to it. If the table is simply a list of functions that won’t be used as a metatable, isstatic should be set to 1 to create a plain table instead of a metatable. Please see C_API/Examples/Array
for an example of how to use registerClass
to create a Lua table-like object from C.
typedef struct lua_reg { const char *name; lua_CFunction func; } lua_reg;
typedef struct lua_val { const char *name; enum { kInt, kFloat, kStr } type; union { unsigned int intval; float floatval; const char* strval; } v; } luaL_Val;
If a class includes an __index
function, it should call this first to check if the indexed variable exists in the metatable. If the indexMetatable() call returns 1, it has located the variable and put it on the stack, and the __index
function should return 1 to indicate a value was found. If indexMetatable() doesn’t find a value, the __index
function can then do its custom getter magic.
Starts the run loop back up.
Stops the run loop.
Getting values from Lua
The following functions are called from within a Lua function implementation to retrieve the function arguments and set the return value(s):
Returns the type of the variable at stack position pos. If the type is kTypeObject and outClass is non-NULL, it returns the name of the object’s metatable.
enum LuaType { kTypeNil, kTypeBool, kTypeInt, kTypeFloat, kTypeString, kTypeTable, kTypeFunction, kTypeThread, kTypeObject };
Returns the number of arguments passed to the function.
Returns 1 if the argument at the given position pos is nil.
Returns one if the argument at position pos is true, zero if not.
Returns the argument at position pos as a float.
Returns the argument at position pos as an int.
Returns the argument at position pos as a string.
Returns the argument at position pos as an LCDSprite.
Returns the argument at position pos as a string and sets outlen to its length.
Returns the argument at position pos as an LCDBitmap.
Returning values to Lua
If a function needs to return data to the caller it must return the number of return values pushed onto the stack.
Pushes the int val onto the stack.
Pushes the float val onto the stack.
Pushes the int val onto the stack.
Pushes nil onto the stack.
Pushes the string str onto the stack.
Like pushString(), but pushes an arbitrary byte array to the stack, ignoring \0 characters.
Pushes a lua_CFunction onto the stack.
Pushes the LCDBitmap bitmap onto the stack.
Pushes the LCDSprite sprite onto the stack.
Passing custom objects between C and Lua
Pushes the given custom object obj onto the stack and returns a pointer to the opaque LuaUDObject. type must match the class name used in playdate->lua->registerClass(). nValues is the number of slots to allocate for Lua values (see set/getObjectValue()).
Checks the object type of the argument at position pos and returns a pointer to it if it’s the correct type. Optionally sets outud to a pointer to the opaque LuaUDObject for the given stack.
Retains the opaque LuaUDObject obj and returns same.
Releases the opaque LuaUDObject obj.
Sets the value of object obj's uservalue slot number slot (starting at 1, not zero) to the value at the top of the stack.
Copies the value at obj's given uservalue slot to the top of the stack and returns its stack position.
Calling Lua from C
Calls the Lua function name and and indicates that nargs number of arguments have already been pushed to the stack for the function to use. name can be a table path using dots, e.g. “playdate.apiVersion”. Returns 1 on success; on failure, returns 0 and puts an error message into the outerr
pointer, if it’s set. Calling Lua from C is slow, so use sparingly.
7.12. Sprites
Allocates and returns a new LCDSprite.
Allocates and returns a copy of the given sprite.
Frees the given sprite.
Properties
Sets the bounds of the given sprite with bounds.
Returns the bounds of the given sprite as an PDRect;
Moves the given sprite to x, y and resets its bounds based on the bitmap dimensions and center.
Moves the given sprite to by offsetting its current position by dx, dy.
Sets x and y to the current position of sprite.
Sets the given sprite's image to the given bitmap.
Returns the LCDBitmap currently assigned to the given sprite.
Sets the size. The size is used to set the sprite’s bounds when calling moveTo().
Sets the Z order of the given sprite. Higher Z sprites are drawn on top of those with lower Z order.
Returns the Z index of the given sprite.
Sets the tag of the given sprite. This can be useful for identifying sprites or types of sprites when using the collision API.
Returns the tag of the given sprite.
Sets the mode for drawing the sprite’s bitmap.
Flips the bitmap.
Returns the flip setting of the sprite’s bitmap.
Specifies a stencil image to be set on the frame buffer before the sprite is drawn.
Specifies a stencil image to be set on the frame buffer before the sprite is drawn. If tile is set, the stencil will be tiled. Tiled stencils must have width evenly divisible by 32.
Sets the sprite’s stencil to the given pattern.
Clears the sprite’s stencil.
Sets the clipping rectangle for sprite drawing.
Clears the sprite’s clipping rectangle.
Sets the clipping rectangle for all sprites with a Z index within startZ and endZ inclusive.
Clears the clipping rectangle for all sprites with a Z index within startZ and endZ inclusive.
Set the updatesEnabled flag of the given sprite (determines whether the sprite has its update function called). One is true, 0 is false.
Get the updatesEnabled flag of the given sprite.
Set the visible flag of the given sprite (determines whether the sprite has its draw function called). One is true, 0 is false.
Get the visible flag of the given sprite.
Marking a sprite opaque tells the sprite system that it doesn’t need to draw anything underneath the sprite, since it will be overdrawn anyway. If you set an image without a mask/alpha channel on the sprite, it automatically sets the opaque flag.
When flag is set to 1, the given sprite will always redraw.
Forces the given sprite to redraw.
Marks the given dirtyRect (in screen coordinates) as needing a redraw. Graphics drawing functions now call this automatically, adding their drawn areas to the sprite’s dirty list, so there’s usually no need to call this manually.
When flag is set to 1, the sprite will draw in screen coordinates, ignoring the currently-set drawOffset.
This only affects drawing, and should not be used on sprites being used for collisions, which will still happen in world-space.
Sets the update function for the given sprite.
typedef void LCDSpriteUpdateFunction(LCDSprite* sprite);
Sets the draw function for the given sprite.
typedef void LCDSpriteDrawFunction(LCDSprite* sprite, LCDRect bounds, LCDRect drawrect);
If the sprite doesn’t have an image, the sprite’s draw function is called as needed to update the display. bounds is the bounds of hte given sprite. frame is the raw bitmap data of the current frame buffer. drawRect is the current dirty rect being updated by the display list.
Sets and gets the sprite’s userdata, an arbitrary pointer used for associating the sprite with other data.
Display List
Adds the given sprite to the display list, so that it is drawn in the current scene.
Removes the given sprite from the display list.
Removes the given count sized array of sprites from the display list.
Removes all sprites from the display list.
Returns the total number of sprites in the display list.
Draws every sprite in the display list.
Updates and draws every sprite in the display list.
Collisions
Note that the caller is responsible for freeing any arrays returned by these collision methods.
Frees and reallocates internal collision data, resetting everything to its default state.
Set the collisionsEnabled flag of the given sprite (along with the collideRect, this determines whether the sprite participates in collisions). One is true, 0 is false. Set to 1 by default.
Get the collisionsEnabled flag of the given sprite.
Marks the area of the given sprite, relative to its bounds, to be checked for collisions with other sprites' collide rects.
Returns the given sprite’s collide rect.
Clears the given sprite’s collide rect.
Set a callback that returns a SpriteCollisionResponseType for a collision between sprite and other.
typedef SpriteCollisionResponseType LCDSpriteCollisionFilterProc(LCDSprite* sprite, LCDSprite* other);
Returns the same values as playdate->sprite->moveWithCollisions() but does not actually move the sprite.
Moves the given sprite towards goalX, goalY taking collisions into account and returns an array of SpriteCollisionInfo. len is set to the size of the array and actualX, actualY are set to the sprite’s position after collisions. If no collisions occurred, this will be the same as goalX, goalY.
Note that SpriteCollisionInfo is a struct so you should grab a pointer when accessing each one: SpriteCollisionInfo *info = &results[i];
.
struct SpriteCollisionInfo { LCDSprite *sprite; LCDSprite *other; SpriteCollisionResponseType responseType; uint8_t overlaps; float ti; CollisionPoint move; CollisionVector normal; CollisionPoint touch; PDRect spriteRect; PDRect otherRect; };
-
sprite: The sprite being moved.
-
other: The sprite colliding with the sprite being moved.
-
type: The result of collisionResponse (see SpriteCollisionResponseType below).
-
overlaps: One if the sprite was overlapping other when the collision started. Zero if it didn’t overlap but tunneled through other.
-
ti: A number between 0 and 1 indicating how far along the movement to the goal the collision occurred.
-
move: The difference between the original coordinates and the actual ones when the collision happened (see CollisionPoint below).
-
normal: The collision normal; usually -1, 0, or 1 in x and y. Use this value to determine things like if your character is touching the ground (see CollisionVector below).
-
touch: The coordinates where the sprite started touching other.
-
spriteRect: The rectangle the sprite occupied when the touch happened.
-
otherRect: The rectangle other occupied when the touch happened.
typedef enum { kSpriteCollisionTypeSlide, kSpriteCollisionTypeFreeze, kSpriteCollisionTypeOverlap, kSpriteCollisionTypeBounce } SpriteCollisionResponseType;
-
kSpriteCollisionTypeSlide: Use for collisions that should slide over other objects, like Super Mario does over a platform or the ground.
-
kSpriteCollisionTypeFreeze: Use for collisions where the sprite should stop moving as soon as it collides with other, such as an arrow hitting a wall.
-
kSpriteCollisionTypeOverlap: Use for collisions in which you want to know about the collision but it should not impact the movement of the sprite, such as when collecting a coin.
-
kSpriteCollisionTypeBounce: Use when the sprite should move away from other, like the ball in Pong or Arkanoid.
typedef struct { float x; float y; } CollisionPoint;
typedef struct { int x; int y; } CollisionVector;
Returns an array of all sprites with collision rects containing the point at x, y. len is set to the size of the array.
Returns an array of all sprites with collision rects that intersect the width by height rect at x, y. len is set to the size of the array.
Returns an array of all sprites with collision rects that intersect the line connecting x1, y1 and x2, y2. len is set to the size of the array.
Returns an array of SpriteQueryInfo for all sprites with collision rects that intersect the line connecting x1, y1 and x2, y2. len is set to the size of the array. If you don’t need this information, use querySpritesAlongLine() as it will be faster.
Note that SpriteQueryInfo is a struct so you should grab a pointer when accessing each one: SpriteQueryInfo *info = &results[i];
.
struct SpriteQueryInfo { LCDSprite *sprite; float ti1; float ti2; CollisionPoint entryPoint; CollisionPoint exitPoint; };
-
sprite: The sprite being intersected by the segment.
-
ti1 & ti2: Numbers between 0 and 1 which indicate how far from the starting point of the line segment the collision happened. ti1 is relative to the entry point, ti2 is relative to the exit point.
-
entryPoint: The coordinates of the first intersection between sprite and the line segment (see CollisionPoint above).
-
exitPoint: The coordinates of the second intersection between sprite and the line segment (see CollisionPoint above).
Returns an array of sprites that have collide rects that are currently overlapping the given sprite’s collide rect. len is set to the size of the array.
Returns an array of all sprites that have collide rects that are currently overlapping. Each consecutive pair of sprites is overlapping (eg. 0 & 1 overlap, 2 & 3 overlap, etc). len is set to the size of the array.
8. Performance Considerations
8.1. Floating point Math operations
The Cortex-M7 processor used in the Playdate has support for single precision floating point operations (ie. the float
type), but not for double precision operations (ie. the double
type).
When using double
precision values, the compiler will emit code that emulates double-precision floating point calcuations, rather than using the specialized hardware for single-precision floating point calculations. This is extremely slow in comparison, and will come at a large cost of performance.
One small (but important) thing to keep in mind is that constant floating point values, like 3.14
are implicitly double
typed values at compile time. Ensure that any constant values are floats by adding the f
suffix: 3.14f
, 1.f
, and so on.
The C standard library has variants on its math functions with operate on either double
or float
values. When using these, make sure that you are using the float
variant. For example, ensure you are using fabsf()
not fabs()
, and powf()
rather thatn pow()
, and so on.
Some compiler flags can be used to make these mistakes obvious or easier to deal with:
-
-Wdouble-promotion
: Warns whenever afloat
is implicitly being converted to double. -
-fsingle-precision-constant
: All real constants will befloat
rather thandouble
, ie.1.0
will be the same as1.0f
.
9. Getting Help
9.1. Where can I download the SDK?
Head to the Playdate Developer page to download the latest SDK.
9.2. Where do I go if I have questions about the SDK?
You can find the SDK documentation for Lua here and C here . If you’re interested in seeing the Playdate SDK in action, check out our Twitch stream . For tips on making Playdate games, click here.
Searching in the Get Help and Development Discussion on our Developer Forum to find solutions will also be a good place to look at. If you still need help, the best way to get help from either the community or Panic is to post in that same Get Help category.
9.3. Where do I report bugs or issues relating to the SDK?
Head to the Bug Reports category and check the Bug Report category info for information on how to post a bug report. One of us at Panic will take a look at it! And what if I have feature requests?
To share your ideas, suggestions, and requests relating to Playdate, head to the Feature Request category and check the Feature Request category info before posting your feature request.
9.4. List of Helpful Libraries and Code
This thread includes some helpful tips from the community. Check it out here. For more resources, head to the Development Discussion category.
10. Legal information
Playdate fonts are licensed to you under the Creative Commons Attribution 4.0 International (CC BY 4.0) license.