Adopted Span and MutableSpan for buffers across the library to adapt to Swift 6.4.

This commit is contained in:
2026-09-18 12:06:21 +02:00
parent 89c8d7a04c
commit 4cb0f8f500
18 changed files with 127 additions and 83 deletions
+11 -11
View File
@@ -31,9 +31,10 @@ extension File {
/// Reads up to `buffer.count` bytes into `buffer`. Returns the number /// Reads up to `buffer.count` bytes into `buffer`. Returns the number
/// of bytes read; 0 indicates end of file. /// of bytes read; 0 indicates end of file.
public func read(into buffer: UnsafeMutableRawBufferPointer) throws(PlaydateError) -> Int { public func read(into buffer: inout MutableSpan<UInt8>) throws(PlaydateError) -> Int {
let result = fileAPI.pointee.read.unsafelyUnwrapped( let result = buffer.withUnsafeMutableBufferPointer { buffer in
pointer, buffer.baseAddress, UInt32(buffer.count)) fileAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count))
}
if result < 0 { throw lastFileError() } if result < 0 { throw lastFileError() }
return Int(result) return Int(result)
} }
@@ -49,11 +50,12 @@ extension File {
return bytes return bytes
} }
/// Writes the buffer to the file. Returns the number of bytes written. /// Writes the bytes to the file. Returns the number of bytes written.
@discardableResult @discardableResult
public func write(_ buffer: UnsafeRawBufferPointer) throws(PlaydateError) -> Int { public func write(_ bytes: Span<UInt8>) throws(PlaydateError) -> Int {
let result = fileAPI.pointee.write.unsafelyUnwrapped( let result = bytes.withUnsafeBufferPointer { buffer in
pointer, buffer.baseAddress, UInt32(buffer.count)) fileAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count))
}
if result < 0 { throw lastFileError() } if result < 0 { throw lastFileError() }
return Int(result) return Int(result)
} }
@@ -61,11 +63,9 @@ extension File {
/// Writes the bytes to the file. Returns the number of bytes written. /// Writes the bytes to the file. Returns the number of bytes written.
@discardableResult @discardableResult
public func write(_ bytes: [UInt8]) throws(PlaydateError) -> Int { public func write(_ bytes: [UInt8]) throws(PlaydateError) -> Int {
let result = bytes.withUnsafeBytes { buffer in try bytes.withUnsafeBufferPointer { buffer throws(PlaydateError) in
fileAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count)) try write(buffer.span)
} }
if result < 0 { throw lastFileError() }
return Int(result)
} }
/// Writes the string's UTF-8 to the file. Returns the bytes written. /// Writes the string's UTF-8 to the file. Returns the bytes written.
@@ -39,14 +39,42 @@ extension Graphics {
// MARK: Properties // MARK: Properties
/// The bitmap's dimensions, row stride, and raw storage. /// The bitmap's dimensions and row stride.
public var data: Data { public var data: Data {
let raw = rawData()
return Data(width: raw.width, height: raw.height, rowBytes: raw.rowBytes,
hasMask: raw.mask != nil)
}
/// Calls `body` with the bitmap's pixel data: one bit per pixel,
/// `height` rows of `rowBytes` bytes each.
public func withPixelData<Result, Failure: Error>(
_ body: (inout MutableSpan<UInt8>) throws(Failure) -> Result
) throws(Failure) -> Result {
let raw = rawData()
var span = UnsafeMutableBufferPointer(
start: raw.data, count: raw.data == nil ? 0 : raw.height * raw.rowBytes).mutableSpan
return try body(&span)
}
/// Calls `body` with the bitmap's mask data, laid out like the pixel
/// data. Returns `nil` if the bitmap has no mask.
public func withMaskData<Result, Failure: Error>(
_ body: (inout MutableSpan<UInt8>) throws(Failure) -> Result
) throws(Failure) -> Result? {
let raw = rawData()
guard let mask = raw.mask else { return nil }
var span = UnsafeMutableBufferPointer(start: mask, count: raw.height * raw.rowBytes).mutableSpan
return try body(&span)
}
private func rawData() -> (width: Int, height: Int, rowBytes: Int,
mask: UnsafeMutablePointer<UInt8>?, data: UnsafeMutablePointer<UInt8>?) {
var width: Int32 = 0, height: Int32 = 0, rowBytes: Int32 = 0 var width: Int32 = 0, height: Int32 = 0, rowBytes: Int32 = 0
var mask: UnsafeMutablePointer<UInt8>? var mask: UnsafeMutablePointer<UInt8>?
var data: UnsafeMutablePointer<UInt8>? var data: UnsafeMutablePointer<UInt8>?
gfx.pointee.getBitmapData.unsafelyUnwrapped(pointer, &width, &height, &rowBytes, &mask, &data) gfx.pointee.getBitmapData.unsafelyUnwrapped(pointer, &width, &height, &rowBytes, &mask, &data)
return Data(width: Int(width), height: Int(height), rowBytes: Int(rowBytes), return (Int(width), Int(height), Int(rowBytes), mask, data)
mask: mask, data: data)
} }
/// Cached dimensions, so `width`/`height` don't pay a full /// Cached dimensions, so `width`/`height` don't pay a full
@@ -56,8 +84,8 @@ extension Graphics {
private var size: (width: Int, height: Int) { private var size: (width: Int, height: Int) {
if let cachedSize { return cachedSize } if let cachedSize { return cachedSize }
let data = self.data let raw = rawData()
let size = (data.width, data.height) let size = (raw.width, raw.height)
cachedSize = size cachedSize = size
return size return size
} }
@@ -23,9 +23,11 @@ extension Graphics {
/// Creates a font from the contents of a .pft file already in memory. /// Creates a font from the contents of a .pft file already in memory.
/// The bytes are copied and retained for the font's lifetime. /// The bytes are copied and retained for the font's lifetime.
public convenience init?(data: UnsafeRawBufferPointer, wide: Bool = false) { public convenience init?(data: Span<UInt8>, wide: Bool = false) {
let copy = UnsafeMutableRawPointer.allocate(byteCount: data.count, alignment: 4) let copy = UnsafeMutableRawPointer.allocate(byteCount: data.count, alignment: 4)
copy.copyMemory(from: data.baseAddress.unsafelyUnwrapped, byteCount: data.count) data.withUnsafeBytes { bytes in
copy.copyMemory(from: bytes.baseAddress.unsafelyUnwrapped, byteCount: bytes.count)
}
let fontData = OpaquePointer(copy) let fontData = OpaquePointer(copy)
guard let pointer = gfx.pointee.makeFontFromData.unsafelyUnwrapped( guard let pointer = gfx.pointee.makeFontFromData.unsafelyUnwrapped(
fontData, wide ? 1 : 0, Int32(data.count)) else { fontData, wide ? 1 : 0, Int32(data.count)) else {
+16 -6
View File
@@ -279,15 +279,25 @@ extension Graphics {
// MARK: - Framebuffer // MARK: - Framebuffer
/// The current working framebuffer. Rows are `rowSize` bytes. /// Calls `body` with the current working framebuffer: `rows` rows of
/// `rowSize` bytes each. Returns `nil` if there is no framebuffer.
/// Call `markUpdatedRows(from:to:)` after writing directly. /// Call `markUpdatedRows(from:to:)` after writing directly.
public static var frame: UnsafeMutablePointer<UInt8>? { public static func withFrame<Result, Failure: Error>(
gfx.pointee.getFrame.unsafelyUnwrapped() _ body: (inout MutableSpan<UInt8>) throws(Failure) -> Result
) throws(Failure) -> Result? {
guard let frame = gfx.pointee.getFrame.unsafelyUnwrapped() else { return nil }
var span = UnsafeMutableBufferPointer(start: frame, count: rows * rowSize).mutableSpan
return try body(&span)
} }
/// The framebuffer currently shown on the display. Rows are `rowSize` bytes. /// Calls `body` with the framebuffer currently shown on the display:
public static var displayFrame: UnsafeMutablePointer<UInt8>? { /// `rows` rows of `rowSize` bytes each. Returns `nil` if there is no
gfx.pointee.getDisplayFrame.unsafelyUnwrapped() /// framebuffer.
public static func withDisplayFrame<Result, Failure: Error>(
_ body: (Span<UInt8>) throws(Failure) -> Result
) throws(Failure) -> Result? {
guard let frame = gfx.pointee.getDisplayFrame.unsafelyUnwrapped() else { return nil }
return try body(UnsafeBufferPointer(start: frame, count: rows * rowSize).span)
} }
/// A bitmap view of the display framebuffer. Simulator only; `nil` on device. /// A bitmap view of the display framebuffer. Simulator only; `nil` on device.
@@ -1,18 +1,14 @@
extension Graphics.Bitmap { extension Graphics.Bitmap {
/// The bitmap's dimensions, row stride, and raw pixel/mask storage. /// The bitmap's dimensions and row stride. Access the pixels themselves
/// The pointers are owned by the bitmap. /// with `withPixelData(_:)` and `withMaskData(_:)`.
public struct Data { public struct Data: Sendable {
/// The bitmap's width, in pixels. /// The bitmap's width, in pixels.
public let width: Int public let width: Int
/// The bitmap's height, in pixels. /// The bitmap's height, in pixels.
public let height: Int public let height: Int
/// The stride of one row of pixel data, in bytes. /// The stride of one row of pixel (and mask) data, in bytes.
public let rowBytes: Int public let rowBytes: Int
/// The bitmap's mask data, or `nil` if it has no mask. One bit per /// Whether the bitmap has a mask.
/// pixel; rows are `rowBytes` wide. public let hasMask: Bool
public let mask: UnsafeMutablePointer<UInt8>?
/// The bitmap's pixel data. One bit per pixel; rows are `rowBytes`
/// wide.
public let data: UnsafeMutablePointer<UInt8>?
} }
} }
+2 -2
View File
@@ -126,9 +126,9 @@ extension JSON {
reader.read = { userdata, buffer, size in reader.read = { userdata, buffer, size in
guard let userdata, let buffer else { return -1 } guard let userdata, let buffer else { return -1 }
let file = Unmanaged<File.Handle>.fromOpaque(userdata).takeUnretainedValue() let file = Unmanaged<File.Handle>.fromOpaque(userdata).takeUnretainedValue()
let destination = UnsafeMutableRawBufferPointer(start: buffer, count: Int(size)) var destination = UnsafeMutableBufferPointer(start: buffer, count: Int(size)).mutableSpan
do { do {
let count = try file.read(into: destination) let count = try file.read(into: &destination)
return count > 0 ? Int32(count) : -1 return count > 0 ? Int32(count) : -1
} catch { } catch {
return -1 return -1
@@ -152,9 +152,10 @@ extension Network {
/// Reads up to `buffer.count` response bytes. Returns the number of /// Reads up to `buffer.count` response bytes. Returns the number of
/// bytes read. /// bytes read.
public func read(into buffer: UnsafeMutableRawBufferPointer) throws(NetError) -> Int { public func read(into buffer: inout MutableSpan<UInt8>) throws(NetError) -> Int {
let result = httpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, let result = buffer.withUnsafeMutableBufferPointer { buffer in
UInt32(buffer.count)) httpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, UInt32(buffer.count))
}
if result < 0 { if result < 0 {
throw NetError(rawValue: result) ?? .unknown throw NetError(rawValue: result) ?? .unknown
} }
@@ -112,8 +112,10 @@ extension Network {
/// Reads up to `buffer.count` bytes, waiting up to the read timeout. /// Reads up to `buffer.count` bytes, waiting up to the read timeout.
/// Returns the number of bytes read. /// Returns the number of bytes read.
public func read(into buffer: UnsafeMutableRawBufferPointer) throws(NetError) -> Int { public func read(into buffer: inout MutableSpan<UInt8>) throws(NetError) -> Int {
let result = tcpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) let result = buffer.withUnsafeMutableBufferPointer { buffer in
tcpAPI.pointee.read.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count)
}
if result < 0 { if result < 0 {
throw NetError(rawValue: result) ?? .unknown throw NetError(rawValue: result) ?? .unknown
} }
@@ -133,11 +135,13 @@ extension Network {
return bytes return bytes
} }
/// Writes the buffer to the connection. Returns the number of bytes /// Writes the bytes to the connection. Returns the number of bytes
/// accepted. /// accepted.
@discardableResult @discardableResult
public func write(_ buffer: UnsafeRawBufferPointer) throws(NetError) -> Int { public func write(_ bytes: Span<UInt8>) throws(NetError) -> Int {
let result = tcpAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) let result = bytes.withUnsafeBufferPointer { buffer in
tcpAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count)
}
if result < 0 { if result < 0 {
throw NetError(rawValue: result) ?? .unknown throw NetError(rawValue: result) ?? .unknown
} }
@@ -148,13 +152,9 @@ extension Network {
/// accepted. /// accepted.
@discardableResult @discardableResult
public func write(_ bytes: [UInt8]) throws(NetError) -> Int { public func write(_ bytes: [UInt8]) throws(NetError) -> Int {
let result = bytes.withUnsafeBytes { buffer in try bytes.withUnsafeBufferPointer { buffer throws(NetError) in
tcpAPI.pointee.write.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count) try write(buffer.span)
} }
if result < 0 {
throw NetError(rawValue: result) ?? .unknown
}
return Int(result)
} }
} }
} }
@@ -1,8 +1,9 @@
extension Sound.Effect { extension Sound.Effect {
/// Processes up to `AUDIO_FRAMES_PER_CYCLE` sample frames in signed /// Processes up to `AUDIO_FRAMES_PER_CYCLE` sample frames in signed
/// Q8.24 format. `bufferActive` is `false` when the input buffer is /// Q8.24 format. `right` is empty when the channel is mono.
/// silent. Returns `true` if the effect produced output. /// `bufferActive` is `false` when the input buffer is silent. Returns
public typealias Processor = (_ left: UnsafeMutableBufferPointer<Int32>, /// `true` if the effect produced output.
_ right: UnsafeMutableBufferPointer<Int32>?, public typealias Processor = (_ left: inout MutableSpan<Int32>,
_ right: inout MutableSpan<Int32>,
_ bufferActive: Bool) -> Bool _ bufferActive: Bool) -> Bool
} }
@@ -30,9 +30,9 @@ extension Sound {
guard let effect, let left, guard let effect, let left,
let userdata = effectAPI.pointee.getUserdata.unsafelyUnwrapped(effect) else { return 0 } let userdata = effectAPI.pointee.getUserdata.unsafelyUnwrapped(effect) else { return 0 }
let box = Unmanaged<ProcessorBox>.fromOpaque(userdata).takeUnretainedValue() let box = Unmanaged<ProcessorBox>.fromOpaque(userdata).takeUnretainedValue()
let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)) var leftSpan = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)).mutableSpan
let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(nsamples)) } var rightSpan = UnsafeMutableBufferPointer(start: right, count: right == nil ? 0 : Int(nsamples)).mutableSpan
return box.processor(leftBuffer, rightBuffer, bufactive != 0) ? 1 : 0 return box.processor(&leftSpan, &rightSpan, bufactive != 0) ? 1 : 0
}, box.toOpaque()).unsafelyUnwrapped }, box.toOpaque()).unsafelyUnwrapped
isOwned = true isOwned = true
} }
+4 -4
View File
@@ -51,14 +51,14 @@ extension Sound {
/// Sets a callback that records microphone input. Return `false` from the /// Sets a callback that records microphone input. Return `false` from the
/// callback to stop recording. Pass `nil` to stop recording immediately. /// callback to stop recording. Pass `nil` to stop recording immediately.
/// The buffer contains mono 16-bit samples. /// The span contains mono 16-bit samples.
@discardableResult @discardableResult
public static func setMicCallback(source: MicSource = .autodetect, public static func setMicCallback(source: MicSource = .autodetect,
_ callback: ((UnsafeMutableBufferPointer<Int16>) -> Bool)?) -> Bool { _ callback: ((Span<Int16>) -> Bool)?) -> Bool {
micCallback = callback micCallback = callback
if callback != nil { if callback != nil {
return snd.pointee.setMicCallback.unsafelyUnwrapped({ _, buffer, length in return snd.pointee.setMicCallback.unsafelyUnwrapped({ _, buffer, length in
let samples = UnsafeMutableBufferPointer(start: buffer, count: Int(length)) let samples = UnsafeBufferPointer(start: buffer, count: Int(length)).span
return Sound.micCallback?(samples) == true ? 1 : 0 return Sound.micCallback?(samples) == true ? 1 : 0
}, nil, CPlaydate.MicSource(CPlaydate.MicSource.RawValue(source.rawValue))) != 0 }, nil, CPlaydate.MicSource(CPlaydate.MicSource.RawValue(source.rawValue))) != 0
} else { } else {
@@ -66,7 +66,7 @@ extension Sound {
} }
} }
nonisolated(unsafe) private static var micCallback: ((UnsafeMutableBufferPointer<Int16>) -> Bool)? nonisolated(unsafe) private static var micCallback: ((Span<Int16>) -> Bool)?
/// Asks the user for permission to record from the microphone. `purpose` /// Asks the user for permission to record from the microphone. `purpose`
/// is shown in the permission prompt. The completion receives whether /// is shown in the permission prompt. The completion receives whether
@@ -1,6 +1,6 @@
extension Sound.CallbackSource { extension Sound.CallbackSource {
/// Fills the sample buffers and returns `true` if output was /// Fills the sample buffers and returns `true` if output was
/// produced. `right` is non-nil only for stereo sources. /// produced. `right` is empty for mono sources.
public typealias Callback = (_ left: UnsafeMutableBufferPointer<Int16>, public typealias Callback = (_ left: inout MutableSpan<Int16>,
_ right: UnsafeMutableBufferPointer<Int16>?) -> Bool _ right: inout MutableSpan<Int16>) -> Bool
} }
@@ -27,9 +27,9 @@ extension Sound {
UnsafeMutablePointer<Int16>?, Int32) -> Int32 = { context, left, right, length in UnsafeMutablePointer<Int16>?, Int32) -> Int32 = { context, left, right, length in
guard let context, let left else { return 0 } guard let context, let left else { return 0 }
let source = Unmanaged<CallbackSource>.fromOpaque(context).takeUnretainedValue() let source = Unmanaged<CallbackSource>.fromOpaque(context).takeUnretainedValue()
let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(length)) var leftSpan = UnsafeMutableBufferPointer(start: left, count: Int(length)).mutableSpan
let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(length)) } var rightSpan = UnsafeMutableBufferPointer(start: right, count: right == nil ? 0 : Int(length)).mutableSpan
return source.callback(leftBuffer, rightBuffer) ? 1 : 0 return source.callback(&leftSpan, &rightSpan) ? 1 : 0
} }
/// Attaches the C object created for this source. /// Attaches the C object created for this source.
@@ -7,7 +7,7 @@ extension Sound {
var loopCallback: ((FilePlayer) -> Void)? var loopCallback: ((FilePlayer) -> Void)?
var fadeCallback: ((FilePlayer) -> Void)? var fadeCallback: ((FilePlayer) -> Void)?
var mp3DataSource: ((UnsafeMutableBufferPointer<UInt8>) -> Int)? var mp3DataSource: ((inout MutableSpan<UInt8>) -> Int)?
private var retainedRateModulator: SignalValue? private var retainedRateModulator: SignalValue?
override init(pointer: OpaquePointer?, isOwned: Bool) { override init(pointer: OpaquePointer?, isOwned: Bool) {
@@ -131,13 +131,13 @@ extension Sound {
/// fills the buffer and returns the number of bytes written; return 0 /// fills the buffer and returns the number of bytes written; return 0
/// to signal the end of the stream. /// to signal the end of the stream.
public func setMP3StreamSource(bufferLength: Float, public func setMP3StreamSource(bufferLength: Float,
_ dataSource: @escaping (UnsafeMutableBufferPointer<UInt8>) -> Int) { _ dataSource: @escaping (inout MutableSpan<UInt8>) -> Int) {
mp3DataSource = dataSource mp3DataSource = dataSource
FilePlayer.api.pointee.setMP3StreamSource.unsafelyUnwrapped(pointer, { data, bytes, userdata in FilePlayer.api.pointee.setMP3StreamSource.unsafelyUnwrapped(pointer, { data, bytes, userdata in
guard let userdata, let data else { return 0 } guard let userdata, let data else { return 0 }
let player = Unmanaged<FilePlayer>.fromOpaque(userdata).takeUnretainedValue() let player = Unmanaged<FilePlayer>.fromOpaque(userdata).takeUnretainedValue()
let buffer = UnsafeMutableBufferPointer(start: data, count: Int(bytes)) var buffer = UnsafeMutableBufferPointer(start: data, count: Int(bytes)).mutableSpan
return Int32(player.mp3DataSource?(buffer) ?? 0) return Int32(player.mp3DataSource?(&buffer) ?? 0)
}, Unmanaged.passUnretained(self).toOpaque(), bufferLength) }, Unmanaged.passUnretained(self).toOpaque(), bufferLength)
} }
@@ -75,9 +75,9 @@ extension Sound {
{ userdata, left, right, nsamples, rate, drate in { userdata, left, right, nsamples, rate, drate in
guard let userdata, let left else { return 0 } guard let userdata, let left else { return 0 }
let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue() let box = Unmanaged<GeneratorBox>.fromOpaque(userdata).takeUnretainedValue()
let leftBuffer = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)) var leftSpan = UnsafeMutableBufferPointer(start: left, count: Int(nsamples)).mutableSpan
let rightBuffer = right.map { UnsafeMutableBufferPointer(start: $0, count: Int(nsamples)) } var rightSpan = UnsafeMutableBufferPointer(start: right, count: right == nil ? 0 : Int(nsamples)).mutableSpan
return Int32(box.generator.render(leftBuffer, rightBuffer, rate, drate)) return Int32(box.generator.render(&leftSpan, &rightSpan, rate, drate))
}, },
{ userdata, note, velocity, length in { userdata, note, velocity, length in
guard let userdata else { return } guard let userdata else { return }
@@ -2,11 +2,11 @@ extension Sound.Synth {
/// Custom generator callbacks. Samples are in signed Q8.24 format. /// Custom generator callbacks. Samples are in signed Q8.24 format.
public struct Generator { public struct Generator {
/// Renders up to 256 sample frames into `left` (and `right` for /// Renders up to 256 sample frames into `left` (and `right` for
/// stereo generators). `rate` is the per-frame phase step in /// stereo generators; it is empty for mono ones). `rate` is the per-frame phase step in
/// Q0.32 format and `drate` its per-frame change. Returns the /// Q0.32 format and `drate` its per-frame change. Returns the
/// number of frames rendered. /// number of frames rendered.
public var render: (_ left: UnsafeMutableBufferPointer<Int32>, public var render: (_ left: inout MutableSpan<Int32>,
_ right: UnsafeMutableBufferPointer<Int32>?, _ right: inout MutableSpan<Int32>,
_ rate: UInt32, _ drate: Int32) -> Int _ rate: UInt32, _ drate: Int32) -> Int
/// Called when a note starts. `length` is -1 for indefinite notes. /// Called when a note starts. `length` is -1 for indefinite notes.
public var noteOn: ((_ note: Sound.MIDINote, _ velocity: Float, _ length: Float) -> Void)? public var noteOn: ((_ note: Sound.MIDINote, _ velocity: Float, _ length: Float) -> Void)?
@@ -17,8 +17,8 @@ extension Sound.Synth {
/// valid. /// valid.
public var setParameter: ((_ parameter: Int, _ value: Float) -> Bool)? public var setParameter: ((_ parameter: Int, _ value: Float) -> Bool)?
public init(render: @escaping (_ left: UnsafeMutableBufferPointer<Int32>, public init(render: @escaping (_ left: inout MutableSpan<Int32>,
_ right: UnsafeMutableBufferPointer<Int32>?, _ right: inout MutableSpan<Int32>,
_ rate: UInt32, _ drate: Int32) -> Int, _ rate: UInt32, _ drate: Int32) -> Int,
noteOn: ((_ note: Sound.MIDINote, _ velocity: Float, _ length: Float) -> Void)? = nil, noteOn: ((_ note: Sound.MIDINote, _ velocity: Float, _ length: Float) -> Void)? = nil,
release: ((_ stop: Bool) -> Void)? = nil, release: ((_ stop: Bool) -> Void)? = nil,
+6 -2
View File
@@ -307,8 +307,12 @@ extension System {
/// Sends data over the mirror connection. Returns `false` if mirroring is /// Sends data over the mirror connection. Returns `false` if mirroring is
/// not active or the send fails. /// not active or the send fails.
@discardableResult @discardableResult
public static func sendMirrorData(command: UInt8, data: UnsafeMutableRawBufferPointer) -> Bool { public static func sendMirrorData(command: UInt8, data: Span<UInt8>) -> Bool {
api.pointee.sendMirrorData.unsafelyUnwrapped(command, data.baseAddress, Int32(data.count)) data.withUnsafeBufferPointer { buffer in
// The C API takes a non-const pointer but only reads the data.
api.pointee.sendMirrorData.unsafelyUnwrapped(
command, UnsafeMutableRawPointer(mutating: buffer.baseAddress), Int32(buffer.count))
}
} }
/// OS, language, and pdx version information. /// OS, language, and pdx version information.
+4 -2
View File
@@ -146,7 +146,8 @@ struct WrapperTests {
var produced = 0 var produced = 0
let source = Sound.addSource(stereo: false) { left, right in let source = Sound.addSource(stereo: false) { left, right in
produced += left.count produced += left.count
#expect(right == nil) let isMono = right.isEmpty // #expect cannot capture a span
#expect(isMono)
return true return true
} }
#expect(Sound.CallbackSource.live.count == baseline + 1) #expect(Sound.CallbackSource.live.count == baseline + 1)
@@ -280,7 +281,8 @@ struct WrapperTests {
let effect = Sound.Effect(processor: { left, right, _ in let effect = Sound.Effect(processor: { left, right, _ in
_ = token _ = token
processed += left.count processed += left.count
#expect(right == nil) let isMono = right.isEmpty // #expect cannot capture a span
#expect(isMono)
return true return true
}) })