Last active
July 29, 2021 17:32
-
-
Save iluvcapra/8d5a71c5271160bd974e7267fa85a2b3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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