Skip to content

Commit fd4ba17

Browse files
committed
Initial version of AudioKitEX separated from AudioKit
1 parent 7ac0501 commit fd4ba17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+4392
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,11 @@ fastlane/test_output
8888
# https://github.com/johnno1962/injectionforxcode
8989

9090
iOSInjectionProject/
91+
92+
.DS_Store
93+
/.build
94+
/Packages
95+
/*.xcodeproj
96+
xcuserdata/
97+
DerivedData/
98+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Package.resolved

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "AudioKitEX",
8+
platforms: [.macOS(.v10_13), .iOS(.v11), .tvOS(.v11)],
9+
products: [.library(name: "AudioKitEX", targets: ["AudioKitEX"])],
10+
dependencies: [.package(url: "https://github.com/AudioKit/AudioKit", .branch("swift-only"))],
11+
targets: [
12+
.target(name: "AudioKitEX", dependencies: ["AudioKit", "CAudioKitEX"]),
13+
.target(name: "CAudioKitEX", cxxSettings: [.headerSearchPath(".")]),
14+
.testTarget(name: "AudioKitEXTests", dependencies: ["AudioKitEX"], resources: [.copy("TestResources/")])
15+
],
16+
cxxLanguageStandard: .cxx14
17+
)
18+
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2+
3+
import AudioToolbox
4+
import AVFoundation
5+
import CAudioKitEX
6+
import AudioKit
7+
8+
/// AudioUnit which instantiates a DSP kernel based on the componentSubType.
9+
open class AudioKitAU: AUAudioUnit {
10+
// MARK: AUAudioUnit Overrides
11+
12+
private var inputBusArray: [AUAudioUnitBus] = []
13+
private var outputBusArray: [AUAudioUnitBus] = []
14+
private var internalBuffers: [AVAudioPCMBuffer] = []
15+
16+
// Default supported channel capabilities
17+
public var supportedLeftChannelCount: NSNumber = 2
18+
public var supportedRightChannelCount: NSNumber = 2
19+
20+
override public var channelCapabilities: [NSNumber]? {
21+
return [supportedLeftChannelCount, supportedRightChannelCount]
22+
}
23+
24+
/// Allocate the render resources
25+
override public func allocateRenderResources() throws {
26+
try super.allocateRenderResources()
27+
28+
if let inputFormat = inputBusArray.first?.format {
29+
30+
// we don't need to allocate a buffer if we can process in place
31+
if !canProcessInPlace || inputBusArray.count > 1 {
32+
for i in inputBusArray.indices {
33+
if let buffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: maximumFramesToRender) {
34+
setBufferDSP(dsp, buffer.mutableAudioBufferList, i)
35+
internalBuffers.append(buffer)
36+
}
37+
}
38+
}
39+
}
40+
41+
if let outputFormat = outputBusArray.first?.format {
42+
allocateRenderResourcesDSP(dsp, outputFormat.channelCount, outputFormat.sampleRate)
43+
}
44+
}
45+
46+
/// Delllocate Render Resources
47+
override public func deallocateRenderResources() {
48+
super.deallocateRenderResources()
49+
deallocateRenderResourcesDSP(dsp)
50+
internalBuffers = []
51+
}
52+
53+
/// Reset the DSP
54+
override public func reset() {
55+
resetDSP(dsp)
56+
}
57+
58+
private lazy var auInputBusArray: AUAudioUnitBusArray = {
59+
AUAudioUnitBusArray(audioUnit: self, busType: .input, busses: inputBusArray)
60+
}()
61+
62+
/// Input busses
63+
override public var inputBusses: AUAudioUnitBusArray {
64+
return auInputBusArray
65+
}
66+
67+
private lazy var auOutputBusArray: AUAudioUnitBusArray = {
68+
AUAudioUnitBusArray(audioUnit: self, busType: .output, busses: outputBusArray)
69+
}()
70+
71+
/// Output bus array
72+
override public var outputBusses: AUAudioUnitBusArray {
73+
return auOutputBusArray
74+
}
75+
76+
/// Internal render block
77+
override public var internalRenderBlock: AUInternalRenderBlock {
78+
internalRenderBlockDSP(dsp)
79+
}
80+
81+
private var _parameterTree: AUParameterTree?
82+
83+
/// Parameter tree
84+
override public var parameterTree: AUParameterTree? {
85+
get { return _parameterTree }
86+
set {
87+
_parameterTree = newValue
88+
89+
_parameterTree?.implementorValueObserver = { [unowned self] parameter, value in
90+
setParameterValueDSP(self.dsp, parameter.address, value)
91+
}
92+
93+
_parameterTree?.implementorValueProvider = { [unowned self] parameter in
94+
getParameterValueDSP(self.dsp, parameter.address)
95+
}
96+
97+
_parameterTree?.implementorStringFromValueCallback = { parameter, value in
98+
if let value = value {
99+
return String(format: "%.2f", value.pointee)
100+
} else {
101+
return "Invalid"
102+
}
103+
}
104+
}
105+
}
106+
107+
/// Whether the unit can process in place
108+
override public var canProcessInPlace: Bool {
109+
return canProcessInPlaceDSP(dsp)
110+
}
111+
112+
/// Set in order to bypass processing
113+
override public var shouldBypassEffect: Bool {
114+
get { return getBypassDSP(dsp) }
115+
set { setBypassDSP(dsp, newValue) }
116+
}
117+
118+
// MARK: Lifecycle
119+
120+
/// DSP Reference
121+
public private(set) var dsp: DSPRef?
122+
123+
/// Initialize with component description and options
124+
/// - Parameters:
125+
/// - componentDescription: Audio Component Description
126+
/// - options: Audio Component Instantiation Options
127+
/// - Throws: error
128+
override public init(componentDescription: AudioComponentDescription,
129+
options: AudioComponentInstantiationOptions = []) throws {
130+
try super.init(componentDescription: componentDescription, options: options)
131+
132+
// Create pointer to C++ DSP code.
133+
dsp = akCreateDSP(componentDescription.componentSubType)
134+
assert(dsp != nil)
135+
136+
// create audio bus connection points
137+
let format = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 2)!
138+
for _ in 0..<inputBusCountDSP(dsp) {
139+
inputBusArray.append(try AUAudioUnitBus(format: format))
140+
}
141+
142+
// All AudioKit nodes have one output bus.
143+
outputBusArray.append(try AUAudioUnitBus(format: format))
144+
145+
parameterTree = AUParameterTree.createTree(withChildren: [])
146+
147+
}
148+
149+
deinit {
150+
deleteDSP(dsp)
151+
}
152+
153+
// MARK: AudioKit
154+
155+
/// Trigger something within the audio unit
156+
public func trigger(note: MIDINoteNumber, velocity: MIDIVelocity) {
157+
#if !os(tvOS)
158+
guard let midiBlock = scheduleMIDIEventBlock else {
159+
fatalError("Attempt to trigger audio unit which doesn't respond to MIDI.")
160+
}
161+
let event = MIDIEvent(noteOn: note, velocity: velocity, channel: 0)
162+
event.data.withUnsafeBufferPointer { ptr in
163+
guard let ptr = ptr.baseAddress else { return }
164+
midiBlock(AUEventSampleTimeImmediate, 0, event.data.count, ptr)
165+
}
166+
#endif
167+
}
168+
169+
/// Trigger something within the audio unit
170+
public func trigger() {
171+
trigger(note: 64, velocity: 127)
172+
}
173+
174+
/// Turn off the the trigger for a gate
175+
public func detrigger() {
176+
trigger(note: 64, velocity: 0)
177+
}
178+
179+
180+
/// Create an array of values to use as waveforms or other things inside an audio unit
181+
/// - Parameters:
182+
/// - wavetable: Array of float values
183+
/// - index: Optional index at which to set the table (useful for multiple waveform audio units)
184+
public func setWavetable(_ wavetable: [AUValue], index: Int = 0) {
185+
setWavetableDSP(dsp, wavetable, wavetable.count, Int32(index))
186+
}
187+
188+
/// Set wave table
189+
/// - Parameters:
190+
/// - data: A pointer to the data
191+
/// - size: Size of the table
192+
/// - index: Index at which to set the value
193+
public func setWavetable(data: UnsafePointer<AUValue>?, size: Int, index: Int = 0) {
194+
setWavetableDSP(dsp, data, size, Int32(index))
195+
}
196+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2+
3+
import AVFoundation
4+
import CAudioKitEX
5+
import Foundation
6+
7+
/// An automation curve (with curved segments) suitable for any time varying parameter.
8+
/// Includes functions for manipulating automation curves and conversion to linear automation ramps
9+
/// used by DSP code.
10+
public struct AutomationCurve {
11+
/// Shorter name
12+
public typealias Point = ParameterAutomationPoint
13+
14+
/// Array of points that make up the curve
15+
public var points: [Point]
16+
17+
/// Initialize with points
18+
/// - Parameter points: Array of points
19+
public init(points: [Point]) {
20+
self.points = points
21+
}
22+
23+
static func evalRamp(start: Float, segment: Point, time: Float, endTime: Float) -> Float {
24+
let remain = endTime - time
25+
let taper = segment.rampTaper
26+
let goal = segment.targetValue
27+
28+
// x is normalized position in ramp segment
29+
let x = (segment.rampDuration - remain) / segment.rampDuration
30+
let taper1 = start + (goal - start) * pow(x, abs(taper))
31+
let absxm1 = abs((segment.rampDuration - remain) / segment.rampDuration - 1.0)
32+
let taper2 = start + (goal - start) * (1.0 - pow(absxm1, 1.0 / abs(taper)))
33+
34+
return taper1 * (1.0 - segment.rampSkew) + taper2 * segment.rampSkew
35+
}
36+
37+
/// Returns a new piecewise-linear automation curve which can be handed off to the audio thread
38+
/// for efficient processing.
39+
///
40+
/// - Parameters:
41+
/// - initialValue: Starting point
42+
/// - resolution: Duration of each linear segment in seconds
43+
///
44+
/// - Returns: A new array of piecewise linear automation points
45+
public func evaluate(initialValue: AUValue, resolution: Float) -> [AutomationEvent] {
46+
var result = [AutomationEvent]()
47+
48+
// The last evaluated value
49+
var value = initialValue
50+
51+
for i in 0 ..< points.count {
52+
let point = points[i]
53+
54+
if point.isLinear() {
55+
result.append(AutomationEvent(targetValue: point.targetValue,
56+
startTime: point.startTime,
57+
rampDuration: point.rampDuration))
58+
value = point.targetValue
59+
60+
} else {
61+
// Cut off the end if another point comes along.
62+
let nextPointStart = i < points.count - 1 ? points[i + 1].startTime
63+
: Float.greatestFiniteMagnitude
64+
let endTime: Float = min(nextPointStart,
65+
point.startTime + point.rampDuration)
66+
67+
var t = point.startTime
68+
let start = value
69+
70+
// March t along the segment
71+
// this is effectively `while t <= endTime - resolution` without potentional for rounding errors
72+
for _ in 0 ..< Int(round(endTime / resolution)) {
73+
value = AutomationCurve.evalRamp(start: start,
74+
segment: point,
75+
time: t + resolution,
76+
endTime: point.startTime + point.rampDuration)
77+
78+
result.append(AutomationEvent(targetValue: value,
79+
startTime: t,
80+
rampDuration: resolution))
81+
82+
t += resolution
83+
84+
// safety check to not run past the final target value
85+
if t >= endTime { break }
86+
}
87+
}
88+
}
89+
90+
return result
91+
}
92+
93+
/// Replaces automation over a time range.
94+
///
95+
/// Use this when calculating a new automation curve after recording automation.
96+
///
97+
/// - Parameters:
98+
/// - range: time range
99+
/// - withPoints: new automation events
100+
/// - Returns: new automation curve
101+
public func replace(range: ClosedRange<Float>, withPoints newPoints: [(Float, AUValue)]) -> AutomationCurve {
102+
var result = points
103+
let startTime = range.lowerBound
104+
let stopTime = range.upperBound
105+
106+
// Clear existing points in segment range.
107+
result.removeAll { point in point.startTime >= startTime && point.startTime <= stopTime }
108+
109+
// Append recorded points.
110+
result.append(contentsOf: newPoints.map { point in
111+
Point(targetValue: point.1, startTime: point.0, rampDuration: 0.01)
112+
})
113+
114+
// Sort vector by time.
115+
result.sort { $0.startTime < $1.startTime }
116+
117+
return AutomationCurve(points: result)
118+
}
119+
}

0 commit comments

Comments
 (0)