Swift 6.4 migration and exhancements (#1)
This PR contains the work done to update the library and the attached example project to use the Swift 6.4 computer as a minimum supported version and also, to use the latest features introduced in it. Reviewed-on: #1 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
extension Sound.Effect {
|
||||
/// Processes up to `AUDIO_FRAMES_PER_CYCLE` sample frames in signed
|
||||
/// Q8.24 format. `bufferActive` is `false` when the input buffer is
|
||||
/// silent. Returns `true` if the effect produced output.
|
||||
public typealias Processor = (_ left: UnsafeMutableBufferPointer<Int32>,
|
||||
_ right: UnsafeMutableBufferPointer<Int32>?,
|
||||
/// Processes up to 512 (`AUDIO_FRAMES_PER_CYCLE`) signed Q8.24 frames in place.
|
||||
/// `right` is empty on mono channels; `bufferActive` is `false` if nothing was
|
||||
/// written. Returns `true` if it changed the samples.
|
||||
public typealias Processor = (_ left: inout MutableSpan<Int32>,
|
||||
_ right: inout MutableSpan<Int32>,
|
||||
_ bufferActive: Bool) -> Bool
|
||||
}
|
||||
|
||||
@@ -18,17 +18,17 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// When `true`, `setDepth` values map exponentially to bit depth.
|
||||
/// If `true`, quantizing scales with amplitude so quiet sounds survive; if `false`,
|
||||
/// it clears a fixed number of low-order bits.
|
||||
public func setExponential(_ flag: Bool) {
|
||||
BitCrusher.api.pointee.setExponential.unsafelyUnwrapped(pointer, flag)
|
||||
}
|
||||
|
||||
/// The amount of crushing, 0 (none) to 1 (quantized to 1 bit).
|
||||
/// Quantizing, 0 (none) to 1 (1-bit output).
|
||||
public func setDepth(_ depth: Float) {
|
||||
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 {
|
||||
@@ -37,12 +37,11 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of downsampling, 0 (none) to 1 (every sample repeated).
|
||||
/// Sample-rate reduction, 0 (none) to 1 (so much that audio stops).
|
||||
public func setDownsampling(_ downsampling: Float) {
|
||||
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 {
|
||||
|
||||
@@ -5,7 +5,7 @@ extension Sound {
|
||||
public final class DelayLine: Effect {
|
||||
private static var api: UnsafePointer<playdate_sound_effect_delayline> { Playdate.delayLineAPI.unsafelyUnwrapped }
|
||||
|
||||
/// Creates a delay line holding `length` frames.
|
||||
/// `length` is in frames.
|
||||
public init(length: Int, stereo: Bool = false) {
|
||||
super.init(pointer: DelayLine.api.pointee.newDelayLine.unsafelyUnwrapped(
|
||||
Int32(length), stereo ? 1 : 0).unsafelyUnwrapped, isOwned: true)
|
||||
@@ -17,19 +17,18 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the delay length. Cannot be larger than the line's
|
||||
/// original length.
|
||||
/// Clears the buffer and reallocates, so not safe while the line is in use.
|
||||
public func setLength(frames: Int) {
|
||||
DelayLine.api.pointee.setLength.unsafelyUnwrapped(pointer, Int32(frames))
|
||||
}
|
||||
|
||||
/// The feedback level, 0...1.
|
||||
/// 0...1.
|
||||
public func setFeedback(_ feedback: Float) {
|
||||
DelayLine.api.pointee.setFeedback.unsafelyUnwrapped(pointer, feedback)
|
||||
}
|
||||
|
||||
/// Adds a tap `delay` frames behind the write head. The tap can be
|
||||
/// added to a channel as a sound source.
|
||||
/// `delay` is in frames behind the write head, at most the line's length.
|
||||
/// The tap keeps the line alive.
|
||||
public func addTap(delay: Int) -> DelayLineTap? {
|
||||
guard let tap = DelayLine.api.pointee.addTap.unsafelyUnwrapped(pointer, Int32(delay)) else {
|
||||
return nil
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
internal import CPlaydate
|
||||
|
||||
extension Sound {
|
||||
/// A tap into a delay line; produces audio and can be added to a channel
|
||||
/// as a source. Wraps `DelayLineTap`.
|
||||
/// A read point on a delay line, playable as a channel source. Wraps `DelayLineTap`.
|
||||
public final class DelayLineTap: Source {
|
||||
private static var api: UnsafePointer<playdate_sound_effect_delayline> { Playdate.delayLineAPI.unsafelyUnwrapped }
|
||||
|
||||
/// The delay line is retained so the tap stays valid.
|
||||
/// Kept alive: the tap reads from its buffer.
|
||||
private let delayLine: DelayLine
|
||||
private var retainedDelayModulator: SignalValue?
|
||||
|
||||
@@ -19,12 +18,12 @@ extension Sound {
|
||||
DelayLineTap.api.pointee.freeTap.unsafelyUnwrapped(pointer)
|
||||
}
|
||||
|
||||
/// The tap's position in the delay line, in frames.
|
||||
/// In frames, up to the delay line's length.
|
||||
public func setDelay(frames: Int) {
|
||||
DelayLineTap.api.pointee.setTapDelay.unsafelyUnwrapped(pointer, Int32(frames))
|
||||
}
|
||||
|
||||
/// Modulates the tap's delay.
|
||||
/// A continuous signal speeds up or slows down playback.
|
||||
public var delayModulator: SignalValue? {
|
||||
get { SignalValue.wrap(DelayLineTap.api.pointee.getTapDelayModulator.unsafelyUnwrapped(pointer)) }
|
||||
set {
|
||||
@@ -33,7 +32,7 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// For stereo delay lines: swaps the left and right channels.
|
||||
/// Stereo delay lines only.
|
||||
public func setChannelsFlipped(_ flipped: Bool) {
|
||||
DelayLineTap.api.pointee.setTapChannelsFlipped.unsafelyUnwrapped(pointer, flipped ? 1 : 0)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
internal import CPlaydate
|
||||
|
||||
/// The cached `playdate->sound->effect` C API table.
|
||||
private var effectAPI: UnsafePointer<playdate_sound_effect> { Playdate.effectAPI.unsafelyUnwrapped }
|
||||
|
||||
extension Sound {
|
||||
/// An effect that processes a channel's audio: the base class of the
|
||||
/// built-in effects. Wraps `SoundEffect`.
|
||||
/// Processes a channel's audio; base of the built-in effects. Wraps `SoundEffect`.
|
||||
public class Effect {
|
||||
let pointer: OpaquePointer
|
||||
let isOwned: Bool
|
||||
@@ -22,7 +20,7 @@ extension Sound {
|
||||
self.isOwned = isOwned
|
||||
}
|
||||
|
||||
/// Creates an effect that processes audio with a Swift callback.
|
||||
/// Runs `processor` each audio cycle; keeps it alive until deinit.
|
||||
public init(processor: @escaping Processor) {
|
||||
let box = Unmanaged.passRetained(ProcessorBox(processor))
|
||||
processorBox = box
|
||||
@@ -30,18 +28,15 @@ extension Sound {
|
||||
guard let effect, let left,
|
||||
let userdata = effectAPI.pointee.getUserdata.unsafelyUnwrapped(effect) else { return 0 }
|
||||
let box = Unmanaged<ProcessorBox>.fromOpaque(userdata).takeUnretainedValue()
|
||||
let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(nsamples))
|
||||
let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(nsamples)) }
|
||||
return box.processor(leftBuffer, rightBuffer, bufactive != 0) ? 1 : 0
|
||||
var leftSpan = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)).mutableSpan
|
||||
var rightSpan = UnsafeMutableBufferPointer(start: right, count: right == nil ? 0 : Int(nsamples)).mutableSpan
|
||||
return box.processor(&leftSpan, &rightSpan, bufactive != 0) ? 1 : 0
|
||||
}, box.toOpaque()).unsafelyUnwrapped
|
||||
isOwned = true
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Subclasses free the C object in their own deinit with the
|
||||
// subsystem's type-specific free (freeDelayLine, freeOverdrive,
|
||||
// ...); freeing here as well would double-free. The base class
|
||||
// owns only the custom-processor effects it creates itself.
|
||||
// Subclasses free their C object themselves; freeing here would double-free.
|
||||
if let processorBox {
|
||||
if isOwned {
|
||||
effectAPI.pointee.freeEffect.unsafelyUnwrapped(pointer)
|
||||
@@ -50,12 +45,11 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The wet/dry mix: 1 is fully processed, 0 fully dry.
|
||||
/// Wet/dry mix: 0 leaves the effect out, 1 replaces the input with its output.
|
||||
public func setMix(_ level: Float) {
|
||||
effectAPI.pointee.setMix.unsafelyUnwrapped(pointer, level)
|
||||
}
|
||||
|
||||
/// Modulates the wet/dry mix.
|
||||
public var mixModulator: SignalValue? {
|
||||
get { SignalValue.wrap(effectAPI.pointee.getMixModulator.unsafelyUnwrapped(pointer)) }
|
||||
set {
|
||||
|
||||
@@ -18,13 +18,11 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The filter's cutoff: -1 to 1, where values above 0 are low-pass
|
||||
/// and values below 0 high-pass.
|
||||
/// The cutoff, -1 to 1: above 0 is high-pass, below 0 low-pass.
|
||||
public func setParameter(_ parameter: Float) {
|
||||
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 {
|
||||
|
||||
@@ -18,7 +18,7 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The input gain applied before clipping.
|
||||
/// Input gain, applied before clipping.
|
||||
public func setGain(_ gain: Float) {
|
||||
Overdrive.api.pointee.setGain.unsafelyUnwrapped(pointer, gain)
|
||||
}
|
||||
@@ -28,7 +28,6 @@ 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 {
|
||||
@@ -37,12 +36,11 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// A DC offset applied to the input, making the clipping asymmetric.
|
||||
/// Added to the upper and lower limits, making clipping asymmetric.
|
||||
public func setOffset(_ offset: Float) {
|
||||
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 {
|
||||
|
||||
@@ -18,12 +18,11 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The modulation frequency, in Hz.
|
||||
/// In Hz.
|
||||
public func setFrequency(_ frequency: Float) {
|
||||
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 {
|
||||
|
||||
@@ -24,12 +24,12 @@ extension Sound {
|
||||
TwoPoleFilter.api.pointee.setType.unsafelyUnwrapped(pointer, kind.cValue)
|
||||
}
|
||||
|
||||
/// The center/corner frequency, in Hz.
|
||||
/// Center or corner frequency, in Hz.
|
||||
public func setFrequency(_ frequency: Float) {
|
||||
TwoPoleFilter.api.pointee.setFrequency.unsafelyUnwrapped(pointer, frequency)
|
||||
}
|
||||
|
||||
/// Modulates the filter's frequency.
|
||||
/// 1 is half the sample rate.
|
||||
public var frequencyModulator: SignalValue? {
|
||||
get { SignalValue.wrap(TwoPoleFilter.api.pointee.getFrequencyModulator.unsafelyUnwrapped(pointer)) }
|
||||
set {
|
||||
@@ -38,7 +38,7 @@ extension Sound {
|
||||
}
|
||||
}
|
||||
|
||||
/// The gain, used by PEQ and shelf filters.
|
||||
/// Used by `.peq` and shelf filters.
|
||||
public func setGain(_ gain: Float) {
|
||||
TwoPoleFilter.api.pointee.setGain.unsafelyUnwrapped(pointer, gain)
|
||||
}
|
||||
@@ -47,7 +47,6 @@ 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,13 +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
|
||||
/// Band-reject.
|
||||
case notch = 3
|
||||
/// A parametric EQ filter.
|
||||
/// Parametric EQ.
|
||||
case peq = 4
|
||||
case lowShelf = 5
|
||||
case highShelf = 6
|
||||
|
||||
Reference in New Issue
Block a user