Tightened the source code and README documentations in the library.

This commit is contained in:
2026-09-18 12:45:39 +02:00
parent c9f887bacb
commit 9ae10590cc
97 changed files with 854 additions and 1142 deletions
@@ -1,7 +1,7 @@
internal import CPlaydate
extension Sound {
/// A synthesizer voice. Wraps `PDSynth`.
/// A synthesizer voice. Wraps `PDSynth`. Keeps samples and generators set on it alive.
public final class Synth: Source {
private static var api: UnsafePointer<playdate_sound_synth> { Playdate.synthAPI.unsafelyUnwrapped }
@@ -37,7 +37,7 @@ extension Sound {
}
}
/// Copies the synth (and its generator, if any).
/// An independently owned copy, including any generator.
public func copy() -> Synth {
Synth(pointer: Synth.api.pointee.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped,
isOwned: true)
@@ -49,15 +49,15 @@ extension Sound {
Synth.api.pointee.setWaveform.unsafelyUnwrapped(pointer, waveform.cValue)
}
/// Plays a sample instead of a waveform. A nonzero sustain range
/// loops that part of the sample while the note is held.
/// Plays `sample` (uncompressed PCM, not ADPCM). Frames `sustainStart..<sustainEnd`
/// loop while held; `sustainEnd` 0 with nonzero `sustainStart` means the sample's end.
public func setSample(_ sample: AudioSample, sustainStart: UInt32 = 0, sustainEnd: UInt32 = 0) {
retainedSample = sample
Synth.api.pointee.setSample.unsafelyUnwrapped(pointer, sample.pointer, sustainStart, sustainEnd)
}
/// Uses a wavetable for the synth. `log2size` is the base-2 log of
/// each waveform's size (e.g. 8 for 256 samples).
/// Plays `sample` (16-bit mono, uncompressed) as `columns` × `rows` cells of
/// 2^`log2size` samples; parameters 14 select the position.
public func setWavetable(_ sample: AudioSample, log2size: Int,
columns: Int, rows: Int) throws(PlaydateError) {
retainedSample = sample
@@ -67,7 +67,7 @@ extension Sound {
}
}
/// Provides audio via custom Swift callbacks.
/// `copy()` shares `generator`.
public func setGenerator(stereo: Bool, _ generator: Generator) {
let box = Unmanaged.passRetained(GeneratorBox(generator, stereo: stereo))
Synth.api.pointee.setGenerator.unsafelyUnwrapped(
@@ -108,45 +108,44 @@ extension Sound {
// MARK: Envelope
/// The envelope's attack time, in seconds.
/// In seconds.
public func setAttackTime(_ attack: Float) {
Synth.api.pointee.setAttackTime.unsafelyUnwrapped(pointer, attack)
}
/// The envelope's decay time, in seconds.
/// In seconds.
public func setDecayTime(_ decay: Float) {
Synth.api.pointee.setDecayTime.unsafelyUnwrapped(pointer, decay)
}
/// The envelope's sustain level, 0...1.
/// 0...1.
public func setSustainLevel(_ sustain: Float) {
Synth.api.pointee.setSustainLevel.unsafelyUnwrapped(pointer, sustain)
}
/// The envelope's release time, in seconds.
/// In seconds.
public func setReleaseTime(_ release: Float) {
Synth.api.pointee.setReleaseTime.unsafelyUnwrapped(pointer, release)
}
/// The synth's amplitude envelope. Owned by the synth.
/// The amplitude envelope; owned by the synth, valid only while it is alive.
public var envelope: Envelope? {
guard let envelope = Synth.api.pointee.getEnvelope.unsafelyUnwrapped(pointer) else { return nil }
return Envelope(pointer: envelope, isOwned: false)
}
/// Clears the synth's envelope so it plays at constant volume.
public func clearEnvelope() {
Synth.api.pointee.clearEnvelope.unsafelyUnwrapped(pointer)
}
// MARK: Modulation
// MARK: Pitch, modulation, and parameters
/// Transposes played notes by `halfSteps` (fractional values allowed).
/// Fractional half-steps allowed.
public func setTranspose(_ halfSteps: Float) {
Synth.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps)
}
/// Modulates the synth's frequency.
/// 1 is an octave up, -1 an octave down.
public var frequencyModulator: SignalValue? {
get { SignalValue.wrap(Synth.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -155,7 +154,6 @@ extension Sound {
}
}
/// Modulates the synth's amplitude.
public var amplitudeModulator: SignalValue? {
get { SignalValue.wrap(Synth.api.pointee.getAmplitudeModulator.unsafelyUnwrapped(pointer)) }
set {
@@ -164,26 +162,25 @@ extension Sound {
}
}
/// The number of parameters the synth's generator supports.
/// The number of parameters the generator supports.
public var parameterCount: Int {
Int(Synth.api.pointee.getParameterCount.unsafelyUnwrapped(pointer))
}
/// Sets a generator parameter. Returns `false` if the parameter is
/// invalid.
/// `parameter` is 1-based. Returns `false` if it is invalid.
@discardableResult
public func setParameter(_ parameter: Int, value: Float) -> Bool {
Synth.api.pointee.setParameter.unsafelyUnwrapped(pointer, Int32(parameter), value) != 0
}
/// Modulates a generator parameter.
/// `parameter` is 1-based.
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.
/// `parameter` is 1-based.
public func parameterModulator(_ parameter: Int) -> SignalValue? {
SignalValue.wrap(Synth.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter)))
}
@@ -196,26 +193,25 @@ extension Sound {
// MARK: Playing
/// Plays a note at `frequency` Hz. `length` is in seconds; `nil`
/// plays until `noteOff()`. `when` is the audio-clock time to start,
/// or 0 for immediately.
/// `frequency` in Hz; `length` in seconds, `nil` until `noteOff(when:)`;
/// `when` is an audio-clock time, 0 for now.
public func playNote(frequency: Float, velocity: Float = 1,
length: Float? = nil, when: UInt32 = 0) {
Synth.api.pointee.playNote.unsafelyUnwrapped(pointer, frequency, velocity, length ?? -1, when)
}
/// Plays a MIDI note, where 60 is middle C.
/// 60 is C4; fractional notes allowed. Other arguments as in `playNote`.
public func playMIDINote(_ note: MIDINote, velocity: Float = 1,
length: Float? = nil, when: UInt32 = 0) {
Synth.api.pointee.playMIDINote.unsafelyUnwrapped(pointer, note, velocity, length ?? -1, when)
}
/// Releases the playing note at time `when`, or immediately if 0.
/// Releases the note at audio-clock time `when`, or now if 0.
public func noteOff(when: UInt32 = 0) {
Synth.api.pointee.noteOff.unsafelyUnwrapped(pointer, when)
}
/// Stops the synth immediately, without playing the release phase.
/// Stops immediately, skipping the release phase.
public func stop() {
Synth.api.pointee.stop.unsafelyUnwrapped(pointer)
}