Skip to content

Instantly share code, notes, and snippets.

@iluvcapra
Last active November 30, 2021 08:15
Show Gist options
  • Save iluvcapra/5208960 to your computer and use it in GitHub Desktop.
Save iluvcapra/5208960 to your computer and use it in GitHub Desktop.
Pro Tools Import Apple Event via AEDebugSends=1
{ 1 } 'aevt': Sd2a/SRgn (i386){
return id: 315 (0x13b)
transaction id: 0 (0x0)
interaction level: 32 (0x20)
reply required: 1 (0x1)
remote: 0 (0x0)
for recording: 0 (0x0)
reply port: 0 (0x0)
target:
{ 1 } 'sign': 4 bytes {
'PTul' (Pro Tools)
}
fEventSourcePSN: { 0x0,0x2a02a } (Soundminer v4Pro)
optional attributes:
/* I don't know what these are, I assume they aren't a part of the event soundminer composes, and are attached by the
operating system post hoc */
{ 1 } 'reco': - 1 items {
key 'shas' -
{ 1 } 'list': - 1 elements {
{ 1 } 'TEXT': 224 bytes {
"59d055578f770792f6f0097bfda6985211c5d6a5;00000000;0000000000000020;com.apple.app-sandbox.read-write;00000001;0e00000b;000000000032ec78;/users/mpe_wr/desktop/untitled/audio files/punch, face; light face punch_af08_76_04.m.wav"
}
}
}
event data:
{ 1 } 'aevt': - 7 items {
key 'FILE' -
{ 1 } 'alis': 564 bytes {
/* An alias to the file soundminer created and transferred to Pro Tools's "Audio Files" folder
Alias's aren't paths, they're Carbon objects that you create with FSNewAlias* and defined in <CoreServices/CoreServices.h>.
These are deprecated in 10.8 so it's unclear how this will work in the future, if they're ever dropped.
*/
/Users/MPE_WR/Desktop/untitled/Audio Files/Punch, Face; Light Face Punch_AF08_76_04.M.wav
}
key 'Trak' -
{ 1 } 'shor': 2 bytes {
-99 (0xffffff9d) // always -99
}
key 'FFrm' -
{ 1 } 'shor': 2 bytes {
1 (0x1) // always 1
}
key 'TkOf' -
{ 1 } 'shor': 2 bytes {
0 (0x0) // as this increments, the insertion point steps down tracks
}
key 'SMSt' -
{ 1 } 'long': 4 bytes {
0 (0x0) // samples offset after the insertion point
}
key 'Strm' -
{ 1 } 'shor': 2 bytes {
1 (0x1) // always 1
}
key 'Rgn ' -
{ 1 } 'reco': - 3 items {
key 'Star' -
{ 1 } 'long': 4 bytes {
0 (0x0) // region start from file
}
key 'Stop' -
{ 1 } 'long': 4 bytes {
5120 (0x1400) // region end from file
}
key 'Name' -
{ 1 } 'TEXT': 256 bytes { // always 256 bytes, the first byte is a length field a-la a Pascal string,
// the rest appears to be garbage
"*Punch, Face; Light Face Punch_AF08_76_04.M`??x???8???L
???Kg
/* This brings Pro Tools to the front */
{ 1 } 'aevt': misc/actv (i386){
return id: 316 (0x13c)
transaction id: 0 (0x0)
interaction level: 112 (0x70)
reply required: 0 (0x0)
remote: 0 (0x0)
for recording: 0 (0x0)
reply port: 0 (0x0)
target:
{ 2 } 'sign': 4 bytes {
'PTul' (Pro Tools)
}
fEventSourcePSN: { 0x0,0x2a02a } (Soundminer v4Pro)
optional attributes:
< empty record >
event data:
{ 1 } 'aevt': - 0 items {
}
}
#import <Foundation/Foundation.h>
#import <ApplicationServices/ApplicationServices.h>
#define kSpotToSelectedTrack (-99)
#define kSpotToRegionList (0)
/* JHSpotFileToProToolsInsertionPoint()
* url : URL of the file to spot
* spottedRegionName : name to use in session, will be flattened to ASCII and
* truncated to the first 255 characters
* sampleRange : region of `url` to spot into session
*
* spotTarget : Which track to spot the region on:
* - `kSpotToSelectedTrack` spots to the highest track
* that is selected
* - `kSpotToRegionList` spots to the region list
* - Any int > 0 spots to that track, counting from the
* top of the session (the top track is track 1).
*
* trackOffset : negative vertical offset for track, when
* spotTarget == kSpotToSelectedTrack
* "0" will spot on track with cursor, "1" will spot
* one track below the cursor, etc.
*
* cursorPostOffset : when spotTarget == kSpotToSelectedTrack, samples after
* insertion point to place region
* when spotTarget > 0, samples after session start to
* place region
*
* proToolsPID : kernel pid of Pro Tools
*
* replyDesc : Return descriptor from Apple Event to Pro Tools
*/
OSStatus JHSpotFileToProToolsInsertionPoint(NSURL *url,
NSString *spottedRegionName,
NSRange sampleRange,
NSInteger spotTarget,
NSUInteger trackOffset,
NSUInteger cursorPostOffset,
pid_t proToolsPID,
NSAppleEventDescriptor **replyDesc);
/* implementation */
/
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
NSAppleEventDescriptor *JHCreateAliasDescriptorForURL(NSURL *aURL) {
NSAppleEventDescriptor *retObj = nil;
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 1080
FSRef fsReference;
if (CFURLGetFSRef((__bridge CFURLRef)aURL, &fsReference)) {
AliasHandle aliasHandle = NULL;
OSStatus err = FSNewAliasMinimal(&fsReference, &aliasHandle);
if (err == noErr && aliasHandle != NULL) {
HLock((Handle)aliasHandle);
retObj = [NSAppleEventDescriptor descriptorWithDescriptorType:typeAlias
data:[NSData dataWithBytes:*aliasHandle
length:GetHandleSize((Handle)aliasHandle)]];
HUnlock((Handle)aliasHandle);
DisposeHandle((Handle)aliasHandle);
}
}
#else
NSData *urlData = [[aURL absoluteString] dataUsingEncoding:NSUTF8StringEncoding];
retObj = [NSAppleEventDescriptor descriptorWithDescriptorType:typeFileURL
data:urlData];
retObj = [retObj coerceToDescriptorType:typeAlias];
#endif
return retObj;
}
#endif
NSAppleEventDescriptor *JHCreateTEXTDescriptorForString(NSString *str) {
const NSUInteger textMaxLength = 255;
const NSUInteger bufferLength = textMaxLength + 1;
// this is for safety's sake, but it's worth an experiment to see if it'd work another way
NSData *stringData = [str dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
stringData = [stringData subdataWithRange:NSMakeRange(0, MIN(textMaxLength, [stringData length]) )];
char length = (char)[stringData length];
char *buf = calloc(bufferLength,sizeof(char));
buf[0] = length;
[stringData getBytes:buf+1 length:length];
NSAppleEventDescriptor *retObj = [NSAppleEventDescriptor descriptorWithDescriptorType:typeChar
bytes:buf
length:bufferLength];
return retObj;
}
NSAppleEventDescriptor *JHCreateDescriptorForLong(long int value) {
return [NSAppleEventDescriptor descriptorWithDescriptorType:typeSInt32
bytes:&value
length:sizeof(value)];
}
NSAppleEventDescriptor *JHCreateDescriptorForShort(short int value) {
return [NSAppleEventDescriptor descriptorWithDescriptorType:typeSInt16
bytes:&value
length:sizeof(value)];
}
NSAppleEventDescriptor *JHCreateDescriptorForRegion(NSString *regionName, NSRange sampleRange) {
NSAppleEventDescriptor *retObj = [NSAppleEventDescriptor recordDescriptor];
[retObj setDescriptor:JHCreateDescriptorForLong(sampleRange.location)
forKeyword:'Star'];
[retObj setDescriptor:JHCreateDescriptorForLong(sampleRange.location + sampleRange.length)
forKeyword:'Stop'];
[retObj setDescriptor:JHCreateTEXTDescriptorForString(regionName)
forKeyword:'Name'];
return retObj;
}
OSStatus JHSpotFileToProToolsInsertionPoint(
NSURL *url,
NSString *spottedRegionName,
NSRange sampleRange,
NSInteger spotTarget,
NSUInteger trackOffset,
NSUInteger cursorPostOffset,
pid_t proToolsPID,
NSAppleEventDescriptor **replyDesc) {
NSAppleEventDescriptor *address = [[NSAppleEventDescriptor alloc]
initWithDescriptorType:typeKernelProcessID
bytes: &proToolsPID length:sizeof(proToolsPID)];
NSAppleEventDescriptor *message = [NSAppleEventDescriptor
appleEventWithEventClass: 'Sd2a'
eventID: 'SRgn'
targetDescriptor: address
returnID: kAutoGenerateReturnID
transactionID: kAnyTransactionID
];
[message setParamDescriptor:JHCreateAliasDescriptorForURL(url)
forKeyword:'FILE'];
// setting this to 0 will put the region in the clip list, 1-256 will insert
// it on a track counting from the top
[message setParamDescriptor:JHCreateDescriptorForShort(spotTarget & 0xff)
forKeyword:'Trak'];
// dunno
[message setParamDescriptor:JHCreateDescriptorForShort(1)
forKeyword:'FFrm'];
[message setParamDescriptor:JHCreateDescriptorForShort(trackOffset & 0xffff)
forKeyword:'TkOf'];
[message setParamDescriptor:JHCreateDescriptorForLong(cursorPostOffset & 0xffffffff)
forKeyword:'SMSt'];
// this selects the lane a mono file region will be dropped in
[message setParamDescriptor:JHCreateDescriptorForShort(1)
forKeyword:'Strm'];
[message setParamDescriptor:JHCreateDescriptorForRegion(spottedRegionName, sampleRange)
forKeyword:'Rgn '];
AppleEvent reply;
OSStatus retVal = AESendMessage([message aeDesc], &reply, kAEWaitReply, kAEDefaultTimeout);
if (retVal == noErr) {
*replyDesc = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&reply];
} else {
*replyDesc = nil;
}
return retVal;
}
@sebastianmorsch
Copy link

This is really cool!

Though I had to remove the '& 0xff' mask in line 163 to make spotting-to-cursor work... Obviously, because masking with 0xff would kill a negative number like -99 right?

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment