Files
playdate-kit/Sources/PlaydateKit/Sound/Synth/Classes/Instrument.swift
T

107 lines
4.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
internal import CPlaydate
extension Sound {
/// A bank of synth voices for playing a sequence track. Wraps
/// `PDSynthInstrument`.
public final class Instrument {
private static var api: UnsafePointer<playdate_sound_instrument> { Playdate.instrumentAPI.unsafelyUnwrapped }
let pointer: OpaquePointer
let isOwned: Bool
private var retainedVoices: [Synth] = []
init(pointer: OpaquePointer, isOwned: Bool) {
self.pointer = pointer
self.isOwned = isOwned
}
public convenience init() {
self.init(pointer: Instrument.api.pointee.newInstrument.unsafelyUnwrapped().unsafelyUnwrapped,
isOwned: true)
}
deinit {
if isOwned {
Instrument.api.pointee.freeInstrument.unsafelyUnwrapped(pointer)
}
}
/// Adds a voice to the instrument, handling notes in
/// `rangeStart...rangeEnd` (0...127 handles all notes), transposed by
/// `transpose` half-steps.
@discardableResult
public func addVoice(_ synth: Synth, rangeStart: MIDINote = 0, rangeEnd: MIDINote = 127,
transpose: Float = 0) -> Bool {
let added = Instrument.api.pointee.addVoice.unsafelyUnwrapped(
pointer, synth.pointer, rangeStart, rangeEnd, transpose) != 0
if added, !retainedVoices.contains(where: { $0 === synth }) {
retainedVoices.append(synth)
}
return added
}
/// Plays a note at `frequency` Hz on an available voice. Returns the
/// synth used, if any.
@discardableResult
public func playNote(frequency: Float, velocity: Float = 1,
length: Float? = nil, when: UInt32 = 0) -> Synth? {
let synth = Instrument.api.pointee.playNote.unsafelyUnwrapped(
pointer, frequency, velocity, length ?? -1, when)
return voice(for: synth)
}
/// Plays a MIDI note on an available voice. Returns the synth used.
@discardableResult
public func playMIDINote(_ note: MIDINote, velocity: Float = 1,
length: Float? = nil, when: UInt32 = 0) -> Synth? {
let synth = Instrument.api.pointee.playMIDINote.unsafelyUnwrapped(
pointer, note, velocity, length ?? -1, when)
return voice(for: synth)
}
private func voice(for pointer: OpaquePointer?) -> Synth? {
guard let pointer else { return nil }
if let voice = retainedVoices.first(where: { $0.pointer == pointer }) {
return voice
}
return Synth(pointer: pointer, isOwned: false)
}
/// Bends played notes by `bend` × the pitch bend range.
public func setPitchBend(_ bend: Float) {
Instrument.api.pointee.setPitchBend.unsafelyUnwrapped(pointer, bend)
}
public func setPitchBendRange(halfSteps: Float) {
Instrument.api.pointee.setPitchBendRange.unsafelyUnwrapped(pointer, halfSteps)
}
public func setTranspose(halfSteps: Float) {
Instrument.api.pointee.setTranspose.unsafelyUnwrapped(pointer, halfSteps)
}
/// Releases the voice playing `note` at time `when` (0 = now).
public func noteOff(_ note: MIDINote, when: UInt32 = 0) {
Instrument.api.pointee.noteOff.unsafelyUnwrapped(pointer, note, when)
}
public func allNotesOff(when: UInt32 = 0) {
Instrument.api.pointee.allNotesOff.unsafelyUnwrapped(pointer, when)
}
public func setVolume(left: Float, right: Float) {
Instrument.api.pointee.setVolume.unsafelyUnwrapped(pointer, left, right)
}
public var volume: (left: Float, right: Float) {
var left: Float = 0, right: Float = 0
Instrument.api.pointee.getVolume.unsafelyUnwrapped(pointer, &left, &right)
return (left, right)
}
public var activeVoiceCount: Int {
Int(Instrument.api.pointee.activeVoiceCount.unsafelyUnwrapped(pointer))
}
}
}