Files
playdate-kit/Sources/PlaydateKit/Sound/Source/Classes/SamplePlayer.swift
T

101 lines
3.9 KiB
Swift
Raw Normal View History

internal import CPlaydate
extension Sound {
/// Plays an `AudioSample` from memory. Wraps `SamplePlayer`.
public final class SamplePlayer: Source {
private static var api: UnsafePointer<playdate_sound_sampleplayer> { Playdate.samplePlayerAPI.unsafelyUnwrapped }
var loopCallback: ((SamplePlayer) -> Void)?
private var retainedSample: AudioSample?
private var retainedRateModulator: SignalValue?
override init(pointer: OpaquePointer?, isOwned: Bool) {
super.init(pointer: pointer, isOwned: isOwned)
}
public convenience init() {
self.init(pointer: SamplePlayer.api.pointee.newPlayer.unsafelyUnwrapped().unsafelyUnwrapped,
isOwned: true)
}
public convenience init(path: String) throws(PlaydateError) {
self.init()
sample = try AudioSample(path: path)
}
deinit {
if isOwned {
SamplePlayer.api.pointee.freePlayer.unsafelyUnwrapped(pointer)
}
}
2026-09-18 13:15:08 +00:00
/// Retained by the player.
public var sample: AudioSample? {
get { retainedSample }
set {
retainedSample = newValue
SamplePlayer.api.pointee.setSample.unsafelyUnwrapped(pointer, newValue?.pointer)
}
}
2026-09-18 13:15:08 +00:00
/// Plays `repeat` times at `rate` (1 is normal); 0 loops forever, -1 ping-pongs.
@discardableResult
public func play(repeat repeatCount: Int = 1, rate: Float = 1) -> Bool {
SamplePlayer.api.pointee.play.unsafelyUnwrapped(pointer, Int32(repeatCount), rate) != 0
}
public func stop() {
SamplePlayer.api.pointee.stop.unsafelyUnwrapped(pointer)
}
public func setPaused(_ paused: Bool) {
SamplePlayer.api.pointee.setPaused.unsafelyUnwrapped(pointer, paused ? 1 : 0)
}
2026-09-18 13:15:08 +00:00
/// Length in seconds.
public var length: Float {
SamplePlayer.api.pointee.getLength.unsafelyUnwrapped(pointer)
}
2026-09-18 13:15:08 +00:00
/// Playback position, in seconds.
public var offset: Float {
get { SamplePlayer.api.pointee.getOffset.unsafelyUnwrapped(pointer) }
set { SamplePlayer.api.pointee.setOffset.unsafelyUnwrapped(pointer, newValue) }
}
2026-09-18 13:15:08 +00:00
/// Playback rate; 1 is normal. Negative plays backward (PCM only, not ADPCM).
public var rate: Float {
get { SamplePlayer.api.pointee.getRate.unsafelyUnwrapped(pointer) }
set { SamplePlayer.api.pointee.setRate.unsafelyUnwrapped(pointer, newValue) }
}
2026-09-18 13:15:08 +00:00
/// Restricts playback to `start``end`, in sample frames.
public func setPlayRange(start: Int, end: Int) {
SamplePlayer.api.pointee.setPlayRange.unsafelyUnwrapped(pointer, Int32(start), Int32(end))
}
2026-09-18 13:15:08 +00:00
/// Called each time playback loops; `nil` removes it.
public func setLoopCallback(_ callback: ((SamplePlayer) -> Void)?) {
loopCallback = callback
if callback != nil {
SamplePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, { _, userdata in
guard let userdata else { return }
let player = Unmanaged<SamplePlayer>.fromOpaque(userdata).takeUnretainedValue()
player.loopCallback?(player)
}, Unmanaged.passUnretained(self).toOpaque())
} else {
SamplePlayer.api.pointee.setLoopCallback.unsafelyUnwrapped(pointer, nil, nil)
}
}
2026-09-18 13:15:08 +00:00
/// A signal added to `rate`; `nil` clears it. The player retains it.
public var rateModulator: SignalValue? {
get { SignalValue.wrap(SamplePlayer.api.pointee.getRateModulator.unsafelyUnwrapped(pointer)) }
set {
retainedRateModulator = newValue
SamplePlayer.api.pointee.setRateModulator.unsafelyUnwrapped(pointer, newValue?.pointer)
}
}
}
}