added sound, and did various other minor things to the cocoa port

This commit is contained in:
gecko_reverse 2007-11-25 16:38:47 +00:00
parent 312bdff914
commit aeea42a141
11 changed files with 774 additions and 77 deletions

View File

@ -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]

View File

@ -3,29 +3,75 @@
<FileVersion major="1" minor="6" />
<Project>
<Option title="DeSmuME Cocoa" />
<Option platforms="Mac;" />
<Option pch_mode="2" />
<Option compiler="gcc" />
<Option show_notes="0">
<notes>
<![CDATA[DeSmuME
Cocoa Port
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]]>
</notes>
</Option>
<Build>
<Target title="Cocoa">
<Target title="Cocoa X86">
<Option platforms="Mac;" />
<Option output="DeSmuME.app/Contents/MacOS/DeSmuME" prefix_auto="0" extension_auto="0" />
<Option object_output="obj/Release/" />
<Option type="0" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-march=pentium-m" />
<Add option="-arch i386" />
<Add option="-DDESMUME_COCOA" />
</Compiler>
<Linker>
<Add option="-framework Cocoa" />
<Add option="-framework OpenGL" />
<Add option="-framework CoreAudio" />
<Add option="-framework AudioUnit" />
<Add option="-framework AudioToolbox" />
</Linker>
</Target>
<Target title="Cocoa PPC">
<Option platforms="Mac;" />
<Option output="DeSmuME.app/Contents/MacOS/DeSmuME" prefix_auto="0" extension_auto="0" />
<Option object_output="obj/Release/" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-DDESMUME_COCOA" />
<Add option="-DWORDS_BIGENDIAN" />
</Compiler>
<Linker>
<Add option="-framework Cocoa" />
<Add option="-framework OpenGL" />
<Add option="-framework CoreAudio" />
<Add option="-framework AudioUnit" />
<Add option="-framework AudioToolbox" />
</Linker>
</Target>
</Build>
<VirtualTargets>
<Add alias="Cocoa Universal" targets="Cocoa X86;Cocoa PPC;" />
</VirtualTargets>
<Compiler>
<Add option="-fexpensive-optimizations" />
<Add option="-O3" />
<Add option="-Wall" />
</Compiler>
<Linker>
<Add option="-s" />
@ -104,6 +150,11 @@
<Option compile="1" />
<Option link="1" />
</Unit>
<Unit filename="sndOSX.h" />
<Unit filename="sndOSX.m">
<Option compile="1" />
<Option link="1" />
</Unit>
<Unit filename="../config.h" />
<Unit filename="../cp15.c">
<Option compilerVar="CC" />

View File

@ -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

View File

@ -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;

View File

@ -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)];

View File

@ -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

View File

@ -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";

View File

@ -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

View File

@ -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

View File

@ -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

483
desmume/src/cocoa/sndOSX.m Normal file
View File

@ -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 <Cocoa/Cocoa.h>
#include <CoreServices/CoreServices.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolBox.h>
//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);
}
}
///////////////////////////////////////////////////////////////////////////