Restructured the source code in the Playdate bindings target.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Sound {
|
||||
/// A synthesizer voice. Wraps `PDSynth`.
|
||||
public final class Synth: Source {
|
||||
private static var api: UnsafePointer<playdate_sound_synth> { Playdate.synthAPI.unsafelyUnwrapped }
|
||||
|
||||
private final class GeneratorBox {
|
||||
let generator: Generator
|
||||
let stereo: Bool
|
||||
init(_ generator: Generator, stereo: Bool) {
|
||||
self.generator = generator
|
||||
self.stereo = stereo
|
||||
}
|
||||
}
|
||||
|
||||
private var retainedSample: AudioSample?
|
||||
private var retainedModulators: [SignalValue] = []
|
||||
|
||||
override init(pointer: OpaquePointer?, isOwned: Bool) {
|
||||
super.init(pointer: pointer, isOwned: isOwned)
|
||||
}
|
||||
|
||||
public convenience init() {
|
||||
self.init(pointer: Synth.api.pointee.newSynth.unsafelyUnwrapped().unsafelyUnwrapped,
|
||||
isOwned: true)
|
||||
}
|
||||
|
||||
public convenience init(waveform: Waveform) {
|
||||
self.init()
|
||||
setWaveform(waveform)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if isOwned {
|
||||
Synth.api.pointee.freeSynth.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the synth (and its generator, if any).
|
||||
public func copy() -> Synth {
|
||||
Synth(pointer: Synth.api.pointee.copy.unsafelyUnwrapped(pointer).unsafelyUnwrapped,
|
||||
isOwned: true)
|
||||
}
|
||||
|
||||
// MARK: Sound generation
|
||||
|
||||
public func setWaveform(_ waveform: Waveform) {
|
||||
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.
|
||||
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).
|
||||
public func setWavetable(_ sample: AudioSample, log2size: Int,
|
||||
columns: Int, rows: Int) throws(PlaydateError) {
|
||||
retainedSample = sample
|
||||
guard Synth.api.pointee.setWavetable.unsafelyUnwrapped(
|
||||
pointer, sample.pointer, Int32(log2size), Int32(columns), Int32(rows)) != 0 else {
|
||||
throw PlaydateError(message: "invalid wavetable dimensions")
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides audio via custom Swift callbacks.
|
||||
public func setGenerator(stereo: Bool, _ generator: Generator) {
|
||||
let box = Unmanaged.passRetained(GeneratorBox(generator, stereo: stereo))
|
||||
Synth.api.pointee.setGenerator.unsafelyUnwrapped(
|
||||
pointer, stereo ? 1 : 0,
|
||||
{ userdata, left, right, nsamples, rate, drate in
|
||||
guard let userdata, let left else { return 0 }
|
||||
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(nsamples))
|
||||
let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(nsamples)) }
|
||||
return Int32(box.generator.render(leftBuffer, rightBuffer, rate, drate))
|
||||
},
|
||||
{ userdata, note, velocity, length in
|
||||
guard let userdata else { return }
|
||||
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
box.generator.noteOn?(note, velocity, length)
|
||||
},
|
||||
{ userdata, stop in
|
||||
guard let userdata else { return }
|
||||
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
box.generator.release?(stop != 0)
|
||||
},
|
||||
{ userdata, parameter, value in
|
||||
guard let userdata else { return 0 }
|
||||
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
return box.generator.setParameter?(Int(parameter), value) == true ? 1 : 0
|
||||
},
|
||||
{ userdata in
|
||||
guard let userdata else { return }
|
||||
Unmanaged<GeneratorBox>.fromOpaque(userdata).release()
|
||||
},
|
||||
{ userdata in
|
||||
guard let userdata else { return nil }
|
||||
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
return Unmanaged.passRetained(GeneratorBox(box.generator, stereo: box.stereo)).toOpaque()
|
||||
},
|
||||
box.toOpaque())
|
||||
}
|
||||
|
||||
// MARK: Envelope
|
||||
|
||||
public func setAttackTime(_ attack: Float) {
|
||||
Synth.api.pointee.setAttackTime.unsafelyUnwrapped(pointer, attack)
|
||||
}
|
||||
|
||||
public func setDecayTime(_ decay: Float) {
|
||||
Synth.api.pointee.setDecayTime.unsafelyUnwrapped(pointer, decay)
|
||||
}
|
||||
|
||||
public func setSustainLevel(_ sustain: Float) {
|
||||
Synth.api.pointee.setSustainLevel.unsafelyUnwrapped(pointer, sustain)
|
||||
}
|
||||
|
||||
public func setReleaseTime(_ release: Float) {
|
||||
Synth.api.pointee.setReleaseTime.unsafelyUnwrapped(pointer, release)
|
||||
}
|
||||
|
||||
/// The synth's amplitude envelope. Owned by the synth.
|
||||
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
|
||||
|
||||
/// Transposes played notes by `halfSteps` (fractional values allowed).
|
||||
public func setTranspose(_ halfSteps: Float) {
|
||||
Synth.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps)
|
||||
}
|
||||
|
||||
public var frequencyModulator: SignalValue? {
|
||||
get { SignalValue.wrap(Synth.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
|
||||
set {
|
||||
retain(newValue)
|
||||
Synth.api.pointee.setFrequencyModulator.unsafelyUnwrapped(pointer, newValue?.pointer)
|
||||
}
|
||||
}
|
||||
|
||||
public var amplitudeModulator: SignalValue? {
|
||||
get { SignalValue.wrap(Synth.api.pointee.getAmplitudeModulator.unsafelyUnwrapped(pointer)) }
|
||||
set {
|
||||
retain(newValue)
|
||||
Synth.api.pointee.setAmplitudeModulator.unsafelyUnwrapped(pointer, newValue?.pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of parameters the synth's generator supports.
|
||||
public var parameterCount: Int {
|
||||
Int(Synth.api.pointee.getParameterCount.unsafelyUnwrapped(pointer))
|
||||
}
|
||||
|
||||
/// Sets a generator parameter. Returns `false` if the parameter is
|
||||
/// invalid.
|
||||
@discardableResult
|
||||
public func setParameter(_ parameter: Int, value: Float) -> Bool {
|
||||
Synth.api.pointee.setParameter.unsafelyUnwrapped(pointer, Int32(parameter), value) != 0
|
||||
}
|
||||
|
||||
public func setParameterModulator(_ parameter: Int, _ modulator: SignalValue?) {
|
||||
retain(modulator)
|
||||
Synth.api.pointee.setParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter),
|
||||
modulator?.pointer)
|
||||
}
|
||||
|
||||
public func parameterModulator(_ parameter: Int) -> SignalValue? {
|
||||
SignalValue.wrap(Synth.api.pointee.getParameterModulator.unsafelyUnwrapped(pointer, Int32(parameter)))
|
||||
}
|
||||
|
||||
private func retain(_ modulator: SignalValue?) {
|
||||
if let modulator, !retainedModulators.contains(where: { $0 === modulator }) {
|
||||
retainedModulators.append(modulator)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
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.
|
||||
public func noteOff(when: UInt32 = 0) {
|
||||
Synth.api.pointee.noteOff.unsafelyUnwrapped(pointer, when)
|
||||
}
|
||||
|
||||
/// Stops the synth immediately, without playing the release phase.
|
||||
public func stop() {
|
||||
Synth.api.pointee.stop.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user