Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/AudioKit/AudioKit
Browse files Browse the repository at this point in the history
  • Loading branch information
aure committed Apr 26, 2024
2 parents b2dbaed + afef8a7 commit 2ebd422
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 91 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/swift.yml
Expand Up @@ -15,7 +15,8 @@ jobs:

runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@v4
- name: Build
run: xcodebuild -scheme AudioKit -destination "platform=iOS Simulator,name=iPhone 14"
- name: Run tests
Expand Down
5 changes: 4 additions & 1 deletion Package.swift
Expand Up @@ -7,7 +7,10 @@ let package = Package(
platforms: [.macOS(.v11), .iOS(.v13), .tvOS(.v13), .visionOS(.v1)],
products: [.library(name: "AudioKit", targets: ["AudioKit"])],
targets: [
.target(name: "AudioKit"),
.target(
name: "AudioKit",
resources: [.process("Resources")]
),
.testTarget(name: "AudioKitTests", dependencies: ["AudioKit"], resources: [.copy("TestResources/")]),
]
)
2 changes: 1 addition & 1 deletion Sources/AudioKit/MIDI/Enums/MIDICustomMetaEvent.swift
Expand Up @@ -125,7 +125,7 @@ public struct MIDICustomMetaEvent: MIDIMessage {
let type = MIDICustomMetaEventType(rawValue: data[1]),
let vlqLength = MIDIVariableLengthQuantity(fromBytes: Array(data.suffix(from: 2))) {
self.length = Int(vlqLength.quantity)
self.data = Array(data.prefix(3 + length)) //drop excess data
self.data = Array(data.prefix(2 + vlqLength.length + length)) // drop excess data
self.type = type
} else {
return nil
Expand Down
3 changes: 2 additions & 1 deletion Sources/AudioKit/MIDI/MIDI+Sending.swift
Expand Up @@ -356,7 +356,8 @@ extension MIDI {
time: MIDITimeStamp = mach_absolute_time(),
endpointsUIDs: [MIDIUniqueID]? = nil,
virtualOutputPorts: [MIDIPortRef]? = nil) {
let packetListPointer: UnsafeMutablePointer<MIDIPacketList> = UnsafeMutablePointer.allocate(capacity: 1)
let packetListPointer: UnsafeMutablePointer<MIDIPacketList> = UnsafeMutablePointer.allocate(
capacity: data.count)

var packet: UnsafeMutablePointer<MIDIPacket> = MIDIPacketListInit(packetListPointer)
packet = MIDIPacketListAdd(packetListPointer, 1_024, packet, time, data.count, data)
Expand Down
14 changes: 9 additions & 5 deletions Sources/AudioKit/MIDI/MIDISampler.swift
Expand Up @@ -43,12 +43,16 @@ open class MIDISampler: AppleSampler, NamedNode {
fatalError("Expected AU to respond to MIDI.")
}
CheckError(MIDIDestinationCreateWithBlock(midiClient, cfName, &midiIn) { packetList, _ in
for e in packetList.pointee {
for event in e {
event.data.withUnsafeBufferPointer { ptr in
guard let ptr = ptr.baseAddress else { return }
midiBlock(AUEventSampleTimeImmediate, 0, event.data.count, ptr)
withUnsafePointer(to: packetList.pointee.packet) { packetPtr in
var p = packetPtr
for _ in 1...packetList.pointee.numPackets {
for event in p.pointee {
event.data.withUnsafeBufferPointer { ptr in
guard let ptr = ptr.baseAddress else { return }
midiBlock(AUEventSampleTimeImmediate, 0, event.data.count, ptr)
}
}
p = UnsafePointer<MIDIPacket>(MIDIPacketNext(p))
}
}
})
Expand Down
17 changes: 17 additions & 0 deletions Sources/AudioKit/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>8FFB.1</string>
</array>
</dict>
</array>
</dict>
</plist>
Expand Up @@ -668,7 +668,7 @@ open class AppleSequencer: NSObject {
MusicSequenceGetIndTrack(existingSequence, UInt32(i), &musicTrack)
}
if let existingMusicTrack = musicTrack {
tracks.append(MusicTrackManager(musicTrack: existingMusicTrack, name: "InitializedTrack"))
tracks.append(MusicTrackManager(musicTrack: existingMusicTrack, name: ""))
}
}

Expand Down
102 changes: 94 additions & 8 deletions Sources/AudioKit/Sequencing/Apple Sequencer/MusicTrack.swift
Expand Up @@ -81,25 +81,47 @@ open class MusicTrackManager {
///
/// - parameter musicTrack: An Apple Music Track
/// - parameter name: Name for the track
/// - if name is an empty string, the name is read from track name meta event.
/// - if name is not empty, that name is used and a track name meta event is added or replaced.
///
public init(musicTrack: MusicTrack, name: String = "Unnamed") {
self.name = name
internalMusicTrack = musicTrack
trackPointer = UnsafeMutablePointer(musicTrack)

let data = [MIDIByte](name.utf8)

let metaEventPtr = MIDIMetaEvent.allocate(metaEventType: 3, data: data)
defer { metaEventPtr.deallocate() }

let result = MusicTrackNewMetaEvent(musicTrack, MusicTimeStamp(0), metaEventPtr)
if result != 0 {
Log("Unable to name Track")
if name == "" {
// Use track name from meta event (or empty name if no meta event found)
self.name = tryReadTrackNameFromMetaEvent() ?? ""
} else {
// Clear track name meta event if exists
clearMetaEvent(3)
// Add meta event with new track name
let data = [MIDIByte](name.utf8)
addMetaEvent(metaEventType: 3, data: data)
}

initSequence()
}

/// Try to read existing track name from meta event
///
/// - returns: the found track name or nil
///
func tryReadTrackNameFromMetaEvent() -> String? {
var trackName: String?

eventData?.forEach({ event in
if event.type == kMusicEventType_Meta {
let metaEventPointer = UnsafeMIDIMetaEventPointer(event.data)
let metaEvent = metaEventPointer!.event.pointee
if metaEvent.metaEventType == 0x03 {
trackName = String(decoding: metaEventPointer!.payload, as: UTF8.self)
}
}
})
return trackName
}

/// Initialize with a music track and the NoteEventSequence
///
/// - parameter musicTrack: An Apple Music Track
Expand Down Expand Up @@ -360,6 +382,48 @@ open class MusicTrackManager {
DisposeMusicEventIterator(iterator)
}

/// Clear a specific meta event
public func clearMetaEvent(_ metaEventType: MIDIByte) {
guard let track = internalMusicTrack else {
Log("internalMusicTrack does not exist")
return
}
var tempIterator: MusicEventIterator?
NewMusicEventIterator(track, &tempIterator)
guard let iterator = tempIterator else {
Log("Unable to create iterator in clearNote")
return
}
var eventTime = MusicTimeStamp(0)
var eventType = MusicEventType()
var eventData: UnsafeRawPointer?
var eventDataSize: UInt32 = 0
var hasNextEvent: DarwinBoolean = false
var isReadyForNextEvent: Bool

MusicEventIteratorHasCurrentEvent(iterator, &hasNextEvent)
while hasNextEvent.boolValue {
isReadyForNextEvent = true
MusicEventIteratorGetEventInfo(iterator,
&eventTime,
&eventType,
&eventData,
&eventDataSize)
if eventType == kMusicEventType_Meta {
if let convertedData = eventData?.load(as: MIDIMetaEvent.self) {
if convertedData.metaEventType == metaEventType {
MusicEventIteratorDeleteEvent(iterator)
isReadyForNextEvent = false
}
}
}

if isReadyForNextEvent { MusicEventIteratorNextEvent(iterator) }
MusicEventIteratorHasCurrentEvent(iterator, &hasNextEvent)
}
DisposeMusicEventIterator(iterator)
}

/// Determine if the sequence is empty
open var isEmpty: Bool {
guard let track = internalMusicTrack else {
Expand Down Expand Up @@ -559,6 +623,28 @@ open class MusicTrackManager {
}
}

/// Add MetaEvent to sequence
///
/// - Parameters:
/// - data: The MIDI data byte array - standard bytes containing the length of the data are added automatically
/// - position: Where in the sequence to start the note (expressed in beats)
///
public func addMetaEvent(metaEventType: MIDIByte,
data: [MIDIByte],
position: Duration = Duration(beats: 0)) {
guard let track = internalMusicTrack else {
Log("internalMusicTrack does not exist")
return
}
let metaEventPtr = MIDIMetaEvent.allocate(metaEventType: metaEventType, data: data)
defer { metaEventPtr.deallocate() }

let result = MusicTrackNewMetaEvent(track, position.musicTimeStamp, metaEventPtr)
if result != 0 {
Log("Unable to write meta event")
}
}

/// Add Pitch Bend change to sequence
///
/// - Parameters:
Expand Down
2 changes: 1 addition & 1 deletion Sources/AudioKit/Taps/NodeRecorder.swift
Expand Up @@ -63,7 +63,7 @@ open class NodeRecorder: NSObject {

private var recordedFileURL: URL?

private static var recordedFiles = [URL]()
public static var recordedFiles = [URL]()

/// Callback type
public typealias AudioDataCallback = ([Float], AVAudioTime) -> Void
Expand Down
7 changes: 7 additions & 0 deletions Tests/AudioKitTests/MIDI Tests/UMPParsingTests.swift
Expand Up @@ -121,6 +121,13 @@ class UMPParsingTests: XCTestCase {
portID: sender.uniqueID)])
}

// Test for heap overflow encountered in https://github.com/AudioKit/AudioKit/issues/2890
// will timeout if heap overflow is encountered
func testHeapAllocation() {
let overflowingMessage: [UInt8] = Array(repeating: 0x01, count: 260)
midi.sendMessage(overflowingMessage)
}

func testSimultaneousStreams() throws {
throw XCTSkip("skip test for now: sysex joining is not thread safe")

Expand Down
30 changes: 24 additions & 6 deletions Tests/AudioKitTests/Node Tests/GenericNodeTests.swift
Expand Up @@ -108,25 +108,43 @@ class GenericNodeTests: XCTestCase {

func testEffects() {
nodeParameterTest(md5: "d15c926f3da74630f986f7325adf044c", factory: { input in Compressor(input) })
nodeParameterTest(md5: "ddfea2413fac59b7cdc71f1b8ed733a2", factory: { input in Decimator(input) })
nodeParameterTest(
md5: "ddfea2413fac59b7cdc71f1b8ed733a2",
factory: { input in Decimator(input) },
m1MD5: "5dc2828d3ae812e780e6778d2d075615")
nodeParameterTest(md5: "d12817d8f84dfee6380030c5ddf7916b", factory: { input in Delay(input, time: 0.01) })
nodeParameterTest(md5: "583791002739d735fba13f6bac48dba6", factory: { input in Distortion(input) })
nodeParameterTest(
md5: "583791002739d735fba13f6bac48dba6",
factory: { input in Distortion(input) },
m1MD5: "0135e68b5e4a208d33b4ef44a4479e4c")
nodeParameterTest(md5: "0ae9a6b248486f343c55bf0818c3007d", factory: { input in PeakLimiter(input) })
nodeParameterTest(md5: "b31ce15bb38716fd95070d1299679d3a", factory: { input in RingModulator(input) })
nodeParameterTest(
md5: "b31ce15bb38716fd95070d1299679d3a",
factory: { input in RingModulator(input) },
m1MD5: "b1acb3ec51f58d18d0d5df2d2c073a50")

#if os(iOS)
nodeParameterTest(md5: "28d2cb7a5c1e369ca66efa8931d31d4d", factory: { player in Reverb(player) })
#endif

#if os(macOS)
nodeParameterTest(md5: "bff0b5fa57e589f5192b17194d9a43cb", factory: { player in Reverb(player) })
nodeParameterTest(
md5: "bff0b5fa57e589f5192b17194d9a43cb",
factory: { player in Reverb(player) },
m1MD5: "ac8d2c81f0c74217d3fff003cbf28d68")
#endif
}

func testFilters() {
nodeParameterTest(md5: "03e7b02e4fceb5fe6a2174740eda7e36", factory: { input in HighPassFilter(input) })
nodeParameterTest(md5: "af137ecbe57e669340686e9721a2d1f2", factory: { input in HighShelfFilter(input) })
nodeParameterTest(
md5: "af137ecbe57e669340686e9721a2d1f2",
factory: { input in HighShelfFilter(input) },
m1MD5: "2c058606c0e5fb7c6e245eb0f098366d")
nodeParameterTest(md5: "a43c821e13efa260d88d522b4d29aa45", factory: { input in LowPassFilter(input) })
nodeParameterTest(md5: "2007d443458f8536b854d111aae4b51b", factory: { input in LowShelfFilter(input) })
nodeParameterTest(
md5: "2007d443458f8536b854d111aae4b51b",
factory: { input in LowShelfFilter(input) },
m1MD5: "197e9f80a2e1fecb7492845fb16ca8c8")
}
}

0 comments on commit 2ebd422

Please sign in to comment.