diff --git a/OpenDialog/cocoa.m b/OpenDialog/cocoa.m index cfb2553..fd9af3c 100644 --- a/OpenDialog/cocoa.m +++ b/OpenDialog/cocoa.m @@ -11,7 +11,7 @@ char *do_open_rom_dialog(void) NSOpenPanel *dialog = [NSOpenPanel openPanel]; dialog.title = @"Open ROM"; dialog.allowedFileTypes = @[@"gb", @"gbc", @"sgb", @"isx"]; - [dialog runModal]; + if ([dialog runModal] != NSModalResponseOK) return nil; [key makeKeyAndOrderFront:nil]; NSString *ret = [[[dialog URLs] firstObject] path]; dup2(stderr_fd, STDERR_FILENO); @@ -32,7 +32,7 @@ char *do_open_folder_dialog(void) dialog.title = @"Select Boot ROMs Folder"; dialog.canChooseDirectories = true; dialog.canChooseFiles = false; - [dialog runModal]; + if ([dialog runModal] != NSModalResponseOK) return nil; [key makeKeyAndOrderFront:nil]; NSString *ret = [[[dialog URLs] firstObject] path]; dup2(stderr_fd, STDERR_FILENO); @@ -42,3 +42,25 @@ char *do_open_folder_dialog(void) return NULL; } } + +/* The Cocoa variant of this function isn't as fully featured as the GTK and Windows ones, as Mac users would use + the fully featured Cocoa port of SameBoy anyway*/ +char *do_save_recording_dialog(unsigned frequency) +{ + @autoreleasepool { + int stderr_fd = dup(STDERR_FILENO); + close(STDERR_FILENO); + NSWindow *key = [NSApp keyWindow]; + NSSavePanel *dialog = [NSSavePanel savePanel]; + dialog.title = @"Audio recording save location"; + dialog.allowedFileTypes = @[@"aiff", @"aif", @"aifc", @"wav", @"raw", @"pcm"]; + if ([dialog runModal] != NSModalResponseOK) return nil; + [key makeKeyAndOrderFront:nil]; + NSString *ret = [[dialog URL] path]; + dup2(stderr_fd, STDERR_FILENO); + if (ret) { + return strdup(ret.UTF8String); + } + return NULL; + } +} diff --git a/OpenDialog/gtk.c b/OpenDialog/gtk.c index 378dcb4..d0eb942 100644 --- a/OpenDialog/gtk.c +++ b/OpenDialog/gtk.c @@ -6,6 +6,7 @@ #include #define GTK_FILE_CHOOSER_ACTION_OPEN 0 +#define GTK_FILE_CHOOSER_ACTION_SAVE 1 #define GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 2 #define GTK_RESPONSE_ACCEPT -3 #define GTK_RESPONSE_CANCEL -6 @@ -28,6 +29,19 @@ void _gtk_file_filter_set_name(void *filter, const char *name); void _gtk_file_chooser_add_filter(void *dialog, void *filter); void _gtk_main_iteration(void); bool _gtk_events_pending(void); +unsigned long _g_signal_connect_data(void *instance, + const char *detailed_signal, + void *c_handler, + void *data, + void *destroy_data, + unsigned connect_flags); +void _gtk_file_chooser_set_current_name(void *dialog, + const char *name); +void *_gtk_file_chooser_get_filter(void *dialog); +const char *_gtk_file_filter_get_name (void *dialog); +#define g_signal_connect(instance, detailed_signal, c_handler, data) \ +g_signal_connect_data((instance), (detailed_signal), (c_handler), (data), NULL, 0) + #define LAZY(symbol) static typeof(_##symbol) *symbol = NULL;\ @@ -87,7 +101,7 @@ char *do_open_rom_dialog(void) gtk_file_filter_set_name(filter, "Game Boy ROMs"); gtk_file_chooser_add_filter(dialog, filter); - int res = gtk_dialog_run (dialog); + int res = gtk_dialog_run(dialog); char *ret = NULL; if (res == GTK_RESPONSE_ACCEPT) { @@ -155,7 +169,168 @@ char *do_open_folder_dialog(void) NULL ); - int res = gtk_dialog_run (dialog); + int res = gtk_dialog_run(dialog); + char *ret = NULL; + + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + filename = gtk_file_chooser_get_filename(dialog); + ret = strdup(filename); + g_free(filename); + } + + while (gtk_events_pending()) { + gtk_main_iteration(); + } + + gtk_widget_destroy(dialog); + + while (gtk_events_pending()) { + gtk_main_iteration(); + } + return ret; + +lazy_error: + fprintf(stderr, "Failed to display GTK dialog\n"); + return NULL; +} + +static void filter_changed(void *dialog, + void *unused, + void *unused2) +{ + static void *handle = NULL; + + TRY_DLOPEN("libgtk-3.so"); + TRY_DLOPEN("libgtk-3.so.0"); + TRY_DLOPEN("libgtk-2.so"); + TRY_DLOPEN("libgtk-2.so.0"); + + if (!handle) { + goto lazy_error; + } + + LAZY(gtk_file_chooser_get_filename); + LAZY(gtk_file_chooser_set_current_name); + LAZY(g_free); + LAZY(gtk_file_chooser_get_filter); + LAZY(gtk_file_filter_get_name); + + char *filename = gtk_file_chooser_get_filename(dialog); + if (!filename) return; + char *temp = filename + strlen(filename); + char *basename = filename; + bool deleted_extension = false; + while (temp != filename) { + temp--; + if (*temp == '.' && !deleted_extension) { + *temp = 0; + deleted_extension = true; + } + else if (*temp == '/') { + basename = temp + 1; + break; + } + } + + char *new_filename = NULL; + + switch (gtk_file_filter_get_name(gtk_file_chooser_get_filter(dialog))[1]) { + case 'p': + default: + asprintf(&new_filename, "%s.aiff", basename); + break; + case 'I': + asprintf(&new_filename, "%s.wav", basename); + break; + case 'a': + asprintf(&new_filename, "%s.raw", basename); + break; + } + + + gtk_file_chooser_set_current_name(dialog, new_filename); + free(new_filename); + g_free(filename); + return; + +lazy_error: + fprintf(stderr, "Failed updating the file extension\n"); +} + + +char *do_save_recording_dialog(unsigned frequency) +{ + static void *handle = NULL; + + TRY_DLOPEN("libgtk-3.so"); + TRY_DLOPEN("libgtk-3.so.0"); + TRY_DLOPEN("libgtk-2.so"); + TRY_DLOPEN("libgtk-2.so.0"); + + if (!handle) { + goto lazy_error; + } + + + LAZY(gtk_init_check); + LAZY(gtk_file_chooser_dialog_new); + LAZY(gtk_dialog_run); + LAZY(g_free); + LAZY(gtk_widget_destroy); + LAZY(gtk_file_chooser_get_filename); + LAZY(g_log_set_default_handler); + LAZY(gtk_file_filter_new); + LAZY(gtk_file_filter_add_pattern); + LAZY(gtk_file_filter_set_name); + LAZY(gtk_file_chooser_add_filter); + LAZY(gtk_events_pending); + LAZY(gtk_main_iteration); + LAZY(g_signal_connect_data); + LAZY(gtk_file_chooser_set_current_name); + + /* Shut up GTK */ + g_log_set_default_handler(nop, NULL); + + gtk_init_check(0, 0); + + + void *dialog = gtk_file_chooser_dialog_new("Audio recording save location", + 0, + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL ); + + + void *filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.aiff"); + gtk_file_filter_add_pattern(filter, "*.aif"); + gtk_file_filter_add_pattern(filter, "*.aifc"); + gtk_file_filter_set_name(filter, "Apple AIFF"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.wav"); + gtk_file_filter_set_name(filter, "RIFF WAVE"); + gtk_file_chooser_add_filter(dialog, filter); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.raw"); + gtk_file_filter_add_pattern(filter, "*.pcm"); + static char raw_name[40]; +#ifdef GB_BIG_ENDIAN + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit BE)", frequency); +#else + sprintf(raw_name, "Raw PCM (Stereo %dHz, 16-bit LE)", frequency); +#endif + gtk_file_filter_set_name(filter, raw_name); + gtk_file_chooser_add_filter(dialog, filter); + + g_signal_connect(dialog, "notify::filter", filter_changed, NULL); + gtk_file_chooser_set_current_name(dialog, "Untitled.aiff"); + + int res = gtk_dialog_run(dialog); char *ret = NULL; if (res == GTK_RESPONSE_ACCEPT) { diff --git a/OpenDialog/open_dialog.h b/OpenDialog/open_dialog.h index 6d7fb5b..3548410 100644 --- a/OpenDialog/open_dialog.h +++ b/OpenDialog/open_dialog.h @@ -3,4 +3,5 @@ char *do_open_rom_dialog(void); char *do_open_folder_dialog(void); +char *do_save_recording_dialog(unsigned frequency); #endif /* open_rom_h */ diff --git a/OpenDialog/windows.c b/OpenDialog/windows.c index edc654a..13dea8e 100644 --- a/OpenDialog/windows.c +++ b/OpenDialog/windows.c @@ -32,7 +32,7 @@ char *do_open_rom_dialog(void) dialog.lpstrInitialDir = NULL; dialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; - if (GetOpenFileNameW(&dialog) == TRUE) { + if (GetOpenFileNameW(&dialog)) { return wc_to_utf8_alloc(filename); } @@ -61,3 +61,45 @@ char *do_open_folder_dialog(void) if (SUCCEEDED(hrOleInit)) OleUninitialize(); return ret; } + +char *do_save_recording_dialog(unsigned frequency) +{ + OPENFILENAMEW dialog; + wchar_t filename[MAX_PATH + 5]; + + filename[0] = '\0'; + memset(&dialog, 0, sizeof(dialog)); + dialog.lStructSize = sizeof(dialog); + dialog.lpstrFile = filename; + dialog.nMaxFile = MAX_PATH; + if (frequency == 48000) { + dialog.lpstrFilter = L"RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo 48000Hz, 16-bit LE)\0*.raw;*.pcm;\0All files\0*.*\0\0"; + } + else { + dialog.lpstrFilter = L"RIFF WAVE\0*.wav\0Apple AIFF\0*.aiff;*.aif;*.aifc\0Raw PCM (Stereo 44100Hz, 16-bit LE)\0*.raw;*.pcm;\0All files\0*.*\0\0"; + } + dialog.nFilterIndex = 1; + dialog.lpstrFileTitle = NULL; + dialog.nMaxFileTitle = 0; + dialog.lpstrInitialDir = NULL; + dialog.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; + + if (GetSaveFileNameW(&dialog)) { + if (dialog.nFileExtension == 0) { + switch (dialog.nFilterIndex) { + case 1: + wcscat(filename, L".wav"); + break; + case 2: + wcscat(filename, L".aiff"); + break; + case 3: + wcscat(filename, L".raw"); + break; + } + } + return wc_to_utf8_alloc(filename); + } + + return NULL; +} diff --git a/SDL/gui.c b/SDL/gui.c index 1dd6207..dbb7e9e 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -316,6 +316,7 @@ static void enter_graphics_menu(unsigned index); static void enter_controls_menu(unsigned index); static void enter_joypad_menu(unsigned index); static void enter_audio_menu(unsigned index); +static void toggle_audio_recording(unsigned index); extern void set_filename(const char *new_filename, typeof(free) *new_free_function); static void open_rom(unsigned index) @@ -344,14 +345,17 @@ static void recalculate_menu_height(void) } } +char audio_recording_menu_item[] = "Start Audio Recording"; + static const struct menu_item paused_menu[] = { {"Resume", NULL}, {"Open ROM", open_rom}, {"Emulation Options", enter_emulation_menu}, {"Graphic Options", enter_graphics_menu}, {"Audio Options", enter_audio_menu}, - {"Keyboard", enter_controls_menu}, - {"Joypad", enter_joypad_menu}, + {"Keyboard Options", enter_controls_menu}, + {"Joypad Options", enter_joypad_menu}, + {audio_recording_menu_item, toggle_audio_recording}, {"Help", item_help}, {"Quit SameBoy", item_exit}, {NULL,} @@ -1151,6 +1155,70 @@ void connect_joypad(void) } } +static void toggle_audio_recording(unsigned index) +{ + if (!GB_is_inited(&gb)) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Cannot start audio recording, open a ROM file first.", window); + return; + } + static bool is_recording = false; + if (is_recording) { + is_recording = false; + show_osd_text("Audio recording ended"); + int error = GB_stop_audio_recording(&gb); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + } + static const char item_string[] = "Start Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + return; + } + char *filename = do_save_recording_dialog(GB_get_sample_rate(&gb)); + + /* Drop events as it SDL seems to catch several in-dialog events */ + SDL_Event event; + while (SDL_PollEvent(&event)); + + if (filename) { + GB_audio_format_t format = GB_AUDIO_FORMAT_RAW; + size_t length = strlen(filename); + if (length >= 5) { + if (strcasecmp(".aiff", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".aifc", filename + length - 5) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (length >= 4) { + if (strcasecmp(".aif", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_AIFF; + } + else if (strcasecmp(".wav", filename + length - 4) == 0) { + format = GB_AUDIO_FORMAT_WAV; + } + } + } + + int error = GB_start_audio_recording(&gb, filename, format); + free(filename); + if (error) { + char *message = NULL; + asprintf(&message, "Could not finalize recording: %s", strerror(error)); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message, window); + free(message); + return; + } + + is_recording = true; + static const char item_string[] = "Stop Audio Recording"; + memcpy(audio_recording_menu_item, item_string, sizeof(item_string)); + show_osd_text("Audio recording started"); + } +} + void run_gui(bool is_running) { SDL_ShowCursor(SDL_ENABLE); diff --git a/SDL/main.c b/SDL/main.c index a3be18f..d73d667 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -757,6 +757,11 @@ static void save_configuration(void) } } +static void stop_recording(void) +{ + GB_stop_audio_recording(&gb); +} + static bool get_arg_flag(const char *flag, int *argc, char **argv) { for (unsigned i = 1; i < *argc; i++) { @@ -847,6 +852,7 @@ int main(int argc, char **argv) } atexit(save_configuration); + atexit(stop_recording); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); diff --git a/Windows/stdio.h b/Windows/stdio.h index 1e6ec02..c369ac6 100755 --- a/Windows/stdio.h +++ b/Windows/stdio.h @@ -1,6 +1,7 @@ #pragma once #include_next #include +#include int access(const char *filename, int mode); #define R_OK 2 @@ -20,6 +21,16 @@ static inline int vasprintf(char **str, const char *fmt, va_list args) } return ret; } + +static inline int asprintf(char **strp, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int r = vasprintf(strp, fmt, args); + va_end(args); + return r; +} + #endif #endif diff --git a/Windows/string.h b/Windows/string.h index b899ca9..f1cf6b1 100755 --- a/Windows/string.h +++ b/Windows/string.h @@ -1,3 +1,4 @@ #pragma once #include_next -#define strdup _strdup \ No newline at end of file +#define strdup _strdup +#define strcasecmp _stricmp