Skip to content

Commit

Permalink
Merge branch 'AudioKit:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
jcavar authored Aug 26, 2024
2 parents 9312cdd + 38fff77 commit 118ec09
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
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
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// swift-tools-version:5.5
// swift-tools-version:5.9

import PackageDescription

let package = Package(
name: "AudioKit",
platforms: [.macOS(.v11), .iOS(.v13), .tvOS(.v13)],
platforms: [.macOS(.v11), .iOS(.v13), .tvOS(.v13), .visionOS(.v1)],
products: [.library(name: "AudioKit", targets: ["AudioKit"])],
targets: [
.target(
Expand Down
14 changes: 9 additions & 5 deletions Sources/AudioKit/MIDI/MIDISampler.swift
Original file line number Diff line number Diff line change
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
23 changes: 14 additions & 9 deletions Sources/AudioKit/MIDI/Packets/MIDIPacketList+SequenceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,27 @@ extension MIDIPacketList: Sequence {
/// Create the sequence
/// - Returns: Iterator of elements
public func makeIterator() -> AnyIterator<Element> {

withUnsafePointer(to: packet) { ptr in
// Copy packets to prevent AddressSanitizer: stack-use-after-return on address
let packets = withUnsafePointer(to: packet) { ptr in
var packets = [MIDIPacket]()
var p = ptr
var idx: UInt32 = 0

return AnyIterator {
guard idx < self.numPackets else {
return nil
for _ in 0..<numPackets {
if let packet = extractPacket(p) {
packets.append(packet)
} else {
// If a packet cannot be extracted, return already extracted and ignore all
// subsequent packets.
return packets
}

idx += 1
let packet = extractPacket(p)
p = UnsafePointer(MIDIPacketNext(p))
return packet
}

return packets
}

return AnyIterator(packets.makeIterator())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public extension AudioPlayer {
/// Schedule a buffer to play at a a specific time, with options
/// - Parameters:
/// - buffer: Buffer to play
/// - when: Time to pay
/// - when: Time to play
/// - options: Buffer options
@available(*, deprecated, renamed: "schedule(at:)")
func scheduleBuffer(_ buffer: AVAudioPCMBuffer,
Expand All @@ -21,7 +21,7 @@ public extension AudioPlayer {
/// Schedule a buffer to play from a URL, at a a specific time, with options
/// - Parameters:
/// - url: URL Location of buffer
/// - when: Time to pay
/// - when: Time to play
/// - options: Buffer options
@available(*, deprecated, renamed: "schedule(at:)")
func scheduleBuffer(url: URL,
Expand Down
15 changes: 3 additions & 12 deletions Sources/AudioKit/Nodes/Playback/AudioPlayer/AudioPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ public class AudioPlayer: Node {

/// Completion handler to be called when file or buffer is done playing.
/// This also will be called when looping from disk,
/// but no completion is called when looping seamlessly when buffered
/// but no completion is called when looping seamlessly when buffered.
/// This runs on an asyncronous thread and
/// requires thread-safety practice. See: https://web.mit.edu/6.031/www/fa17/classes/20-thread-safety/.
public var completionHandler: AVAudioNodeCompletionHandler?

/// The file to use with the player. This can be set while the player is playing.
Expand Down Expand Up @@ -197,18 +199,7 @@ public class AudioPlayer: Node {
// MARK: - Internal functions

func internalCompletionHandler() {
guard status == .playing,
!isSeeking,
engine?.isInManualRenderingMode == false else { return }

completionHandler?()

if isLooping, !isBuffered {
status = .stopped
play()
} else {
status = .stopped
}
}

// MARK: - Init
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
26 changes: 26 additions & 0 deletions Tests/AudioKitTests/Node Tests/Player Tests/AudioPlayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -522,4 +522,30 @@ class AudioPlayerTests: XCTestCase {
XCTAssert(player.status == .stopped)
testMD5(audio)
}

// https://github.com/AudioKit/AudioKit/issues/2916
func testCompletionHandler() {
guard let counting = Bundle.module.url(forResource: "TestResources/12345", withExtension: "wav")
else {
XCTFail("Couldn't find file")
return
}
guard let drumLoop = Bundle.module.url(forResource: "TestResources/drumloop", withExtension: "wav")
else {
XCTFail("Couldn't find file")
return
}
let engine = AudioEngine()
let player = AudioPlayer()
engine.output = player
player.completionHandler = {
try? player.load(url: drumLoop)
player.play()
}
try? player.load(url: counting)
let audio = engine.startTest(totalDuration: 9.0)
player.play()
audio.append(engine.render(duration: 9.0))
testMD5(audio)
}
}
1 change: 1 addition & 0 deletions Tests/AudioKitTests/ValidatedMD5s.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let validatedMD5s: [String: [String]] = [
"-[AudioPlayerTests testSeekWillStop]": ["84b026cbdf45d9c5f5659f1106fdee6a"],
"-[AudioPlayerTests testSeekWillContinueLooping]": ["5becbd9530850f217f95ee1142a8db30"],
"-[AudioPlayerTests testPlaybackWillStopWhenSettingLoopingForBuffer]": ["5becbd9530850f217f95ee1142a8db30"],
"-[AudioPlayerTests testCompletionHandler]": ["931361a78333a754a4c357aa82301e94"],
"-[CompressorTests testAttackTime]": ["f2da585c3e9838c1a41f1a5f34c467d0"],
"-[CompressorTests testDefault]": ["3064ef82b30c512b2f426562a2ef3448"],
"-[CompressorTests testHeadRoom]": ["98ac5f20a433ba5a858c461aa090d81f", "db27f010ec481cd02ca73b8652c4f7c1"],
Expand Down

0 comments on commit 118ec09

Please sign in to comment.