Added missing documentation to the source code in the Playdate bindings target.
CI / Build & test (macOS) (push) Has been cancelled
CI / Embedded Swift cross-compile (push) Has been cancelled
Documentation / deploy (push) Has been cancelled

This commit is contained in:
2026-07-25 12:55:53 +02:00
parent b435a6e8bd
commit 91783deb7b
63 changed files with 259 additions and 3 deletions
@@ -4,6 +4,7 @@ internal import CPlaydate
public enum Display {}
extension Display {
/// The cached `playdate->display` C API table.
private static var api: UnsafePointer<playdate_display> { Playdate.displayAPI.unsafelyUnwrapped }
/// The display width in pixels, taking the current scale into account.
@@ -1,8 +1,11 @@
extension File {
/// The origin used by `Handle.seek(to:from:)`.
public enum SeekOrigin: Int32, Sendable {
/// Relative to the beginning of the file.
case start = 0
/// Relative to the current offset.
case current = 1
/// Relative to the end of the file.
case end = 2
}
}
+4
View File
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->file` C API table.
var fileAPI: UnsafePointer<playdate_file> { Playdate.fileAPI.unsafelyUnwrapped }
/// The most recent file system error as a thrown error.
@@ -8,6 +9,9 @@ func lastFileError() -> PlaydateError {
}
/// The file API: access to the game's Data directory and pdx contents.
///
/// Paths are relative to the game's Data directory (read/write) or the
/// game's pdx (read-only), depending on the mode used to open them.
public enum File {}
extension File {
@@ -1,8 +1,11 @@
extension File {
/// Information about a file or directory, mirroring `FileStat`.
public struct Stat: Sendable {
/// Whether the path is a directory.
public let isDirectory: Bool
/// The file's size, in bytes.
public let size: UInt32
/// The time the file was last modified.
public let modified: System.DateTime
}
}
@@ -39,6 +39,7 @@ extension Graphics {
// MARK: Properties
/// The bitmap's dimensions, row stride, and raw storage.
public var data: Data {
var width: Int32 = 0, height: Int32 = 0, rowBytes: Int32 = 0
var mask: UnsafeMutablePointer<UInt8>?
@@ -61,7 +62,9 @@ extension Graphics {
return size
}
/// The bitmap's width, in pixels.
public var width: Int { size.width }
/// The bitmap's height, in pixels.
public var height: Int { size.height }
/// The color of the pixel at (x, y).
@@ -84,6 +87,7 @@ extension Graphics {
color.withLCDColor { gfx.pointee.clearBitmap.unsafelyUnwrapped(pointer, $0) }
}
/// Returns a new copy of the bitmap.
public func copy() -> Bitmap {
Bitmap(pointer: gfx.pointee.copyBitmap.unsafelyUnwrapped(pointer).unsafelyUnwrapped, isOwned: true)
}
@@ -52,6 +52,7 @@ extension Graphics {
return (Int(count), Int(width))
}
/// The number of bitmaps in the table.
public var count: Int { info.count }
}
}
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->graphics->videostream` C API table.
private var streamAPI: UnsafePointer<playdate_videostream> { Playdate.videoStreamAPI.unsafelyUnwrapped }
extension Graphics {
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->graphics->tilemap` C API table.
private var tilemapAPI: UnsafePointer<playdate_tilemap> { Playdate.tilemapAPI.unsafelyUnwrapped }
extension Graphics {
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->graphics->video` C API table.
private var videoAPI: UnsafePointer<playdate_video> { Playdate.videoAPI.unsafelyUnwrapped }
extension Graphics {
@@ -3,10 +3,15 @@ internal import CPlaydate
extension Graphics {
/// A drawing color: solid or an 8×8 pattern.
public enum Color: Sendable {
/// Solid black.
case black
/// Solid white.
case white
/// Transparent; leaves the destination unchanged.
case clear
/// Inverts the destination pixels.
case xor
/// An 8×8 two-color pattern.
case pattern(Pattern)
/// Materializes the `LCDColor` for the duration of `body`. Pattern
@@ -3,13 +3,21 @@ internal import CPlaydate
extension Graphics {
/// How source pixels combine with the destination when drawing.
public enum DrawMode: UInt32, Sendable {
/// Source pixels replace the destination.
case copy = 0
/// White source pixels are treated as transparent.
case whiteTransparent = 1
/// Black source pixels are treated as transparent.
case blackTransparent = 2
/// Opaque source pixels draw white.
case fillWhite = 3
/// Opaque source pixels draw black.
case fillBlack = 4
/// Source pixels are XORed with the destination.
case xor = 5
/// The inverse of `xor`.
case nxor = 6
/// Source pixels draw inverted.
case inverted = 7
init(_ mode: LCDBitmapDrawMode) { self = DrawMode(rawValue: UInt32(mode.rawValue)) ?? .copy }
@@ -3,6 +3,7 @@ internal import CPlaydate
/// The graphics API: drawing, bitmaps, fonts, tilemaps, and video.
public enum Graphics {}
/// The cached `playdate->graphics` C API table.
var gfx: UnsafePointer<playdate_graphics> { Playdate.graphicsAPI.unsafelyUnwrapped }
extension Graphics {
@@ -50,10 +51,12 @@ extension Graphics {
gfx.pointee.setScreenClipRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height))
}
/// Clears the current clip rect.
public static func clearClipRect() {
gfx.pointee.clearClipRect.unsafelyUnwrapped()
}
/// Sets the end cap style used by subsequent line drawing.
public static func setLineCapStyle(_ style: LineCapStyle) {
gfx.pointee.setLineCapStyle.unsafelyUnwrapped(style.cValue)
}
@@ -71,18 +74,21 @@ extension Graphics {
gfx.pointee.pushContext.unsafelyUnwrapped(target?.pointer)
}
/// Pops the top drawing context off the stack.
public static func popContext() {
gfx.pointee.popContext.unsafelyUnwrapped()
}
// MARK: - Shapes
/// Draws a line from (x1, y1) to (x2, y2) with the given stroke width.
public static func drawLine(x1: Int, y1: Int, x2: Int, y2: Int, width: Int, color: Color) {
color.withLCDColor {
gfx.pointee.drawLine.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2), Int32(width), $0)
}
}
/// Fills the triangle with vertices (x1, y1), (x2, y2), and (x3, y3).
public static func fillTriangle(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int, color: Color) {
color.withLCDColor {
gfx.pointee.fillTriangle.unsafelyUnwrapped(Int32(x1), Int32(y1), Int32(x2), Int32(y2),
@@ -90,18 +96,22 @@ extension Graphics {
}
}
/// Draws the outline of a rectangle, stroked inside its frame.
public static func drawRect(x: Int, y: Int, width: Int, height: Int, color: Color) {
color.withLCDColor {
gfx.pointee.drawRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0)
}
}
/// Fills the rectangle with `color`.
public static func fillRect(x: Int, y: Int, width: Int, height: Int, color: Color) {
color.withLCDColor {
gfx.pointee.fillRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height), $0)
}
}
/// Draws the outline of a rectangle with rounded corners, stroked with
/// `lineWidth`.
public static func drawRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int,
lineWidth: Int, color: Color) {
color.withLCDColor {
@@ -110,6 +120,7 @@ extension Graphics {
}
}
/// Fills a rectangle with rounded corners.
public static func fillRoundRect(x: Int, y: Int, width: Int, height: Int, radius: Int, color: Color) {
color.withLCDColor {
gfx.pointee.fillRoundRect.unsafelyUnwrapped(Int32(x), Int32(y), Int32(width), Int32(height),
@@ -127,6 +138,8 @@ extension Graphics {
}
}
/// Fills an ellipse inside the rect. If the angles differ, fills the
/// wedge from `startAngle` to `endAngle` (clockwise degrees, 0 at top).
public static func fillEllipse(x: Int, y: Int, width: Int, height: Int,
startAngle: Float = 0, endAngle: Float = 0, color: Color) {
color.withLCDColor {
@@ -202,6 +215,7 @@ extension Graphics {
gfx.pointee.setTextTracking.unsafelyUnwrapped(Int32(tracking))
}
/// The extra space currently added between letters, in pixels.
public static var textTracking: Int {
Int(gfx.pointee.getTextTracking.unsafelyUnwrapped())
}
@@ -2,10 +2,17 @@ extension Graphics.Bitmap {
/// The bitmap's dimensions, row stride, and raw pixel/mask storage.
/// The pointers are owned by the bitmap.
public struct Data {
/// The bitmap's width, in pixels.
public let width: Int
/// The bitmap's height, in pixels.
public let height: Int
/// The stride of one row of pixel data, in bytes.
public let rowBytes: Int
/// The bitmap's mask data, or `nil` if it has no mask. One bit per
/// pixel; rows are `rowBytes` wide.
public let mask: UnsafeMutablePointer<UInt8>?
/// The bitmap's pixel data. One bit per pixel; rows are `rowBytes`
/// wide.
public let data: UnsafeMutablePointer<UInt8>?
}
}
@@ -9,6 +9,8 @@ extension Graphics {
public var top: Int
public var bottom: Int
/// Creates a rect from its edges. `right` and `bottom` are not
/// inclusive.
public init(left: Int, right: Int, top: Int, bottom: Int) {
self.left = left
self.right = right
@@ -16,6 +18,7 @@ extension Graphics {
self.bottom = bottom
}
/// Creates a rect from an origin and size.
public init(x: Int, y: Int, width: Int, height: Int) {
self.init(left: x, right: x + width, top: y, bottom: y + height)
}
@@ -30,6 +33,7 @@ extension Graphics {
top: Int32(top), bottom: Int32(bottom))
}
/// Returns the rect offset by (dx, dy).
public func translated(dx: Int, dy: Int) -> Rect {
Rect(left: left + dx, right: right + dx, top: top + dy, bottom: bottom + dy)
}
@@ -1,9 +1,12 @@
extension Graphics {
/// An 8×8 two-color pattern: 8 rows of image data followed by 8 rows of mask.
public struct Pattern: Sendable {
/// The pattern's 8 rows of image data followed by 8 rows of mask,
/// one byte per row.
public var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
/// Creates a pattern from 8 rows of image data and 8 rows of mask.
public init(bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) {
self.bytes = bytes
@@ -22,6 +22,7 @@ extension JSON {
/// The JSON produced so far.
public var json: String { output.text }
/// Starts a JSON array.
public func startArray() {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.startArray.unsafelyUnwrapped($0) }
}
@@ -31,10 +32,12 @@ extension JSON {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.addArrayMember.unsafelyUnwrapped($0) }
}
/// Ends the current array.
public func endArray() {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.endArray.unsafelyUnwrapped($0) }
}
/// Starts a JSON object.
public func startTable() {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.startTable.unsafelyUnwrapped($0) }
}
@@ -48,28 +51,34 @@ extension JSON {
}
}
/// Ends the current object.
public func endTable() {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.endTable.unsafelyUnwrapped($0) }
}
/// Writes a `null` value.
public func writeNull() {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeNull.unsafelyUnwrapped($0) }
}
/// Writes a boolean value.
public func writeBool(_ value: Bool) {
withUnsafeMutablePointer(to: &encoder) {
(value ? $0.pointee.writeTrue : $0.pointee.writeFalse).unsafelyUnwrapped($0)
}
}
/// Writes an integer value.
public func writeInt(_ value: Int) {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeInt.unsafelyUnwrapped($0, Int32(value)) }
}
/// Writes a floating-point value.
public func writeDouble(_ value: Double) {
withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeDouble.unsafelyUnwrapped($0, value) }
}
/// Writes a string value.
public func writeString(_ value: String) {
value.withPlaydateCString { cString in
withUnsafeMutablePointer(to: &encoder) {
@@ -1,12 +1,19 @@
extension JSON {
/// A decoded JSON value.
public indirect enum Value {
/// A JSON `null`.
case null
/// A JSON `true` or `false`.
case bool(Bool)
/// A JSON number without a fractional part.
case int(Int)
/// A JSON number with a fractional part.
case float(Float)
/// A JSON string.
case string(String)
/// A JSON array.
case array([Value])
/// A JSON object.
case table([String: Value])
}
}
+5
View File
@@ -1,8 +1,13 @@
internal import CPlaydate
/// The cached `playdate->json` C API table.
var jsonAPI: UnsafePointer<playdate_json> { Playdate.jsonAPI.unsafelyUnwrapped }
/// The JSON API: decoding to and encoding from a `Value` tree.
///
/// The C decoder is callback-based; this wrapper drives it to build a
/// complete `Value` tree. The encoder is exposed both as a streaming
/// `Encoder` and as a one-shot `encode(_:)` of a `Value`.
public enum JSON {}
extension JSON {
@@ -1,8 +1,11 @@
extension Lua {
/// A constant published on a registered class.
public enum ClassValue {
/// An integer constant.
case int(name: String, value: UInt32)
/// A floating-point constant.
case float(name: String, value: Float)
/// A string constant.
case string(name: String, value: String)
}
}
+14
View File
@@ -1,9 +1,14 @@
internal import CPlaydate
/// The cached `playdate->lua` C API table.
var luaAPI: UnsafePointer<playdate_lua> { Playdate.luaAPI.unsafelyUnwrapped }
/// The Lua bridge: registering C functions and classes, and exchanging
/// values with Lua code.
///
/// Lua callbacks are C function pointers without userdata, so functions
/// registered here must be `@convention(c)` (the `CFunction` typealias),
/// not capturing closures.
public enum Lua {}
extension Lua {
@@ -168,26 +173,33 @@ extension Lua {
// MARK: - Return values
/// Pushes nil onto the stack.
public static func pushNil() {
luaAPI.pointee.pushNil.unsafelyUnwrapped()
}
/// Pushes a boolean onto the stack.
public static func push(_ value: Bool) {
luaAPI.pointee.pushBool.unsafelyUnwrapped(value ? 1 : 0)
}
/// Pushes an integer onto the stack.
public static func push(_ value: Int) {
luaAPI.pointee.pushInt.unsafelyUnwrapped(Int32(value))
}
/// Pushes a float onto the stack.
public static func push(_ value: Float) {
luaAPI.pointee.pushFloat.unsafelyUnwrapped(value)
}
/// Pushes a string onto the stack.
public static func push(_ value: String) {
value.withPlaydateCString { luaAPI.pointee.pushString.unsafelyUnwrapped($0) }
}
/// Pushes raw bytes (which may contain embedded zeros) onto the stack
/// as a Lua string.
public static func push(bytes: [UInt8]) {
bytes.withUnsafeBytes { buffer in
luaAPI.pointee.pushBytes.unsafelyUnwrapped(
@@ -195,10 +207,12 @@ extension Lua {
}
}
/// Pushes a bitmap onto the stack.
public static func push(_ bitmap: Graphics.Bitmap) {
luaAPI.pointee.pushBitmap.unsafelyUnwrapped(bitmap.pointer)
}
/// Pushes a sprite onto the stack.
public static func push(_ sprite: Sprite) {
luaAPI.pointee.pushSprite.unsafelyUnwrapped(sprite.pointer)
}
@@ -11,6 +11,8 @@ extension Lua {
UDObject(pointer: luaAPI.pointee.retainObject.unsafelyUnwrapped(pointer).unsafelyUnwrapped)
}
/// Balances a `retain()`, allowing the object to be
/// garbage-collected again.
public func release() {
luaAPI.pointee.releaseObject.unsafelyUnwrapped(pointer)
}
@@ -1,9 +1,14 @@
internal import CPlaydate
/// The cached `playdate->network->http` C API table.
private var httpAPI: UnsafePointer<playdate_http> { Playdate.httpAPI.unsafelyUnwrapped }
extension Network {
/// An HTTP connection to a server. Wraps `HTTPConnection`.
///
/// The binding stores a back-reference to each wrapper in the
/// underlying object's userdata slot so callbacks can recover the
/// wrapper; the C userdata slot is therefore reserved by the binding.
public final class HTTPConnection {
let pointer: OpaquePointer
@@ -1,9 +1,14 @@
internal import CPlaydate
/// The cached `playdate->network->tcp` C API table.
private var tcpAPI: UnsafePointer<playdate_tcp> { Playdate.tcpAPI.unsafelyUnwrapped }
extension Network {
/// A TCP connection to a server. Wraps `TCPConnection`.
///
/// The binding stores a back-reference to each wrapper in the
/// underlying object's userdata slot so callbacks can recover the
/// wrapper; the C userdata slot is therefore reserved by the binding.
public final class TCPConnection {
let pointer: OpaquePointer
@@ -21,8 +21,11 @@ extension Network {
case notConnectedToAP = -16
case notImplemented = -17
case connectionClosed = -18
/// An error code not covered by `PDNetErr`.
case unknown = 1
/// Creates an error from the C code, or `.unknown` for
/// unrecognized codes.
init(_ error: PDNetErr) {
self = NetError(rawValue: Int32(error.rawValue)) ?? .unknown
}
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->network` C API table.
private var networkAPI: UnsafePointer<playdate_network> { Playdate.networkAPI.unsafelyUnwrapped }
/// The network API: wifi status, HTTP, and TCP.
@@ -18,6 +19,7 @@ extension Network {
error == NET_OK ? nil : NetError(error)
}
/// The device's current wifi status.
public static var status: WifiStatus {
WifiStatus(rawValue: UInt32(networkAPI.pointee.getStatus.unsafelyUnwrapped().rawValue)) ?? .notConnected
}
@@ -1,6 +1,10 @@
/// The user's answer to a permission request (microphone, network).
public enum AccessReply: UInt32, Sendable {
/// The user has not answered yet; the request's completion delivers
/// the answer later.
case ask = 0
/// The user has already denied access; the completion is not called.
case deny = 1
/// The user has already granted access; the completion is not called.
case allow = 2
}
@@ -3,19 +3,34 @@ public import CPlaydate
/// A Swift view of `PDSystemEvent` with the key code folded into the
/// key events.
public enum SystemEvent {
/// Sent once at startup, before the first update.
case initialize
/// Sent when the Lua runtime is ready, for registering custom
/// functions and classes.
case initializeLua
/// The device was locked.
case lock
/// The device was unlocked.
case unlock
/// The game was paused (e.g. the system menu opened).
case pause
/// The game resumed after a pause.
case resume
/// The game is about to be terminated.
case terminate
/// A simulator key was pressed.
case keyPressed(keyCode: UInt32)
/// A simulator key was released.
case keyReleased(keyCode: UInt32)
/// The device is about to power down because the battery is low.
case lowPower
/// A Mirror session started.
case mirrorStarted
/// A Mirror session ended.
case mirrorEnded
/// Creates an event from the C event and its argument, or `nil` for
/// events unknown to this binding.
public init?(event: PDSystemEvent, argument: UInt32) {
switch event {
case kEventInit: self = .initialize
+7 -3
View File
@@ -1,8 +1,12 @@
public import CPlaydate
/// The raw C API bootstrap. Everything else in this module (System,
/// Graphics, Sprite, Sound, ...) lives at the top level of the `PlaydateKit`
/// module and requires `initialize(with:)` to have been called first.
/// The raw C API bootstrap.
///
/// The C API is delivered as a `PlaydateAPI` struct of function pointers
/// that the firmware hands to the game's `eventHandler` entry point. Call
/// `initialize(with:)` from that entry point before using any other API in
/// this module. Everything else (System, Graphics, Sprite, Sound, ...)
/// lives at the top level of the `PlaydateKit` module.
public enum Playdate {
/// The raw C API. Populated by `initialize(with:)`.
///
@@ -1,11 +1,15 @@
/// An error reported by the Playdate OS.
public struct PlaydateError: Swift.Error, Sendable {
/// The message reported by the OS, or a description of the failure.
public let message: String
/// Creates an error with the given message.
init(message: String) {
self.message = message
}
/// Creates an error by copying an OS-provided C string; a nil pointer
/// produces "unknown error".
init(cString: UnsafePointer<CChar>?) {
self.init(message: String(playdateCString: cString) ?? "unknown error")
}
@@ -1,8 +1,13 @@
internal import CPlaydate
/// The cached `playdate->scoreboards` C API table.
var scoreboardsAPI: UnsafePointer<playdate_scoreboards> { Playdate.scoreboardsAPI.unsafelyUnwrapped }
/// The scoreboards API for games with online leaderboards.
///
/// The C callbacks carry no userdata, so one completion per operation kind
/// is tracked at a time; starting a second request of the same kind before
/// the first completes replaces the stored completion.
public enum Scoreboards {}
extension Scoreboards {
@@ -3,7 +3,9 @@ internal import CPlaydate
extension Scoreboards {
/// A board belonging to the game.
public struct Board {
/// The board's identifier, used in the other scoreboard calls.
public let boardID: String
/// The board's display name.
public let name: String
init(_ board: PDBoard) {
@@ -3,7 +3,9 @@ internal import CPlaydate
extension Scoreboards {
/// The game's boards.
public struct BoardsList {
/// When the list was last updated, in seconds since the epoch.
public let lastUpdated: UInt32
/// The game's boards.
public let boards: [Board]
init(_ list: PDBoardsList) {
@@ -3,9 +3,13 @@ internal import CPlaydate
extension Scoreboards {
/// A score on a board.
public struct Score {
/// The score's position on the board, starting at 1.
public let rank: UInt32
/// The score's value.
public let value: UInt32
/// The name of the player who posted the score.
public let player: String
/// The board the score belongs to, when known.
public let boardID: String?
init(_ score: PDScore) {
@@ -3,10 +3,15 @@ internal import CPlaydate
extension Scoreboards {
/// The scores on a board.
public struct ScoresList {
/// The board the scores belong to.
public let boardID: String
/// When the list was last updated, in seconds since the epoch.
public let lastUpdated: UInt32
/// Whether the current player's score is included in the list.
public let playerIncluded: Bool
/// The maximum number of scores the list can hold.
public let limit: UInt32
/// The scores, ordered by rank.
public let scores: [Score]
init(_ list: PDScoresList) {
@@ -28,6 +28,7 @@ extension Sound {
BitCrusher.api.pointee.setDepth.unsafelyUnwrapped(pointer, depth)
}
/// Modulates the crush depth.
public var depthModulator: SignalValue? {
get { SignalValue.wrap(BitCrusher.api.pointee.getDepthModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -41,6 +42,7 @@ extension Sound {
BitCrusher.api.pointee.setDownsampling.unsafelyUnwrapped(pointer, downsampling)
}
/// Modulates the downsampling amount.
public var downsamplingModulator: SignalValue? {
get { SignalValue.wrap(BitCrusher.api.pointee.getDownsamplingModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -24,6 +24,7 @@ extension Sound {
DelayLineTap.api.pointee.setTapDelay.unsafelyUnwrapped(pointer, Int32(frames))
}
/// Modulates the tap's delay.
public var delayModulator: SignalValue? {
get { SignalValue.wrap(DelayLineTap.api.pointee.getTapDelayModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->sound->effect` C API table.
private var effectAPI: UnsafePointer<playdate_sound_effect> { Playdate.effectAPI.unsafelyUnwrapped }
extension Sound {
@@ -48,6 +49,7 @@ extension Sound {
effectAPI.pointee.setMix.unsafelyUnwrapped(pointer, level)
}
/// Modulates the wet/dry mix.
public var mixModulator: SignalValue? {
get { SignalValue.wrap(effectAPI.pointee.getMixModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -24,6 +24,7 @@ extension Sound {
OnePoleFilter.api.pointee.setParameter.unsafelyUnwrapped(pointer, parameter)
}
/// Modulates the filter's cutoff parameter.
public var parameterModulator: SignalValue? {
get { SignalValue.wrap(OnePoleFilter.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -28,6 +28,7 @@ extension Sound {
Overdrive.api.pointee.setLimit.unsafelyUnwrapped(pointer, limit)
}
/// Modulates the clipping limit.
public var limitModulator: SignalValue? {
get { SignalValue.wrap(Overdrive.api.pointee.getLimitModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -41,6 +42,7 @@ extension Sound {
Overdrive.api.pointee.setOffset.unsafelyUnwrapped(pointer, offset)
}
/// Modulates the DC offset.
public var offsetModulator: SignalValue? {
get { SignalValue.wrap(Overdrive.api.pointee.getOffsetModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -23,6 +23,7 @@ extension Sound {
RingModulator.api.pointee.setFrequency.unsafelyUnwrapped(pointer, frequency)
}
/// Modulates the modulation frequency.
public var frequencyModulator: SignalValue? {
get { SignalValue.wrap(RingModulator.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -29,6 +29,7 @@ extension Sound {
TwoPoleFilter.api.pointee.setFrequency.unsafelyUnwrapped(pointer, frequency)
}
/// Modulates the filter's frequency.
public var frequencyModulator: SignalValue? {
get { SignalValue.wrap(TwoPoleFilter.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -46,6 +47,7 @@ extension Sound {
TwoPoleFilter.api.pointee.setResonance.unsafelyUnwrapped(pointer, resonance)
}
/// Modulates the filter's resonance.
public var resonanceModulator: SignalValue? {
get { SignalValue.wrap(TwoPoleFilter.api.pointee.getResonanceModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -1,11 +1,13 @@
internal import CPlaydate
extension Sound.TwoPoleFilter {
/// The filter's response type.
public enum Kind: UInt32, Sendable {
case lowPass = 0
case highPass = 1
case bandPass = 2
case notch = 3
/// A parametric EQ filter.
case peq = 4
case lowShelf = 5
case highShelf = 6
@@ -13,8 +13,11 @@ extension Sound {
init(_ format: SoundFormat) { self = Format(rawValue: UInt32(format.rawValue)) ?? .mono16bit }
var cValue: SoundFormat { SoundFormat(SoundFormat.RawValue(rawValue)) }
/// Whether the format has two channels.
public var isStereo: Bool { rawValue & 1 != 0 }
/// Whether samples are 16-bit (rather than 8-bit or ADPCM).
public var is16bit: Bool { rawValue >= 2 && rawValue < 4 }
/// The size of one sample frame, in bytes.
public var bytesPerFrame: Int { Int(SoundFormat_bytesPerFrame(cValue)) }
}
}
@@ -1,8 +1,12 @@
extension Sound {
/// The microphone used when recording.
public enum MicSource: UInt32, Sendable {
/// Use the headset microphone if one is connected, otherwise the
/// built-in microphone.
case autodetect = 0
/// Always use the built-in microphone.
case internalMic = 1
/// Always use the headset microphone.
case headset = 2
}
}
@@ -21,6 +21,7 @@ extension Sound {
}
}
/// Removes all events from the signal's timeline.
public func clearEvents() {
ControlSignal.api.pointee.clearEvents.unsafelyUnwrapped(pointer)
}
@@ -32,6 +33,7 @@ extension Sound {
interpolate ? 1 : 0)
}
/// Removes the event at `step`, if any.
public func removeEvent(step: Int) {
ControlSignal.api.pointee.removeEvent.unsafelyUnwrapped(pointer, Int32(step))
}
@@ -22,18 +22,22 @@ extension Sound {
}
}
/// The attack time, in seconds.
public func setAttack(_ attack: Float) {
Envelope.api.pointee.setAttack.unsafelyUnwrapped(pointer, attack)
}
/// The decay time, in seconds.
public func setDecay(_ decay: Float) {
Envelope.api.pointee.setDecay.unsafelyUnwrapped(pointer, decay)
}
/// The sustain level, 0...1.
public func setSustain(_ sustain: Float) {
Envelope.api.pointee.setSustain.unsafelyUnwrapped(pointer, sustain)
}
/// The release time, in seconds.
public func setRelease(_ release: Float) {
Envelope.api.pointee.setRelease.unsafelyUnwrapped(pointer, release)
}
@@ -66,6 +70,7 @@ extension Sound {
Envelope.api.pointee.setRateScaling.unsafelyUnwrapped(pointer, scaling, start, end)
}
/// The envelope's current value.
public var value: Float {
Envelope.api.pointee.getValue.unsafelyUnwrapped(pointer)
}
@@ -89,6 +89,7 @@ extension Sound {
LFO.api.pointee.setRandomSeed.unsafelyUnwrapped(pointer, seed)
}
/// The LFO's current value.
public var value: Float {
LFO.api.pointee.getValue.unsafelyUnwrapped(pointer)
}
+1
View File
@@ -1,5 +1,6 @@
internal import CPlaydate
/// The cached `playdate->sound` C API table.
var snd: UnsafePointer<playdate_sound> { Playdate.soundAPI.unsafelyUnwrapped }
/// The sound API: channels, players, synths, sequences, and effects.
@@ -52,10 +52,12 @@ extension Sound {
FilePlayer.api.pointee.play.unsafelyUnwrapped(pointer, Int32(repeatCount)) != 0
}
/// Pauses playback.
public func pause() {
FilePlayer.api.pointee.pause.unsafelyUnwrapped(pointer)
}
/// Stops playback.
public func stop() {
FilePlayer.api.pointee.stop.unsafelyUnwrapped(pointer)
}
@@ -46,10 +46,12 @@ extension Sound {
SamplePlayer.api.pointee.play.unsafelyUnwrapped(pointer, Int32(repeatCount), rate) != 0
}
/// Stops playback.
public func stop() {
SamplePlayer.api.pointee.stop.unsafelyUnwrapped(pointer)
}
/// Pauses or resumes playback.
public func setPaused(_ paused: Bool) {
SamplePlayer.api.pointee.setPaused.unsafelyUnwrapped(pointer, paused ? 1 : 0)
}
@@ -72,10 +72,13 @@ extension Sound {
Instrument.api.pointee.setPitchBend.unsafelyUnwrapped(pointer, bend)
}
/// The range of `setPitchBend(_:)`, in half-steps.
public func setPitchBendRange(halfSteps: Float) {
Instrument.api.pointee.setPitchBendRange.unsafelyUnwrapped(pointer, halfSteps)
}
/// Transposes played notes by `halfSteps` (fractional values
/// allowed).
public func setTranspose(halfSteps: Float) {
Instrument.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps)
}
@@ -85,20 +88,24 @@ extension Sound {
Instrument.api.pointee.noteOff.unsafelyUnwrapped(pointer, note, when)
}
/// Releases every playing voice at time `when` (0 = now).
public func allNotesOff(when: UInt32 = 0) {
Instrument.api.pointee.allNotesOff.unsafelyUnwrapped(pointer, when)
}
/// Sets the volume of the left and right channels, 0...1.
public func setVolume(left: Float, right: Float) {
Instrument.api.pointee.setVolume.unsafelyUnwrapped(pointer, left, right)
}
/// The volume of the left and right channels.
public var volume: (left: Float, right: Float) {
var left: Float = 0, right: Float = 0
Instrument.api.pointee.getVolume.unsafelyUnwrapped(pointer, &left, &right)
return (left, right)
}
/// The number of voices currently playing.
public var activeVoiceCount: Int {
Int(Instrument.api.pointee.activeVoiceCount.unsafelyUnwrapped(pointer))
}
@@ -47,10 +47,12 @@ extension Sound {
}
}
/// Stops playback.
public func stop() {
Sequence.api.pointee.stop.unsafelyUnwrapped(pointer)
}
/// Whether the sequence is playing.
public var isPlaying: Bool {
Sequence.api.pointee.isPlaying.unsafelyUnwrapped(pointer) != 0
}
@@ -45,10 +45,12 @@ extension Sound {
SequenceTrack.api.pointee.addNoteEvent.unsafelyUnwrapped(pointer, step, length, note, velocity)
}
/// Removes the note at `step`, if any.
public func removeNote(step: UInt32, note: MIDINote) {
SequenceTrack.api.pointee.removeNoteEvent.unsafelyUnwrapped(pointer, step, note)
}
/// Removes all notes from the track.
public func clearNotes() {
SequenceTrack.api.pointee.clearNotes.unsafelyUnwrapped(pointer)
}
@@ -94,6 +96,7 @@ extension Sound {
return ControlSignal(pointer: signal, isOwned: false)
}
/// Removes all control signal events from the track.
public func clearControlEvents() {
SequenceTrack.api.pointee.clearControlEvents.unsafelyUnwrapped(pointer)
}
@@ -103,10 +106,12 @@ extension Sound {
Int(SequenceTrack.api.pointee.getPolyphony.unsafelyUnwrapped(pointer))
}
/// The number of notes currently playing.
public var activeVoiceCount: Int {
Int(SequenceTrack.api.pointee.activeVoiceCount.unsafelyUnwrapped(pointer))
}
/// Mutes or unmutes the track.
public func setMuted(_ muted: Bool) {
SequenceTrack.api.pointee.setMuted.unsafelyUnwrapped(pointer, muted ? 1 : 0)
}
@@ -108,18 +108,22 @@ extension Sound {
// MARK: Envelope
/// The envelope's attack time, in seconds.
public func setAttackTime(_ attack: Float) {
Synth.api.pointee.setAttackTime.unsafelyUnwrapped(pointer, attack)
}
/// The envelope's decay time, in seconds.
public func setDecayTime(_ decay: Float) {
Synth.api.pointee.setDecayTime.unsafelyUnwrapped(pointer, decay)
}
/// The envelope's sustain level, 0...1.
public func setSustainLevel(_ sustain: Float) {
Synth.api.pointee.setSustainLevel.unsafelyUnwrapped(pointer, sustain)
}
/// The envelope's release time, in seconds.
public func setReleaseTime(_ release: Float) {
Synth.api.pointee.setReleaseTime.unsafelyUnwrapped(pointer, release)
}
@@ -142,6 +146,7 @@ extension Sound {
Synth.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps)
}
/// Modulates the synth's frequency.
public var frequencyModulator: SignalValue? {
get { SignalValue.wrap(Synth.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -150,6 +155,7 @@ extension Sound {
}
}
/// Modulates the synth's amplitude.
public var amplitudeModulator: SignalValue? {
get { SignalValue.wrap(Synth.api.pointee.getAmplitudeModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -170,12 +176,14 @@ extension Sound {
Synth.api.pointee.setParameter.unsafelyUnwrapped(pointer, Int32(parameter), value) != 0
}
/// Modulates a generator parameter.
public func setParameterModulator(_ parameter: Int, _ modulator: SignalValue?) {
retain(modulator)
Synth.api.pointee.setParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter),
modulator?.pointer)
}
/// The modulator installed on a generator parameter, if any.
public func parameterModulator(_ parameter: Int) -> SignalValue? {
SignalValue.wrap(Synth.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter)))
}
@@ -8,8 +8,11 @@ extension Sound.Synth {
case sine = 2
case noise = 3
case sawtooth = 4
/// A Pocket Operator-style phase-distortion waveform.
case poPhase = 5
/// A Pocket Operator-style digital waveform.
case poDigital = 6
/// A Pocket Operator-style VOSIM (voice simulation) waveform.
case poVosim = 7
var cValue: SoundWaveform { SoundWaveform(SoundWaveform.RawValue(rawValue)) }
@@ -1,10 +1,16 @@
internal import CPlaydate
/// The cached `playdate->sprite` C API table.
private var spriteAPI: UnsafePointer<playdate_sprite> { Playdate.spriteAPI.unsafelyUnwrapped }
/// A sprite: a drawable object with position, z-order, and collision
/// support. Wraps `LCDSprite`. Static members wrap the global sprite
/// system functions.
///
/// The binding stores a back-reference to each `Sprite` wrapper in the
/// underlying `LCDSprite`'s userdata slot, so callbacks and queries can
/// recover the wrapper. Do not mix these wrappers with C code that sets
/// its own sprite userdata; use `userdata` for per-sprite storage instead.
public final class Sprite {
let pointer: OpaquePointer
let isOwned: Bool
@@ -269,11 +275,13 @@ public final class Sprite {
set { spriteAPI.pointee.setUpdatesEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
}
/// Whether the sprite participates in collisions.
public var collisionsEnabled: Bool {
get { spriteAPI.pointee.collisionsEnabled.unsafelyUnwrapped(pointer) != 0 }
set { spriteAPI.pointee.setCollisionsEnabled.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
}
/// Whether the sprite is drawn.
public var isVisible: Bool {
get { spriteAPI.pointee.isVisible.unsafelyUnwrapped(pointer) != 0 }
set { spriteAPI.pointee.setVisible.unsafelyUnwrapped(pointer, newValue ? 1 : 0) }
@@ -3,9 +3,13 @@ internal import CPlaydate
extension Sprite {
/// How a sprite reacts when a collision occurs.
public enum CollisionResponse: UInt32, Sendable {
/// The sprite slides along the edge of the other sprite.
case slide = 0
/// The sprite stops at the point of collision.
case freeze = 1
/// The sprite passes through, still reporting the collision.
case overlap = 2
/// The sprite bounces off the other sprite.
case bounce = 3
init(_ response: SpriteCollisionResponseType) {
@@ -7,6 +7,7 @@ public struct Rect: Sendable {
public var width: Float
public var height: Float
/// Creates a rect from an origin and size.
public init(x: Float, y: Float, width: Float, height: Float) {
self.x = x
self.y = y
+5
View File
@@ -1,5 +1,10 @@
internal import CPlaydate
/// Internal C-string helpers shared by the wrappers.
///
/// The conversions are implemented manually (rather than with
/// `String(cString:)` / `withCString`) so the module stays within the
/// Embedded Swift subset used for device builds.
extension String {
/// Creates a string by copying a null-terminated UTF-8 C string.
init(playdateCString pointer: UnsafePointer<CChar>) {
@@ -8,6 +8,8 @@ extension System {
/// Retains C strings passed to the OS for option titles.
private var retainedOptionTitles: [UnsafeMutablePointer<CChar>] = []
/// Wraps the C menu item; fails (and frees the retained titles) if
/// `pointer` is nil.
init?(pointer: OpaquePointer?,
retainedOptionTitles: [UnsafeMutablePointer<CChar>] = [],
onSelect: @escaping (MenuItem) -> Void) {
@@ -1,8 +1,11 @@
extension System {
/// OS, language, and pdx version information, mirroring `PDInfo`.
public struct Info: Sendable {
/// The Playdate OS version.
public let osVersion: UInt32
/// The system language.
public let language: Language
/// The version of the game's pdx.
public let pdxVersion: UInt32
}
}
@@ -6,8 +6,11 @@ extension System {
public let rawValue: UInt32
public init(rawValue: UInt32) { self.rawValue = rawValue }
/// The battery is charging.
public static let charging = PowerStatus(rawValue: UInt32(kPDPowerStatusCharging.rawValue))
/// Power is supplied over USB.
public static let usb = PowerStatus(rawValue: UInt32(kPDPowerStatusUsb.rawValue))
/// Power is supplied through the accessory screw terminals.
public static let screws = PowerStatus(rawValue: UInt32(kPDPowerStatusScrews.rawValue))
}
}
+12
View File
@@ -4,6 +4,7 @@ internal import CPlaydate
public enum System {}
extension System {
/// The cached `playdate->system` C API table.
private static var api: UnsafePointer<playdate_sys> { Playdate.systemAPI.unsafelyUnwrapped }
// MARK: - Memory
@@ -34,6 +35,7 @@ extension System {
// MARK: - Time
/// The system language setting.
public static var language: Language { Language(api.pointee.getLanguage.unsafelyUnwrapped()) }
/// Milliseconds since the game launched. Wraps around after about 49 days.
@@ -53,21 +55,25 @@ extension System {
/// High-resolution timer value, in seconds.
public static var elapsedTime: Float { api.pointee.getElapsedTime.unsafelyUnwrapped() }
/// Resets the high-resolution timer to zero.
public static func resetElapsedTime() { api.pointee.resetElapsedTime.unsafelyUnwrapped() }
/// Offset from UTC of the user-set timezone, in seconds.
public static var timezoneOffset: Int32 { api.pointee.getTimezoneOffset.unsafelyUnwrapped() }
/// Whether the user prefers 24-hour time display.
public static var shouldDisplay24HourTime: Bool {
api.pointee.shouldDisplay24HourTime.unsafelyUnwrapped() != 0
}
/// Converts seconds since the 2000-01-01 epoch to a calendar date.
public static func convertEpochToDateTime(_ epoch: UInt32) -> DateTime {
var dateTime = PDDateTime()
api.pointee.convertEpochToDateTime.unsafelyUnwrapped(epoch, &dateTime)
return DateTime(dateTime)
}
/// Converts a calendar date to seconds since the 2000-01-01 epoch.
public static func convertDateTimeToEpoch(_ dateTime: DateTime) -> UInt32 {
var cValue = dateTime.cValue
return api.pointee.convertDateTimeToEpoch.unsafelyUnwrapped(&cValue)
@@ -135,6 +141,8 @@ extension System {
nonisolated(unsafe) private static var buttonCallback: ((Buttons, Bool, UInt32) -> Int32)?
/// Enables the given peripherals (e.g. the accelerometer), disabling
/// the rest.
public static func setPeripheralsEnabled(_ peripherals: Peripherals) {
api.pointee.setPeripheralsEnabled.unsafelyUnwrapped(PDPeripherals(PDPeripherals.RawValue(peripherals.rawValue)))
}
@@ -153,6 +161,7 @@ extension System {
/// The crank position in degrees; 0 points along the +Y axis.
public static var crankAngle: Float { api.pointee.getCrankAngle.unsafelyUnwrapped() }
/// Whether the crank is folded into the device.
public static var isCrankDocked: Bool { api.pointee.isCrankDocked.unsafelyUnwrapped() != 0 }
/// Disables or enables the crank dock/undock sounds. Returns the previous setting.
@@ -164,6 +173,7 @@ extension System {
/// Whether the user has the "flipped" system setting enabled.
public static var isFlipped: Bool { api.pointee.getFlipped.unsafelyUnwrapped() != 0 }
/// Disables or re-enables the automatic screen lock.
public static func setAutoLockDisabled(_ disabled: Bool) {
api.pointee.setAutoLockDisabled.unsafelyUnwrapped(disabled ? 1 : 0)
}
@@ -272,6 +282,7 @@ extension System {
/// Battery charge, 0...100.
public static var batteryPercentage: Float { api.pointee.getBatteryPercentage.unsafelyUnwrapped() }
/// The battery voltage, in volts.
public static var batteryVoltage: Float { api.pointee.getBatteryVoltage.unsafelyUnwrapped() }
/// Flushes the CPU instruction cache after loading code at runtime.
@@ -323,6 +334,7 @@ extension System {
/// The system volume, 0...1.
public static var volume: Float { api.pointee.getVolume.unsafelyUnwrapped() }
/// The battery and power supply state.
public static var powerStatus: PowerStatus {
PowerStatus(rawValue: UInt32(api.pointee.getPowerStatus.unsafelyUnwrapped().rawValue))
}