diff --git a/desmume/ChangeLog b/desmume/ChangeLog
index f4d9072ee..d1b78d9e8 100644
--- a/desmume/ChangeLog
+++ b/desmume/ChangeLog
@@ -9,6 +9,7 @@
- Added option to disable execution upon loading. [Jeff B]
- Many more strings are translatable now. [Jeff B]
- Default screen color now black (better represents being "off" and easier on eyes at night) [Jeff B]
+ - Added sound [Jeff B]
general:
- Fixed possible segfault in ROMReader on ia64 and amd64. [evilynux]
- Fixed a crash bug with 2D background corrupting memory [shash]
diff --git a/desmume/src/cocoa/DeSmuME.cbp b/desmume/src/cocoa/DeSmuME.cbp
index 627eb7ac0..1a712d2d7 100644
--- a/desmume/src/cocoa/DeSmuME.cbp
+++ b/desmume/src/cocoa/DeSmuME.cbp
@@ -3,29 +3,75 @@
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -104,6 +150,11 @@
+
+
+
+
+
diff --git a/desmume/src/cocoa/cocoa_util.m b/desmume/src/cocoa/cocoa_util.m
index 28c59e595..8d1eb5760 100644
--- a/desmume/src/cocoa/cocoa_util.m
+++ b/desmume/src/cocoa/cocoa_util.m
@@ -19,6 +19,11 @@
#import "globals.h"
+void setTitle(int i)
+{
+ [main_window setTitle:[NSString stringWithFormat:@"%d", i]];
+}
+
////////////////////////////////////////////////////////////////
//Menu Item implementation--------------------------------------
////////////////////////////////////////////////////////////////
@@ -73,7 +78,7 @@ NSString* openDialog(NSArray *file_types)
[panel setCanChooseFiles:YES];
[panel setAllowsMultipleSelection:NO];
- if([panel runModalForDirectory:@"/Users/gecko/nds" file:nil types:file_types] == NSOKButton)
+ if([panel runModalForDirectory:nil file:nil types:file_types] == NSOKButton)
{
//a file was selected
diff --git a/desmume/src/cocoa/globals.h b/desmume/src/cocoa/globals.h
index 831098f61..a295fe4e7 100644
--- a/desmume/src/cocoa/globals.h
+++ b/desmume/src/cocoa/globals.h
@@ -114,6 +114,10 @@ extern NSMenuItem *reset_item;
extern NSMenuItem *save_state_as_item;
extern NSMenuItem *load_state_from_item;
+//sound (defined/managed in nds_control.m)
+extern NSMenuItem *volume_item[10];
+extern NSMenuItem *mute_item;
+
#define SAVE_SLOTS 10 //this should never be more than NB_SAVES in saves.h
extern NSMenuItem *saveSlot_item[SAVE_SLOTS];
extern NSMenuItem *loadSlot_item[SAVE_SLOTS];
@@ -143,6 +147,9 @@ extern NintendoDS *NDS;
extern VideoOutputWindow *main_window;
void setAppDefaults(); //this is defined in preferences.m and should be called at app launch
+
+void clearEvents(bool wait);
+
//--------------------------------------------------------------------------------------------------
extern volatile int /*desmume_BOOL*/ execute;
diff --git a/desmume/src/cocoa/main.m b/desmume/src/cocoa/main.m
index dba51f14e..e3fea2ce6 100644
--- a/desmume/src/cocoa/main.m
+++ b/desmume/src/cocoa/main.m
@@ -30,7 +30,6 @@ Based on work by yopyop and the DeSmuME team!
//DeSmuME general includes
#define OBJ_C
-#include "../SPU.h"
#include "../render3D.h"
#include "../GPU.h"
#include "../Windows/OGLRender.h"
@@ -44,7 +43,8 @@ FIXME: Hardware acceleration for openglrender.c ??
FIXME: When cross-platform (core) components end emulation due to error - pause should be called (set the menu checkmark)
FIXME: Some bug where states get messed up and hitting execute does nothing......
FIXME: .nds.gba extensions don't work in open panels
-FIXME: Traveling windows when constantly resizing with hotkey
+FIXME: Traveling windows when constantly resizing
+FIXME: Show version number somewhere in the program
*/
//Globals----------------------------------------------------------------------------------------
@@ -94,13 +94,6 @@ volatile desmume_BOOL execute = FALSE;
volatile BOOL finished = FALSE;
volatile BOOL paused = TRUE;
-SoundInterface_struct *SNDCoreList[] = {
-&SNDDummy,
-//&SNDFile,
-//&SNDDIRECTX,
-NULL
-};
-
GPU3DInterface *core3DList[] = {
&gpu3DNull,
&gpu3Dgl
@@ -114,7 +107,7 @@ NintendoDS *NDS;
void Quit()
{
//stop emulation if it's going
- [NDS pause];
+ [NDS destroy];
//
finished = true;
@@ -142,7 +135,7 @@ NSEvent *current_event;
//if wait is true then it will sit around waiting for an
//event for a short period of time taking up very little CPU
//(use when game is not running)
-inline void clearEvents(bool wait)
+void clearEvents(bool wait)
{
//wait for the next event
while((current_event = [NSApp nextEventMatchingMask:0 untilDate:wait?[NSDate dateWithTimeIntervalSinceNow:.2]:nil inMode:NSDefaultRunLoopMode dequeue:YES]))
@@ -162,6 +155,7 @@ void CreateMenu()
NSMenu* file;
NSMenu* emulation;
NSMenu* view;
+ NSMenu* sound_menu;
//NSMenu* window;
NSMenu* help;
@@ -229,6 +223,7 @@ void CreateMenu()
NSMenuItem *file_item = [menu addItemWithTitle:@"File" action:action keyEquivalent:@""];
NSMenuItem *emulation_item = [menu addItemWithTitle:@"Emulation" action:action keyEquivalent:@""];
NSMenuItem *view_item = [menu addItemWithTitle:@"View" action:action keyEquivalent:@""];
+ NSMenuItem *sound_item = [menu addItemWithTitle:@"Sound" action:action keyEquivalent:@""];
//NSMenuItem *window_item = [menu addItemWithTitle:@"Window" action:action keyEquivalent:@""];
NSMenuItem *help_item = [menu addItemWithTitle:@"Help" action:action keyEquivalent:@""];
@@ -508,7 +503,40 @@ a way to get the time of a save that's not a string / human formatted...
allows_resize_item = [view addItemWithTitle:@"Screenshot to Window" action:@selector(screenShotToWindow) keyEquivalent:@""];
[allows_resize_item setTarget:main_window];
+ //Create the sound menu
+ sound_menu = [[NSMenu alloc] initWithTitle:localizedString(@"Sound", nil)];
+ [menu setSubmenu:sound_menu forItem:sound_item];
+ temp = [sound_menu addItemWithTitle:@"Volume" action:nil keyEquivalent:@""];
+ [temp setTarget:NSApp];
+
+ NSMenu *volume_menu = [[NSMenu alloc] initWithTitle:localizedString(@"Volume", nil)];
+ [sound_menu setSubmenu:volume_menu forItem:temp];
+
+ for(i = 0; i < 10; i++)
+ {
+ volume_item[i] = [volume_menu addItemWithTitle:@"Volume %d" withInt:(i+1)*10 action:@selector(setVolume:) keyEquivalent:@""];
+ [volume_item[i] setTarget:NDS];
+ if(i == 9)
+ [volume_item[i] setState:NSOnState]; //check 100% volume since it's defaults
+ }
+
+ [sound_menu addItem:[NSMenuItem separatorItem]];
+
+ mute_item = [sound_menu addItemWithTitle:@"Mute" action:@selector(toggleMuting) keyEquivalent:@""];
+ [mute_item setTarget:NDS];
+/*
+ [sound_menu addItem:[NSMenuItem separatorItem]];
+
+ temp = [sound_menu addItemWithTitle:@"Record to File..." action:@selector(chooseSoundOutputFile) keyEquivalent: @"r"];
+ [temp setTarget:NDS];
+
+ temp = [sound_menu addItemWithTitle:@"Pause Recording" action:@selector(startRecording) keyEquivalent: @""];
+ [temp setTarget:NDS];
+
+ temp = [sound_menu addItemWithTitle:@"Save Recording" action:@selector(pauseRecording) keyEquivalent: @""];
+ [temp setTarget:NDS];
+*/
//Create the window menu
/*
window = [[NSMenu alloc] initWithTitle:localizedString(@"Window", nil)];
diff --git a/desmume/src/cocoa/nds_control.h b/desmume/src/cocoa/nds_control.h
index 1d16c7485..41c1a2785 100644
--- a/desmume/src/cocoa/nds_control.h
+++ b/desmume/src/cocoa/nds_control.h
@@ -24,10 +24,14 @@
//This class manages emulation
//dont instanciate more than once!
-@interface NintendoDS : NSObject {}
+@interface NintendoDS : NSObject
+{
+ BOOL muted;
+}
-//creation
+//
- (id)init;
+- (id)destroy;
//Firmware control
- (void)setPlayerName:(NSString*)player_name;
@@ -56,6 +60,14 @@
//
- (void)showRomInfo;
+
+//sound
+- (void)setVolume:(id)sender;
+- (void)toggleMuting;
+- (void)chooseSoundOutputFile;
+- (void)startRecording;
+- (void)pauseRecording;
+
@end
diff --git a/desmume/src/cocoa/nds_control.m b/desmume/src/cocoa/nds_control.m
index 24e892e1d..50a9c5b66 100644
--- a/desmume/src/cocoa/nds_control.m
+++ b/desmume/src/cocoa/nds_control.m
@@ -25,6 +25,7 @@
//DeSmuME general includes
#define OBJ_C
+#include "sndOSX.h"
#include "../NDSSystem.h"
#include "../saves.h"
#undef BOOL
@@ -33,7 +34,17 @@
//times one million for microseconds per frame
#define DS_MICROSECONDS_PER_FRAME (1.0 / 59.8) * 1000000.0
-//fixme bug when hitting apple+Q during the open dialog
+SoundInterface_struct *SNDCoreList[] = {
+&SNDDummy,
+&SNDFile,
+&SNDOSX,
+NULL
+};
+
+@interface SoundSavePanel : NSSavePanel
+{
+}
+@end
@interface TableHelper : NSObject
{
@@ -75,6 +86,9 @@ NSMenuItem *rom_info_item;
NSMenuItem *frame_skip_auto_item;
NSMenuItem *frame_skip_item[MAX_FRAME_SKIP];
+NSMenuItem *volume_item[10];
+NSMenuItem *mute_item;
+
volatile u8 frame_skip = 0; //this is one more than the acutal frame skip, a value of 0 signifies auto frame skip
static int backupmemorytype=MC_TYPE_AUTODETECT;
@@ -93,17 +107,27 @@ NSString *current_file;
struct armcpu_ctrl_iface *arm7_ctrl_iface;
//struct configured_features my_config;
- NDS_Init( arm9_memio, &arm9_ctrl_iface,
- arm7_memio, &arm7_ctrl_iface);
+ NDS_Init(/*arm9_memio, &arm9_ctrl_iface, arm7_memio, &arm7_ctrl_iface*/);
NDS_FillDefaultFirmwareConfigData(&firmware);
-[self setPlayerName:@"Joe"];
+[self setPlayerName:@"Joe"]; //fixme
NDS_CreateDummyFirmware(&firmware);
+ if(SPU_ChangeSoundCore(SNDCORE_OSX, 735 * 4) != 0)
+ {
+ messageDialog(localizedString(@"Error", nil), @"Unable to initialize sound core");
+ }
+
+ SPU_SetVolume(100);
return self;
}
+- (id)destroy
+{
+ NDS_DeInit();
+}
+
- (void)setPlayerName:(NSString*)player_name
{
//first we convert to UTF-16 which the DS uses to store the nickname
@@ -141,7 +165,7 @@ NSString *current_file;
[self pause];
//load the rom
- if(!NDS_LoadROM([filename cStringUsingEncoding:NSASCIIStringEncoding], backupmemorytype, backupmemorysize, "/Users/gecko/AAAA.sav") > 0)
+ if(!NDS_LoadROM([filename cStringUsingEncoding:NSASCIIStringEncoding], backupmemorytype, backupmemorysize, "temp.sav") > 0)
{
//if it didn't work give an error and dont unpause
messageDialog(localizedString(@"Error", nil), @"Could not open file");
@@ -326,7 +350,6 @@ NSString *current_file;
bool was_running = false; //initialized here to avoid a warning
unsigned long long frame_start_time, frame_end_time;
- unsigned long long microseconds_per_frame;
int frames_to_skip = 0;
@@ -354,6 +377,8 @@ NSString *current_file;
Microseconds((struct UnsignedWide*)&frame_start_time);
cycles = NDS_exec((560190<<1)-cycles, FALSE);
+ SPU_Emulate();
+
if(frames_to_skip != 0)
frames_to_skip--;
@@ -539,8 +564,80 @@ NSString *current_file;
if(!was_paused)[self execute];
}
+- (void)setVolume:(id)sender
+{
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+ if(sender == volume_item[i])
+ {
+ [volume_item[i] setState:NSOnState];
+ SNDOSXSetVolume((i+1)*10);
+ } else
+ [volume_item[i] setState:NSOffState];
+ }
+}
+
+- (void)toggleMuting
+{
+ if([mute_item state] == NSOffState)
+ {
+ [mute_item setState:NSOnState];
+ SNDOSXMuteAudio();
+ } else
+ {
+ [mute_item setState:NSOffState];
+
+ //find and restore volume
+ int i;
+ for(i = 0; i < 10; i++)
+ if([volume_item[i] state] == NSOnState)
+ SNDOSXSetVolume((i+1)*10);
+ }
+}
+
+- (void)chooseSoundOutputFile
+{
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ //fixme localize
+ [panel setTitle:@"Record Audio to File..."];
+
+/*
+ [panel beginSheetForDirectory:@"" file:@"DeSmuMEaudio.wav" modalForWindow:main_window modalDelegate:self
+ didEndSelector:nil contextInfo:nil];
+*/
+ //[panel setMessage:@"Please choose an audio file to record sound output to"];
+ //[panel setNameFieldLabel:@"BAL"];
+ [panel runModal];
+
+ //[NSApp runModalforWindow:panel];
+
+ if(!SNDOSXOpenFile([panel filename]))
+ {
+ messageDialog(localizedString(@"Error", nil), @"Couldn't create sound recording output file");
+ return;
+ }
+
+ SNDOSXStartRecording();
+
+}
+
+- (void)startRecording
+{
+ SNDOSXStartRecording();
+}
+
+- (void)pauseRecording
+{
+ SNDOSXStopRecording();
+}
+
@end
-#define ROM_INFO_ROWS 9
+
+//Rom info helper stuff -------------------------------------------------------------
+
+#define ROM_INFO_ROWS 8
#define ROM_INFO_WIDTH 400
@implementation TableHelper
- (id)initWithWindow:(NSWindow*)window
@@ -549,7 +646,7 @@ NSString *current_file;
type_column = [[NSTableColumn alloc] initWithIdentifier:@""];
[type_column setEditable:NO];
- [value_column setResizable:YES];
+ [type_column setResizable:YES];
[[type_column headerCell] setStringValue:@"Attribute"];
[type_column setMinWidth: 1];
@@ -618,33 +715,30 @@ if([table headerView] == nil)messageDialogBlank();
if(aTableColumn == type_column)
{
if(rowIndex == 0)
- return localizedString(@"File on Disc", @" ROM Info ");
+ return localizedString(@"File", @" ROM Info ");
if(rowIndex == 1)
- return localizedString(@"ROM Title", @" ROM Info ");
+ return localizedString(@"Title", @" ROM Info ");
if(rowIndex == 2)
- return localizedString(@"Maker Code", @" ROM Info ");
+ return localizedString(@"Maker", @" ROM Info ");
if(rowIndex == 3)
- return localizedString(@"Unit Code", @" ROM Info ");
+ return localizedString(@"Size", @" ROM Info ");
if(rowIndex == 4)
- return localizedString(@"Card Size", @" ROM Info ");
+ return localizedString(@"ARM9 Size", @" ROM Info ");
if(rowIndex == 5)
- return localizedString(@"Flags", @" ROM Info ");
+ return localizedString(@"ARM7 Size", @" ROM Info ");
if(rowIndex == 6)
- return localizedString(@"Size of ARM9 Binary", @" ROM Info ");
+ return localizedString(@"Data Size", @" ROM Info ");
if(rowIndex == 7)
- return localizedString(@"Size of ARM7 Size", @" ROM Info ");
-
- if(rowIndex == 8)
- return localizedString(@"Size of Data", @" ROM Info ");
+ return localizedString(@"Icon", @" ROM Info ");
} else
- {
+ {//units?
if(rowIndex == 0)return current_file;
if(rowIndex == 1)
@@ -652,47 +746,37 @@ if([table headerView] == nil)messageDialogBlank();
if(rowIndex == 2)
{
+ if(header->makerCode == 12592)
+ return NSSTRc("Nintendo");
+
return [NSString localizedStringWithFormat:@"%u", header->makerCode];
}
if(rowIndex == 3)
{
- return [NSString localizedStringWithFormat:@"%u", header->unitCode];
- }
-
- if(rowIndex == 4)
- {//fixe: should show units?
return [NSString localizedStringWithFormat:@"%u", header->cardSize];
}
- if(rowIndex == 5)
- {//always seems to be empty?
- return [NSString localizedStringWithFormat:@"%u%u%u%u%u%u%u%u",
- ((header->flags) & 0x01) >> 0,
- ((header->flags) & 0x02) >> 1,
- ((header->flags) & 0x04) >> 2,
- ((header->flags) & 0x08) >> 3,
- ((header->flags) & 0x10) >> 4,
- ((header->flags) & 0x20) >> 5,
- ((header->flags) & 0x40) >> 6,
- ((header->flags) & 0x80) >> 7];
- }
-
- if(rowIndex == 6)
- {//fixe: should show units?
+ if(rowIndex == 4)
+ {
return [NSString localizedStringWithFormat:@"%u", header->ARM9binSize];
}
- if(rowIndex == 7)
- {//fixe: should show units?
+ if(rowIndex == 5)
+ {
return [NSString localizedStringWithFormat:@"%u", header->ARM7binSize];
}
- if(rowIndex == 8)
- {//fixe: should show units?
+ if(rowIndex == 6)
+ {
return [NSString localizedStringWithFormat:@"%u", header->ARM7binSize + header->ARM7src];
}
+ if(rowIndex == 7)
+ {
+ return @"NOT FINISHED";
+ }
+
}
return @"If you see this, there is a bug";
diff --git a/desmume/src/cocoa/preferences.m b/desmume/src/cocoa/preferences.m
index 20bd8d36b..775a0e52a 100644
--- a/desmume/src/cocoa/preferences.m
+++ b/desmume/src/cocoa/preferences.m
@@ -40,7 +40,6 @@ NSDictionary *preferences_font_attribs;
NSTabViewItem *interface_pane_tab;
NSTabViewItem *firmware_pane_tab;
-NSTabViewItem *plugins_pane_tab;
NSText *language_selection_text;
NSPopUpButton *language_selection;
@@ -377,24 +376,12 @@ void setAppDefaults()
[tab_view addTabViewItem:firmware_pane_tab];
NSDictionary *firmware_options = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSArray arrayWithObjects:@"Text", [NSData dataWithBytes:&@selector(playerName:) length:sizeof(SEL)], nil], PREF_FIRMWARE_PLAYER_NAME,
- [NSArray arrayWithObjects:@"Array", [NSData dataWithBytes:&@selector(nonexisant:) length:sizeof(SEL)], @"Danish",@"English",@"French",nil], PREF_FIRMWARE_LANGUAGE,
+ //[NSAsrray arrayWithObjects:@"Text", [NSData dataWithBytes:&@selector(playerName:) length:sizeof(SEL)], nil], PREF_FIRMWARE_PLAYER_NAME,
+ //[NSArray arrayWithObjects:@"Array", [NSData dataWithBytes:&@selector(nonexisant:) length:sizeof(SEL)], @"Japanese",@"English",@"French",@"German",@"Italian",@"Spanish",nil], PREF_FIRMWARE_LANGUAGE,
nil];
NSView *firmware_view = createPreferencesView(firmware_pane_tab, firmware_options, delegate);
- //Create the "Plugins" pane
- plugins_pane_tab = [[NSTabViewItem alloc] initWithIdentifier:nil];
- [plugins_pane_tab setLabel:localizedString(@"Plugins", nil)];
- [tab_view addTabViewItem:plugins_pane_tab];
-
- NSDictionary *plugin_options = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSArray arrayWithObjects:@"Array", [NSData dataWithBytes:&@selector(nonexisant:) length:sizeof(SEL)], @"OpenGL 3D",nil], PREF_3D_PLUGIN,
- [NSArray arrayWithObjects:@"Array", [NSData dataWithBytes:&@selector(nonexisant:) length:sizeof(SEL)], @"None",nil], PREF_SOUND_PLUGIN,
- nil];
-
- NSView *plugins_view = createPreferencesView(plugins_pane_tab, plugin_options, delegate);
-
}
//make the window controller
diff --git a/desmume/src/cocoa/screenshot.h b/desmume/src/cocoa/screenshot.h
index 7569c76ac..ef2e80302 100644
--- a/desmume/src/cocoa/screenshot.h
+++ b/desmume/src/cocoa/screenshot.h
@@ -38,7 +38,7 @@
NSPopUpButton *format_button;
}
-- (id)initWithBuffer:(u8*)buffer rotation:(u8)rotation saveOnly:(BOOL)save_only;
+- (id)initWithBuffer:(const u8*)buffer rotation:(u8)rotation saveOnly:(BOOL)save_only;
@end
#endif
diff --git a/desmume/src/cocoa/sndOSX.h b/desmume/src/cocoa/sndOSX.h
new file mode 100644
index 000000000..9f4fd3b8f
--- /dev/null
+++ b/desmume/src/cocoa/sndOSX.h
@@ -0,0 +1,39 @@
+/* Copyright 2007 Jeff Bland
+
+ This file is part of DeSmuME.
+
+ DeSmuME is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DeSmuME is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DeSmuME; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef SNDOSX_H
+#define SNDOSX_H
+
+#include "../SPU.h"
+
+#define SNDCORE_OSX 58325 //hopefully this is unique number
+
+//This is the sound inerface so the emulator core can send us sound info and whatnot
+extern SoundInterface_struct SNDOSX;
+
+//Beyond this point are sound interface extensions specific to the mac port
+
+//recording to file
+bool SNDOSXOpenFile(void *fname); //opens a file for recording (if filename is the currently opened one, it will restart the file), fname is an NSString
+void SNDOSXStartRecording(); //begins recording to the currently open file if there is an open file
+void SNDOSXStopRecording(); //pauses recording (you can continue recording later)
+void SNDOSXCloseFile(); //closes the file, making sure it's saved
+
+#endif
+
diff --git a/desmume/src/cocoa/sndOSX.m b/desmume/src/cocoa/sndOSX.m
new file mode 100644
index 000000000..7bf1622e4
--- /dev/null
+++ b/desmume/src/cocoa/sndOSX.m
@@ -0,0 +1,483 @@
+/* Copyright 2007 Jeff Bland
+
+ This file is part of DeSmuME
+
+ DeSmuME is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DeSmuME is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DeSmuME; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+//external includes
+
+#include "stdlib.h"
+#include "stdio.h"
+
+#include
+
+#include
+#include
+#include
+
+//desmume includes
+#define OBJ_C
+#include "sndOSX.h"
+#undef BOOL
+
+//globals
+AudioUnit output_unit = NULL; //pointer to our audio device
+UInt16 *sound_data = NULL; //buffer where we hold data between getting it from the emulator and sending it to the device
+u32 sound_buffer_size; //size in bytes of sound_data
+volatile u32 sound_offset; //position in the buffer that we have copied to from the emu
+volatile u32 sound_position; //position in the buffer that we have played to
+
+volatile bool mix_sound = false; //if false, our sound callback will do nothing
+volatile bool in_mix = false; //should probably use a real mutex instead but this seems to work
+
+float current_volume_scalar = 100; //for volume/muting
+
+//file output
+volatile bool file_open = false;
+ExtAudioFileRef outfile;
+
+//defines
+#define SAMPLE_SIZE (sizeof(s16) * 2) //16 bit samples, stereo sound
+
+//////////////////////////////////////////////////////////////////////////////
+
+//This is the callback where we will stick the sound data we've gotten from
+//the emulator into a core audio buffer to be processed and sent to the sound driver
+
+OSStatus soundMixer(
+ void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+//printf("SOUND CALLBACK %u off%u pos%u\n", inNumberFrames * 4, sound_offset, sound_position);
+
+ if(!mix_sound)
+ return noErr;
+
+ in_mix = true;
+
+ UInt32 bytes_to_copy = inNumberFrames * 4;
+ UInt8 *data_output = (UInt8*)ioData->mBuffers[0].mData;
+ UInt8 *data_input = (UInt8*)sound_data;
+
+ if(sound_position == sound_offset)
+ {//buffer empty
+
+ memset(data_output, 0, bytes_to_copy);
+
+ } else
+ { //buffer is not empty
+
+ int x;
+ for(x = 0; x < bytes_to_copy; x++)
+ {
+ sound_position++;
+
+ if(sound_position >= sound_buffer_size)sound_position = 0;
+
+ if(sound_position == sound_offset)
+ {
+ sound_position --;
+ memset(&data_output[x], 0, bytes_to_copy - x);
+ break;
+ }
+
+ data_output[x] = data_input[sound_position];
+
+ }
+ }
+
+ //copy to other channels
+ UInt32 channel;
+ for (channel = 1; channel < ioData->mNumberBuffers; channel++)
+ memcpy(ioData->mBuffers[channel].mData, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);
+
+ //record to file
+ if(file_open)
+ ExtAudioFileWrite(outfile, inNumberFrames, ioData);
+
+ in_mix = false;
+ return noErr;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int SNDOSXInit(int buffer_size)
+{
+ OSStatus err = noErr;
+
+ //Setup the sound buffer -------------------------------------------
+
+ //add one more since sound position can never catch up to
+ //sound_offset - because if they were the same it would signify
+ //that the buffer is emptys
+ buffer_size += 1;
+
+ if((sound_data = (UInt16*)malloc(buffer_size)) == NULL)
+ return -1;
+
+ sound_buffer_size = buffer_size;
+
+ SNDOSXReset();
+
+ //grab the default audio unit -------------------------
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == NULL)
+ return -1;
+
+ err = OpenAComponent(comp, &output_unit);
+ if (comp == NULL)
+ return -1;
+
+ //then setup the callback where we will send the audio -------
+
+ AURenderCallbackStruct callback;
+ callback.inputProc = soundMixer;
+ callback.inputProcRefCon = NULL;
+
+ err = AudioUnitSetProperty(
+ output_unit,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input,
+ 0,
+ &callback,
+ sizeof(callback)
+ );
+
+ if(err != noErr)
+ return -1;
+
+ //now begin running the audio unit-- ----------------------
+
+ AudioStreamBasicDescription audio_format;
+ audio_format.mSampleRate = 44100;
+ audio_format.mFormatID = kAudioFormatLinearPCM;
+ audio_format.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian
+ | kLinearPCMFormatFlagIsPacked;
+ audio_format.mBytesPerPacket = 4;
+ audio_format.mFramesPerPacket = 1;
+ audio_format.mBytesPerFrame = 4;
+ audio_format.mChannelsPerFrame = 2;
+ audio_format.mBitsPerChannel = 16;
+
+ err = AudioUnitSetProperty(
+ output_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ 0,
+ &audio_format,
+ sizeof(audio_format)
+ );
+
+ if(err != noErr){return -1;}
+
+ // Initialize unit
+ err = AudioUnitInitialize(output_unit);
+ if(err != noErr)
+ return -1;
+
+ //Start the rendering
+ //The DefaultOutputUnit will do any format conversions to the format of the default device
+ err = AudioOutputUnitStart(output_unit);
+ if(err != noErr)
+ return -1;
+
+ //
+ current_volume_scalar = 100;
+
+ //we call the CFRunLoopRunInMode to service any notifications that the audio
+ //system has to deal with
+ //CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false);
+ //verify_noerr (AudioOutputUnitStop (output_unit));
+
+ mix_sound = true;
+
+ //------------------------------------------------------------------
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SNDOSXDeInit()
+{
+ if(output_unit != NULL)
+ {
+ //wait for mix to end
+ mix_sound = false;
+ while(in_mix);
+
+ //closes the audio unit (errors are ignored here)
+ AudioOutputUnitStop(output_unit);
+ AudioUnitUninitialize(output_unit);
+
+ output_unit = NULL;
+ }
+
+ SNDOSXCloseFile(); //end recording to file if needed
+
+ if(sound_data != NULL)
+ {
+ free(sound_data);
+ sound_data = NULL;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int SNDOSXReset()
+{
+ if(sound_data == NULL)return;
+
+ memset(sound_data, 0, sound_buffer_size);
+
+ sound_offset = 0;
+ sound_position = 0;
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SNDOSXUpdateAudio(s16 *buffer, u32 num_samples)
+{
+//printf("NEW SOUND DATA %u ", num_samples * SAMPLE_SIZE);
+
+ if(sound_data == NULL)return;
+
+ //get the avilable bufferspace ------------------------------------------------
+
+ //in these calculatations:
+ //1 is subtracted from sound_buffer_size since if the size is 2, we can only go to index 1
+ //1 is subtracted from sound_position since sound_offset should never catch up to it
+ //since them being equal means that the buffer is empty, rather than full
+
+ u32 bytes_to_copy = num_samples * SAMPLE_SIZE;
+
+ int sound_pos = sound_position; //incase the sound render thread changes it
+
+ u32 copy1size, copy2size;
+
+ if(sound_offset >= sound_pos)
+ {
+ if(sound_pos > 0)
+ {
+ copy1size = sound_buffer_size - sound_offset;
+ copy2size = (sound_pos-1);
+ } else
+ {
+ copy1size = sound_buffer_size - sound_offset - 1;
+ copy2size = 0;
+ }
+ } else if(sound_offset < sound_pos)
+ {
+ copy1size = sound_pos - sound_offset - 1;
+ copy2size = 0;
+ }
+
+//printf("copy1:%u copy2:%u both:%u\n",copy1size, copy2size, copy2size+copy1size);
+ //truncate the copy sizes to however much we've been been given to fill --------
+
+ if(copy1size + copy2size < bytes_to_copy)
+ //this shouldn't happen as the emulator core generally asks how much
+ //space is available before sending stuff, but just incase
+ printf("NDOSXUpdateAudio() error: not enough space in buffer");
+ else
+ {
+ u32 diff = copy1size + copy2size - bytes_to_copy;
+
+ if(diff <= copy2size)
+ {
+ copy2size -= diff;
+ } else
+ {
+ diff -= copy2size;
+ copy2size = 0;
+ copy1size -= diff;
+ }
+
+ }
+
+ //copy the data -------------------------------------------------------------
+
+ memcpy((((u8 *)sound_data)+sound_offset), buffer, copy1size);
+
+ if(copy2size)
+ memcpy(sound_data, ((u8 *)buffer)+copy1size, copy2size);
+
+ //change our sound_offset
+ sound_offset += copy1size + copy2size;
+ while(sound_offset >= sound_buffer_size)sound_offset -= sound_buffer_size;
+
+//printf("cop1%u cop2%u off%u size%u \n", copy1size, copy2size, sound_offset, sound_buffer_size);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+u32 SNDOSXGetAudioSpace()
+{
+ if(sound_data == NULL)return 0;
+
+ int sound_pos = sound_position; //incase the sound render thread changes it
+
+ u32 free_space;
+
+ if(sound_offset > sound_pos)
+ free_space = sound_buffer_size + sound_pos - 1 - sound_offset;
+ else if(sound_offset < sound_pos)
+ free_space = sound_pos - sound_offset - 1;
+ else
+ free_space = sound_buffer_size-1; //sound_offset == sound_data, meaning the buffer is empty
+
+ return (free_space / SAMPLE_SIZE);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SNDOSXMuteAudio()
+{
+ if(sound_data == NULL)return;
+
+ OSStatus err = AudioUnitSetParameter(output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, 0, 0);
+
+ if(err != noErr)
+ printf("SNDOSXMuteAudio error\n");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SNDOSXUnMuteAudio()
+{
+ if(sound_data == NULL)return;
+
+ OSStatus err = AudioUnitSetParameter(output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, current_volume_scalar, 0);
+
+ if(err != noErr)
+ printf("SNDOSXUnMuteAudio %f error\n",current_volume_scalar);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void SNDOSXSetVolume(int volume)
+{
+ if(sound_data == NULL)return;
+
+ if(volume > 100)volume = 100;
+ if(volume < 0)volume = 0;
+
+ current_volume_scalar = volume;
+ current_volume_scalar /= 100.0;
+
+ OSStatus err = AudioUnitSetParameter(output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, current_volume_scalar, 0);
+
+ if(err != noErr)
+ printf("SNDOSXSetVolume %f error\n",current_volume_scalar);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+SoundInterface_struct SNDOSX = {
+ SNDCORE_OSX,
+ "Core Audio Sound Interface",
+ SNDOSXInit,
+ SNDOSXDeInit,
+ SNDOSXUpdateAudio,
+ SNDOSXGetAudioSpace,
+ SNDOSXMuteAudio,
+ SNDOSXUnMuteAudio,
+ SNDOSXSetVolume
+};
+
+//////////////////////////////////////////////////////////////////////////////
+//Sound recording
+//////////////////////////////////////////////////////////////////////////////
+
+bool SNDOSXOpenFile(void *fname)
+{
+ if(sound_data == NULL)return false;
+
+ SNDOSXCloseFile();
+
+ if(!fname)return false;
+
+ NSString *filename = (NSString*)fname;
+ FSRef ref;
+
+ if(FSPathMakeRef((const UInt8*)[[filename stringByDeletingLastPathComponent] fileSystemRepresentation], &ref, NULL) != noErr)
+ {
+ SNDOSXStopRecording();
+ return false;
+ }
+
+ AudioStreamBasicDescription audio_format;
+ audio_format.mSampleRate = 44100;
+ audio_format.mFormatID = kAudioFormatLinearPCM;
+ audio_format.mFormatFlags = kAudioFormatFlagIsSignedInteger
+ | kAudioFormatFlagsNativeEndian
+ | kLinearPCMFormatFlagIsPacked;
+ audio_format.mBytesPerPacket = 4;
+ audio_format.mFramesPerPacket = 1;
+ audio_format.mBytesPerFrame = 4;
+ audio_format.mChannelsPerFrame = 2;
+ audio_format.mBitsPerChannel = 16;
+
+ if(ExtAudioFileCreateNew(&ref, (CFStringRef)[[filename pathComponents] lastObject], kAudioFileWAVEType, &audio_format, NULL, &outfile) != noErr)
+ return false;
+
+ file_open = true;
+
+ return true;
+}
+
+void SNDOSXStartRecording()
+{
+ if(sound_data == NULL)return;
+}
+
+void SNDOSXStopRecording()
+{
+ if(sound_data == NULL)return;
+}
+
+void SNDOSXCloseFile()
+{
+ if(sound_data == NULL)return;
+
+ if(file_open)
+ {
+ file_open = false;
+
+ //if it's rendering sound, wait until it's not
+ //so we dont close the file while writing to it
+ while(in_mix);
+
+ ExtAudioFileDispose(outfile);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////