Skip to content

Instantly share code, notes, and snippets.

@iluvcapra
Last active July 29, 2021 17:32
Show Gist options
  • Save iluvcapra/8d5a71c5271160bd974e7267fa85a2b3 to your computer and use it in GitHub Desktop.
Save iluvcapra/8d5a71c5271160bd974e7267fa85a2b3 to your computer and use it in GitHub Desktop.
//
// SpotToProToolsOperation.swift
// Bag of Holding
//
// Created by Jamie Hardt on 12/8/14.
// Copyright (c) 2014 Jamie Hardt. All rights reserved.
//
import Cocoa
import Foundation
import AVFoundation
import CoreMedia
extension Int16 {
func toAppleEventDescriptor() -> NSAppleEventDescriptor? {
var me = self
return NSAppleEventDescriptor(descriptorType: DescType(typeSInt16), bytes: &me, length: MemoryLayout<Int16>.size )
}
}
extension Int32 {
func toAppleEventDescriptor() -> NSAppleEventDescriptor? {
var me = self
return NSAppleEventDescriptor(descriptorType: DescType(typeSInt32), bytes: &me, length: MemoryLayout<Int32>.size )
}
}
extension URL {
func toAppleEventDescriptor() -> NSAppleEventDescriptor? {
if let urlData = self.absoluteString.data(using: String.Encoding.utf8, allowLossyConversion: false) {
let retObj = NSAppleEventDescriptor(descriptorType: DescType(typeFileURL), data: urlData)
return retObj?.coerce(toDescriptorType: DescType(typeAlias))
} else {
return nil
}
}
}
extension String {
func toTEXTAppleEventDescriptor() -> NSAppleEventDescriptor? {
if let stringData = self.data(using: String.Encoding.isoLatin1, allowLossyConversion: true) {
var shortLength = min(255, stringData.count)
let substringData = stringData.subdata(in: 0..<shortLength)
//let substringData = stringData.subdata(in: NSMakeRange(0, Int(shortLength) ))
let type = DescType(typeChar)
let descData = NSMutableData()
descData.append(&shortLength, length: 1)
descData.append(substringData)
return NSAppleEventDescriptor(descriptorType: type, data: descData as Data)
} else {
return nil
}
}
}
class SpotToProToolsOperation: Operation {
struct ProToolsSampleRange {
var start : Int32
var stop : Int32
var name : String
var length : Int32 {
get {
return stop - start
}
set(newVal) {
stop = start + newVal
}
}
func toAppleEventDescriptor() -> NSAppleEventDescriptor {
let retObj = NSAppleEventDescriptor.record()
retObj.setDescriptor(start.toAppleEventDescriptor()!,
forKeyword: UTGetOSTypeFromString("Star" as CFString))
retObj.setDescriptor(stop.toAppleEventDescriptor()!,
forKeyword: UTGetOSTypeFromString("Stop" as CFString))
retObj.setDescriptor(name.toTEXTAppleEventDescriptor()!,
forKeyword: UTGetOSTypeFromString("Name" as CFString))
return retObj
}
}
enum SpotTarget {
case selectedTrack
case regionList
case track(track : Int16)
var appleEventDescriptor : NSAppleEventDescriptor? {
switch self {
case .selectedTrack:
return Int16(-99).toAppleEventDescriptor()
case .regionList:
return Int16(0).toAppleEventDescriptor()
case .track(let track):
return Int16(track).toAppleEventDescriptor()
}
}
}
var urlToSpot : URL
var region : CMTimeRange
var regionName : String
var targetTrack : SpotTarget
var offset : CMTime
var targetRegion : ProToolsSampleRange?
var offsetFromInsertionPoint : Int32?
var errorProc : (Error) -> () = { (error : Error) in return }
class func sampleRateForURLToSpot(_ urlToSpot : URL) -> Int {
if let audioFile = try? AVAudioFile(forReading: urlToSpot) {
return Int(audioFile.fileFormat.sampleRate)
} else {
return 0
}
}
init( url : URL ,
region : CMTimeRange , regionName : String,
track : SpotTarget,
offset : CMTime ) {
urlToSpot = url
self.offset = offset
targetTrack = track
if regionName.count > 0 {
self.regionName = regionName
} else {
self.regionName = "-No Region Name-"
}
self.region = region
super.init()
}
func preflight() -> Bool {
let sampleRate = SpotToProToolsOperation.sampleRateForURLToSpot(urlToSpot)
if sampleRate != 0 {
let startTime = CMTimeConvertScale(region.start, timescale: Int32(sampleRate),
method: CMTimeRoundingMethod.roundTowardZero)
let durTime = CMTimeConvertScale(region.duration, timescale: Int32(sampleRate),
method: CMTimeRoundingMethod.roundAwayFromZero)
let stopTime = CMTimeAdd(startTime, durTime)
let offsetInSessionTime = CMTimeConvertScale(offset, timescale: Int32(sampleRate),
method: CMTimeRoundingMethod.roundTowardZero)
targetRegion = ProToolsSampleRange(
start: Int32(startTime.value) ,
stop: Int32(stopTime.value) ,
name: regionName
)
offsetFromInsertionPoint = Int32(offsetInSessionTime.value)
return true
} else {
return false
}
}
override func main() {
if preflight() == false {
let err = BagOfHoldingError.spotFailedUrlInvalid
// let err = NSError(domain: BagErrorDomain,
// code: BagErrors.spotFailedUrlInvalid.rawValue,
// userInfo: [
// NSLocalizedDescriptionKey : "Spotting Failed",
// NSLocalizedRecoverySuggestionErrorKey: "The audio file URL was invalid, the transfer may have failed."])
errorProc(err)
return
}
let proToolsBundleIdentifier = "com.avid.ProTools"
let proToolsApp : NSRunningApplication? = NSRunningApplication
.runningApplications(withBundleIdentifier: proToolsBundleIdentifier).last as NSRunningApplication?
guard let proToolsPID = proToolsApp?.processIdentifier else {
let error = BagOfHoldingError.spotFailedProToolsNotRunning
// let error = NSError(domain: BagErrorDomain,
// code: BagErrors.spotFailedProToolsNotRunning.rawValue,
// userInfo: [
// NSLocalizedDescriptionKey : "Could Not Spot to Pro Tools",
// NSLocalizedRecoverySuggestionErrorKey : "The transfer could not be spotted to Pro Tools because Pro Tools is not running.",
// ])
errorProc(error)
return
}
var pid = proToolsPID
let targetPidData = Data(bytes: &pid, count: MemoryLayout<pid_t>.size)
let targetApplicationDescriptor = NSAppleEventDescriptor(descriptorType:DescType(typeKernelProcessID) , data: targetPidData)
let spotEventClass = UTGetOSTypeFromString("Sd2a" as CFString)
let spotEventID = UTGetOSTypeFromString("SRgn" as CFString)
let spotAppleEventDescriptor = NSAppleEventDescriptor(eventClass: spotEventClass,
eventID: spotEventID,
targetDescriptor: targetApplicationDescriptor,
returnID: AEReturnID(kAutoGenerateReturnID),
transactionID: AETransactionID(kAnyTransactionID)
)
let targetRegionInFile = targetRegion!.toAppleEventDescriptor()
guard let fileDescriptor = urlToSpot.toAppleEventDescriptor(),
let targetTrackDescriptor = targetTrack.appleEventDescriptor,
let targetFFrm = Int16(1).toAppleEventDescriptor(),
let targetOffsetFromTrack = Int16(0).toAppleEventDescriptor(),
let targetLane = Int16(1).toAppleEventDescriptor(),
let targetOffsetFromInsertion = offsetFromInsertionPoint!.toAppleEventDescriptor() else {
let error = BagOfHoldingError.spotFailedInternalError
errorProc(error)
return
}
spotAppleEventDescriptor.setParam(fileDescriptor,
forKeyword: UTGetOSTypeFromString("FILE" as CFString))
spotAppleEventDescriptor.setParam(targetTrackDescriptor,
forKeyword: UTGetOSTypeFromString("Trak" as CFString))
spotAppleEventDescriptor.setParam(targetFFrm,
forKeyword: UTGetOSTypeFromString("FFrm" as CFString))
spotAppleEventDescriptor.setParam(targetOffsetFromTrack,
forKeyword: UTGetOSTypeFromString("TkOf" as CFString))
spotAppleEventDescriptor.setParam(targetOffsetFromInsertion,
forKeyword: UTGetOSTypeFromString("SMSt" as CFString))
spotAppleEventDescriptor.setParam(targetLane,
forKeyword: UTGetOSTypeFromString("Strm" as CFString))
spotAppleEventDescriptor.setParam(targetRegionInFile,
forKeyword: UTGetOSTypeFromString("Rgn " as CFString))
if #available(OSX 10.13, *) {
do {
let retVal = try spotAppleEventDescriptor.sendEvent(options: NSAppleEventDescriptor.SendOptions.waitForReply,
timeout: 60.0)
if retVal.descriptorType == UTGetOSTypeFromString("aevt" as CFString ),
let errorParam = retVal.paramDescriptor(forKeyword: UTGetOSTypeFromString("errn" as CFString)) {
throw NSError(domain: NSOSStatusErrorDomain,
code: Int(errorParam.int32Value),
userInfo: nil)
}
} catch let error {
errorProc(error)
}
} else {
let appleEventReply : UnsafeMutablePointer<AppleEvent>? = nil
let retval = AESendMessage(spotAppleEventDescriptor.aeDesc, appleEventReply, AESendMode(kAEWaitReply), kAEDefaultTimeout)
if retval != noErr {
// let osError = NSError(domain: NSOSStatusErrorDomain,
// code: Int(retval),
// userInfo: [NSLocalizedDescriptionKey : "AESendMessage Error"])
let error = BagOfHoldingError.spotFailedInternalError
// let error = NSError(domain: BagErrorDomain,
// code: BagErrors.spotFailedInternalError.rawValue,
// userInfo: [
// NSLocalizedDescriptionKey : "Spotting Error",
// NSLocalizedRecoverySuggestionErrorKey : "Sending an AppleEvent to Pro Tools failed. Make sure the Pro Tools is running and has a session open.",
// NSUnderlyingErrorKey : osError
// ])
errorProc(error)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment