Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to add direct MIDI-opening support for OSX #284

Merged
merged 23 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions emulator/resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,30 @@
<key>DISPLAY</key>
<string>:0</string>
</dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>midi</string>
<string>mid</string>
<string>kar</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>SwadgeEmulator.icns</string>
<key>CFBundleTypeNames</key>
<string>MIDI File</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>Midi</string>
</array>
<key>LSItemContentTypes</key>
<array>
<string>public.midi-audio</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
</dict>
</plist>
315 changes: 305 additions & 10 deletions emulator/src/extensions/midi/ext_midi.c
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
#include "ext_midi.h"
#include "emu_ext.h"
#include "emu_main.h"
#include "emu_utils.h"

#include "hdw-nvs_emu.h"
#include "emu_cnfs.h"
#include "ext_modes.h"
#include "mode_synth.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef EMU_MACOS
// Used to handle DocumentOpen event that OSX uses instead of Just Putting It In Argv
#include <Carbon/Carbon.h>
#endif

//==============================================================================
// Types
//==============================================================================

#ifdef EMU_MACOS
typedef void (*MacOpenFileCb)(const char* path);

typedef struct
{
EventHandlerUPP globalEventHandler;
AEEventHandlerUPP appleEventHandler;
EventHandlerRef globalEventHandlerRef;
MacOpenFileCb openFileCallback;
} MacOpenFileHandler;
#endif

//==============================================================================
// Function Prototypes
//==============================================================================

static bool midiInitCb(emuArgs_t* emuArgs);
static void midiPreFrameCb(uint64_t frame);
static bool midiInjectFile(const char* path);

#ifdef EMU_MACOS
// Exists but isn't declared in the headers
extern Boolean ConvertEventRefToEventRecord(EventRef, EventRecord*);

bool installMacOpenFileHandler(MacOpenFileHandler* handlerRef, MacOpenFileCb callback);
void checkForEventsMacOpenFileHandler(MacOpenFileHandler* handlerRef, uint32_t millis);
void uninstallMacOpenFileHandler(MacOpenFileHandler* handlerRef);

static pascal OSErr handleOpenDocumentEvent(const AppleEvent* event, AppleEvent* reply, SRefCon handlerRef);
static OSStatus globalEventHandler(EventHandlerCallRef handler, EventRef event, void* data);
static void doFileOpenCb(const char* path);
#endif

//==============================================================================
// Variables
Expand All @@ -22,14 +61,25 @@ static bool midiInitCb(emuArgs_t* emuArgs);
emuExtension_t midiEmuExtension = {
.name = "midi",
.fnInitCb = midiInitCb,
.fnPreFrameCb = NULL,
.fnPreFrameCb = midiPreFrameCb,
.fnPostFrameCb = NULL,
.fnKeyCb = NULL,
.fnMouseMoveCb = NULL,
.fnMouseButtonCb = NULL,
.fnRenderCb = NULL,
};

static char midiPathBuffer[1024];
static const char* midiFile = NULL;

#ifdef EMU_MACOS
static const EventTypeSpec eventTypes[] = {{.eventClass = kEventClassAppleEvent, .eventKind = kEventAppleEvent}};

static bool handlerInstalled = false;
static bool emulatorStarted = false;
static MacOpenFileHandler macOpenFileHandler;
#endif

//==============================================================================
// Functions
//==============================================================================
Expand All @@ -38,14 +88,20 @@ static bool midiInitCb(emuArgs_t* emuArgs)
{
if (emuArgs->midiFile)
{
printf("Opening MIDI file: %s\n", emuArgs->midiFile);
if (emuCnfsInjectFile(emuArgs->midiFile, emuArgs->midiFile))
{
emuInjectNvs32("storage", "synth_playmode", 1);
emuInjectNvsBlob("storage", "synth_lastsong", strlen(emuArgs->midiFile), emuArgs->midiFile);
emulatorSetSwadgeModeByName(synthMode.modeName);
}
else
midiFile = emuArgs->midiFile;
}

#ifdef EMU_MACOS
handlerInstalled = installMacOpenFileHandler(&macOpenFileHandler, doFileOpenCb);
// Wait up to 100ms for an event at startup
checkForEventsMacOpenFileHandler(&macOpenFileHandler, 100);
emulatorStarted = true;
#endif

if (midiFile)
{
printf("Opening MIDI file: %s\n", midiFile);
if (!midiInjectFile(midiFile))
{
printf("Could not read MIDI file!\n");
emulatorQuit();
Expand All @@ -56,4 +112,243 @@ static bool midiInitCb(emuArgs_t* emuArgs)
}

return false;
}
}

void midiPreFrameCb(uint64_t frame)
{
#ifdef EMU_MACOS
if (handlerInstalled)
{
// Wait up to 5ms for an event, is that enough?
checkForEventsMacOpenFileHandler(&macOpenFileHandler, 5);
}
#endif
}

static bool midiInjectFile(const char* path)
{
if (emuCnfsInjectFile(midiFile, midiFile))
{
emuInjectNvs32("storage", "synth_playmode", 1);
emuInjectNvsBlob("storage", "synth_lastsong", strlen(midiFile), midiFile);
emulatorSetSwadgeModeByName(synthMode.modeName);

return true;
}
else
{
return false;
}
}

#ifdef EMU_MACOS
static void doFileOpenCb(const char* path)
{
strncpy(midiPathBuffer, path, sizeof(midiPathBuffer));
midiFile = midiPathBuffer;

if (emulatorStarted)
{
if (!midiInjectFile(path))
{
printf("Error: could not read MIDI file %s!\n", path);
}
}
}

bool installMacOpenFileHandler(MacOpenFileHandler* handlerRef, MacOpenFileCb callback)
{
// Init handler
handlerRef->appleEventHandler = NULL;
handlerRef->globalEventHandler = NULL;
handlerRef->openFileCallback = callback;

// Install handler
handlerRef->appleEventHandler = NewAEEventHandlerUPP(handleOpenDocumentEvent);
OSStatus result = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, handlerRef->appleEventHandler,
(SRefCon)handlerRef, false);

if (result != noErr)
{
printf("Failed to install OpenDocument handler\n");
uninstallMacOpenFileHandler(handlerRef);
return false;
}

// Install the application-level handler
handlerRef->globalEventHandler = NewEventHandlerUPP(globalEventHandler);
result = InstallApplicationEventHandler(handlerRef->globalEventHandler, 1, eventTypes, NULL,
&handlerRef->globalEventHandlerRef);

if (result != noErr)
{
printf("Failed to install global event handler\n");
uninstallMacOpenFileHandler(handlerRef);
return false;
}

// Handler successfully installed
return true;
}

// Runs the event loop and waits for up to the specified time
// The callback will be called for any events that are recevied
void checkForEventsMacOpenFileHandler(MacOpenFileHandler* handlerRef, uint32_t millis)
{
EventTimeout timeout = millis / 1000.0;
while (1)
{
EventRef eventRef;

OSErr result = ReceiveNextEvent(1, eventTypes, timeout, kEventRemoveFromQueue, &eventRef);

if (result == eventLoopTimedOutErr)
{
// printf("No event received after timeout\n");
break;
}
else if (result == noErr)
{
result = SendEventToEventTarget(eventRef, GetEventDispatcherTarget());
ReleaseEvent(eventRef);
if (result != noErr)
{
if (result == eventNotHandledErr)
{
// printf("Got eventNotHandledErr from SendEventToEventTarget()\n");
}
else
{
printf("Error in SendEventToEventTarget(): %d %s\n", result, strerror(result));
break;
}
}
}
else
{
printf("Error in ReceiveNextEvent()\n");
break;
}
}
}

// Uninstalls and deletes
void uninstallMacOpenFileHandler(MacOpenFileHandler* handlerRef)
{
if (handlerRef != NULL)
{
if (handlerRef->appleEventHandler != NULL)
{
DisposeAEEventHandlerUPP(handlerRef->appleEventHandler);
handlerRef->appleEventHandler = NULL;
}

if (handlerRef->globalEventHandler != NULL)
{
DisposeEventHandlerUPP(handlerRef->globalEventHandler);
handlerRef->globalEventHandler = NULL;
}
handlerRef->openFileCallback = NULL;
}
}

static pascal OSErr handleOpenDocumentEvent(const AppleEvent* event, AppleEvent* reply, SRefCon handlerRefArg)
{
MacOpenFileHandler* handlerRef = (MacOpenFileHandler*)handlerRefArg;

AEDescList docList;
OSErr result = AEGetParamDesc(event, keyDirectObject, typeAEList, &docList);

if (result != noErr)
{
return result;
}

long docCount = 0;
result = AECountItems(&docList, &docCount);
if (result != noErr)
{
return result;
}

char buffer[2048];

// Yup, it's zero-indexed. Weird.
for (long i = 1; i <= docCount; i++)
{
AEKeyword keyword;
DescType docType;
Size docSize;

result = AEGetNthPtr(&docList, i, typeFileURL, &keyword, &docType, &buffer, sizeof(buffer), &docSize);

if (result != noErr)
{
return result;
}

CFURLRef docUrlRef = CFURLCreateWithBytes(NULL, (UInt8*)buffer, docSize, kCFStringEncodingUTF8, NULL);

if (docUrlRef != NULL)
{
CFStringRef docStringRef = CFURLCopyFileSystemPath(docUrlRef, kCFURLPOSIXPathStyle);
if (docStringRef != NULL)
{
char pathBuffer[1024];
if (CFStringGetFileSystemRepresentation(docStringRef, pathBuffer, sizeof(pathBuffer)))
{
handlerRef->openFileCallback(pathBuffer);
}
CFRelease(docStringRef);
}
CFRelease(docUrlRef);
}
}

return AEDisposeDesc(&docList);
}

static OSStatus globalEventHandler(EventHandlerCallRef handler, EventRef event, void* data)
{
bool inQueue = IsEventInQueue(GetMainEventQueue(), event);

if (inQueue)
{
RetainEvent(event);
RemoveEventFromQueue(GetMainEventQueue(), event);
}

EventRecord record;
ConvertEventRefToEventRecord(event, &record);
char messageStr[5] = {
(char)((record.message >> 24) & 0xff),
(char)((record.message >> 16) & 0xff),
(char)((record.message >> 8) & 0xff),
(char)((record.message) & 0xff),
0,
};
printf("globalEventHandler() what=%hu, message=%s\n", record.what, messageStr);
OSStatus result = AEProcessAppleEvent(&record);

if (result == errAEEventNotHandled)
{
printf("errAEEventNotHandled in globalEventHandler()\n");
}
else if (result != noErr)
{
printf("globalEventHandler() AEProcessAppleEvent() returned ERROR: %d (%s)\n", result, strerror(result));
}
else
{
printf("globalEventHandler() AEProcessAppleEvent() success!\n");
}

if (inQueue)
{
ReleaseEvent(event);
}

return noErr;
}

#endif
1 change: 1 addition & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ LIBRARY_FLAGS += \
-static-libstdc++
else
LIBRARY_FLAGS += \
-framework Carbon \
-framework Foundation \
-framework CoreFoundation \
-framework CoreMIDI \
Expand Down
Loading
Loading