diff --git a/README.md b/README.md index 0e28a4f..5e2c072 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Currently, the following sections of the API are implemented: - [ ] Sound - [x] FilePlayer - [x] SamplePlayer + - [x] Synth (partial) - [x] Sprite - [x] System diff --git a/Sources/PlaydateKit/Core/Display.swift b/Sources/PlaydateKit/Core/Display.swift index 9d52c60..3d32cef 100644 --- a/Sources/PlaydateKit/Core/Display.swift +++ b/Sources/PlaydateKit/Core/Display.swift @@ -19,7 +19,7 @@ public enum Display { /// The nominal refresh rate in frames per second. The default is 30 fps, which is a recommended /// figure that balances animation smoothness with performance and power considerations. Maximum is 50 fps. /// - /// If rate is 0, the game’s update callback (the function specified by `Playdate.updateCallback`) is called as soon as possible. + /// If rate is 0, the game’s update callback (the function specified by ``PlaydateGame/update()``) is called as soon as possible. /// Since the display refreshes line-by-line, and unchanged lines aren’t sent to the display, /// the update cycle will be faster than 30 times a second but at an indeterminate rate. public static var refreshRate: Float { diff --git a/Sources/PlaydateKit/Core/Sound.swift b/Sources/PlaydateKit/Core/Sound.swift index b4527fa..f585e5d 100644 --- a/Sources/PlaydateKit/Core/Sound.swift +++ b/Sources/PlaydateKit/Core/Sound.swift @@ -306,10 +306,76 @@ public enum Sound { private var samplePointer: OpaquePointer? } + public class Synth { + // MARK: Lifecycle + + /// Creates a new synth object to play a waveform or wavetable. See ``Sound/Synth/setWaveform(_:)`` for waveform values. + public init() { + pointer = synth.newSynth.unsafelyUnwrapped().unsafelyUnwrapped + } + + deinit { + synth.freeSynth.unsafelyUnwrapped(pointer) + } + + // MARK: Public + + public func setWaveform(_ waveform: SoundWaveform) { + synth.setWaveform.unsafelyUnwrapped(pointer, waveform) + } + + /// Sets the attack time, in seconds. + public func setAttackTime(_ attackTime: Float) { + synth.setAttackTime.unsafelyUnwrapped(pointer, attackTime) + } + + /// Sets the decay time, in seconds. + public func setDecayTime(_ decayTime: Float) { + synth.setDecayTime.unsafelyUnwrapped(pointer, decayTime) + } + + /// Sets the sustain level, as a proportion of the total level (0.0 to 1.0). + public func setSustainLevel(_ sustainLevel: Float) { + synth.setSustainLevel.unsafelyUnwrapped(pointer, sustainLevel) + } + + /// Sets the release time, in seconds. + public func setReleaseTime(_ releaseTime: Float) { + synth.setReleaseTime.unsafelyUnwrapped(pointer, releaseTime) + } + + /// Plays a note with the current waveform or sample. + /// - Parameters: + /// - frequency: The frequency of the note to play. + /// - volume: 0 to 1, defaults to 1 + /// - length: In seconds. If omitted, note will play until you call noteOff() + /// - when: Seconds since the sound engine started (see ``Sound/currentTime``). Defaults to the current time. + public func playNote(frequency: Float, volume: Float = 1, length: Float = -1, when: CUnsignedInt = 0) { + synth.playNote.unsafelyUnwrapped(pointer, frequency, volume, length, when) + } + + /// Sends a note off event to the synth. + /// - Parameter when: The scheduled time to send a note off event. Defaults to immediately. + public func noteOff(when: CUnsignedInt = 0) { + synth.noteOff.unsafelyUnwrapped(pointer, when) + } + + // MARK: Private + + private let pointer: OpaquePointer + } + + /// Returns the current time, in seconds, as measured by the audio device. + /// The audio device uses its own time base in order to provide accurate timing. + public static var currentTime: CUnsignedInt { + sound.getCurrentTime.unsafelyUnwrapped() + } + // MARK: Private private static var sound: playdate_sound { Playdate.playdateAPI.sound.pointee } private static var sample: playdate_sound_sample { sound.sample.pointee } + private static var synth: playdate_sound_synth { sound.synth.pointee } private static var fileplayer: playdate_sound_fileplayer { sound.fileplayer.pointee } private static var sampleplayer: playdate_sound_sampleplayer { sound.sampleplayer.pointee }