Merge branch 'gc-mic'

Added GameCube Microphone support.  Uses your default audio recording device.  The Microphone is selectable from the Slot A/Slot B pulldowns under the GameCube tab.  The Microphone button can be set under GCPad configuration for pad 1 and 2. Thanks to MooglyGuy and skidau.
This commit is contained in:
Shawn Hoffman
2011-10-17 03:21:11 -07:00
41 changed files with 3261 additions and 322 deletions

View File

@ -355,7 +355,7 @@ void SConfig::LoadSettings()
ini.Get("Core", "MemcardA", &m_strMemoryCardA);
ini.Get("Core", "MemcardB", &m_strMemoryCardB);
ini.Get("Core", "ReloadMemcardOnState", &b_reloadMCOnState, true);
ini.Get("Core", "SlotA", (int*)&m_EXIDevice[0], EXIDEVICE_MEMORYCARD_A);
ini.Get("Core", "SlotA", (int*)&m_EXIDevice[0], EXIDEVICE_MEMORYCARD);
ini.Get("Core", "SlotB", (int*)&m_EXIDevice[1], EXIDEVICE_NONE);
ini.Get("Core", "SerialPort1", (int*)&m_EXIDevice[2], EXIDEVICE_NONE);
ini.Get("Core", "BBA_MAC", &m_bba_mac);

View File

@ -75,18 +75,18 @@ void DoState(PointerWrap &p)
void ChangeDeviceCallback(u64 userdata, int cyclesLate)
{
u8 channel = (u8)(userdata >> 32);
u8 device = (u8)(userdata >> 16);
u8 slot = (u8)userdata;
u8 type = (u8)(userdata >> 16);
u8 num = (u8)userdata;
g_Channels[channel]->AddDevice((TEXIDevices)device, slot);
g_Channels[channel]->AddDevice((TEXIDevices)type, num);
}
void ChangeDevice(u8 channel, TEXIDevices device, u8 slot)
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num)
{
// Called from GUI, so we need to make it thread safe.
// Let the hardware see no device for .5b cycles
CoreTiming::ScheduleEvent_Threadsafe(0, changeDevice, ((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | slot);
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | ((u64)device << 16) | slot);
CoreTiming::ScheduleEvent_Threadsafe(0, changeDevice, ((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | device_num);
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | ((u64)device_type << 16) | device_num);
}
// Unused (?!)

View File

@ -33,7 +33,7 @@ void Update();
void UpdateInterrupts();
void ChangeDeviceCallback(u64 userdata, int cyclesLate);
void ChangeDevice(u8 channel, TEXIDevices device, u8 slot);
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num);
void Read32(u32& _uReturnValue, const u32 _iAddress);
void Write32(const u32 _iValue, const u32 _iAddress);

View File

@ -42,7 +42,7 @@ CEXIChannel::CEXIChannel(u32 ChannelId) :
m_Status.CHIP_SELECT = 1;
for (int i = 0; i < NUM_DEVICES; i++)
m_pDevices[i] = EXIDevice_Create(EXIDEVICE_NONE);
m_pDevices[i] = EXIDevice_Create(EXIDEVICE_NONE, m_ChannelId);
}
CEXIChannel::~CEXIChannel()
@ -59,19 +59,19 @@ void CEXIChannel::RemoveDevices()
}
}
void CEXIChannel::AddDevice(const TEXIDevices _device, const unsigned int _iSlot)
void CEXIChannel::AddDevice(const TEXIDevices device_type, const int device_num)
{
_dbg_assert_(EXPANSIONINTERFACE, _iSlot < NUM_DEVICES);
_dbg_assert_(EXPANSIONINTERFACE, device_num < NUM_DEVICES);
// delete the old device
if (m_pDevices[_iSlot] != NULL)
if (m_pDevices[device_num] != NULL)
{
delete m_pDevices[_iSlot];
m_pDevices[_iSlot] = NULL;
delete m_pDevices[device_num];
m_pDevices[device_num] = NULL;
}
// create the new one
m_pDevices[_iSlot] = EXIDevice_Create(_device);
m_pDevices[device_num] = EXIDevice_Create(device_type, m_ChannelId);
// This means "device presence changed", software has to check
// m_Status.EXT to see if it is now present or not
@ -107,9 +107,9 @@ bool CEXIChannel::IsCausingInterrupt()
}
}
IEXIDevice* CEXIChannel::GetDevice(u8 _CHIP_SELECT)
IEXIDevice* CEXIChannel::GetDevice(const u8 chip_select)
{
switch(_CHIP_SELECT)
switch (chip_select)
{
case 1: return m_pDevices[0];
case 2: return m_pDevices[1];

View File

@ -112,12 +112,12 @@ private:
public:
// get device
IEXIDevice* GetDevice(u8 _CHIP_SELECT);
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
CEXIChannel(u32 ChannelId);
~CEXIChannel();
void AddDevice(const TEXIDevices _device, const unsigned int _iSlot);
void AddDevice(const TEXIDevices device_type, const int device_num);
// Remove all devices
void RemoveDevices();

View File

@ -102,20 +102,16 @@ public:
// F A C T O R Y
IEXIDevice* EXIDevice_Create(TEXIDevices _EXIDevice)
IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num)
{
switch(_EXIDevice)
switch (device_type)
{
case EXIDEVICE_DUMMY:
return new CEXIDummy("Dummy");
break;
case EXIDEVICE_MEMORYCARD_A:
return new CEXIMemoryCard("MemoryCardA", SConfig::GetInstance().m_strMemoryCardA, 0);
break;
case EXIDEVICE_MEMORYCARD_B:
return new CEXIMemoryCard("MemoryCardB", SConfig::GetInstance().m_strMemoryCardB, 1);
case EXIDEVICE_MEMORYCARD:
return new CEXIMemoryCard(channel_num);
break;
case EXIDEVICE_MASKROM:
@ -127,7 +123,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices _EXIDevice)
break;
case EXIDEVICE_MIC:
return new CEXIMic(1);
return new CEXIMic(channel_num);
break;
case EXIDEVICE_ETH:

View File

@ -53,8 +53,7 @@ public:
enum TEXIDevices
{
EXIDEVICE_DUMMY,
EXIDEVICE_MEMORYCARD_A,
EXIDEVICE_MEMORYCARD_B,
EXIDEVICE_MEMORYCARD,
EXIDEVICE_MASKROM,
EXIDEVICE_AD16,
EXIDEVICE_MIC,
@ -64,6 +63,6 @@ enum TEXIDevices
EXIDEVICE_NONE = (u8)-1
};
extern IEXIDevice* EXIDevice_Create(TEXIDevices _EXIDevice);
extern IEXIDevice* EXIDevice_Create(const TEXIDevices device_type, const int channel_num);
#endif

View File

@ -45,13 +45,13 @@ void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
ptr->Flush();
}
CEXIMemoryCard::CEXIMemoryCard(const std::string& _rName, const std::string& _rFilename, int _card_index) :
m_strFilename(_rFilename),
card_index(_card_index),
m_bDirty(false)
CEXIMemoryCard::CEXIMemoryCard(const int index)
: card_index(index)
, m_bDirty(false)
{
cards[_card_index] = this;
et_this_card = CoreTiming::RegisterEvent(_rName.c_str(), FlushCallback);
m_strFilename = (card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA : SConfig::GetInstance().m_strMemoryCardB;
cards[card_index] = this;
et_this_card = CoreTiming::RegisterEvent((card_index == 0) ? "memcardA" : "memcardB", FlushCallback);
reloadOnState = SConfig::GetInstance().b_reloadMCOnState;
interruptSwitch = 0;
@ -427,9 +427,6 @@ void CEXIMemoryCard::DoState(PointerWrap &p)
{
if (reloadOnState)
{
int slot = 0;
if (GetFileName() == SConfig::GetInstance().m_strMemoryCardA)
slot = 1;
ExpansionInterface::ChangeDevice(slot, slot ? EXIDEVICE_MEMORYCARD_B : EXIDEVICE_MEMORYCARD_A, 0);
ExpansionInterface::ChangeDevice(card_index, EXIDEVICE_MEMORYCARD, 0);
}
}

View File

@ -32,7 +32,7 @@ struct FlushData
class CEXIMemoryCard : public IEXIDevice
{
public:
CEXIMemoryCard(const std::string& _rName, const std::string& _rFilename, int card_index);
CEXIMemoryCard(const int index);
virtual ~CEXIMemoryCard();
void SetCS(int cs);
void Update();
@ -40,8 +40,6 @@ public:
bool IsPresent();
void DoState(PointerWrap &p);
inline const std::string &GetFileName() const { return m_strFilename; };
private:
// This is scheduled whenever a page write is issued. The this pointer is passed
// through the userdata parameter, so that it can then call Flush on the right card.

View File

@ -16,115 +16,165 @@
// http://code.google.com/p/dolphin-emu/
#include "Common.h"
#include "FileUtil.h"
#include "StringUtil.h"
#include "../Core.h"
#if HAVE_PORTAUDIO
#include "../CoreTiming.h"
#include "SystemTimers.h"
#include "EXI_Device.h"
#include "EXI_DeviceMic.h"
// Unfortunately this must be enabled in Common.h for windows users. Scons should enable it otherwise
#if !HAVE_PORTAUDIO
void SetMic(bool Value){}
CEXIMic::CEXIMic(int _Index){}
CEXIMic::~CEXIMic(){}
bool CEXIMic::IsPresent() {return false;}
void CEXIMic::SetCS(int cs){}
void CEXIMic::Update(){}
void CEXIMic::TransferByte(u8 &byte){}
bool CEXIMic::IsInterruptSet(){return false;}
#else
// We use PortAudio for cross-platform audio input.
// It needs the headers and a lib file for the dll
#include <portaudio.h>
#ifdef _WIN32
#pragma comment(lib, "C:/Users/Shawn/Desktop/portaudio/portaudio-v19/portaudio_x64.lib")
#endif
#include "GCPad.h"
static bool MicButton = false;
static bool IsOpen;
union InputData
void CEXIMic::StreamLog(const char *msg)
{
s16 word;
u8 byte[2];
};
InputData inputData[64]; // 64 words = Max 128 bytes returned????
PaStream *stream;
PaError err;
unsigned short SFreq;
unsigned short SNum;
bool m_bInterruptSet;
bool Sampling;
void SetMic(bool Value)
{
MicButton = Value;
if(Sampling)
Pa_StartStream( stream );
else
Pa_StopStream( stream );
DEBUG_LOG(EXPANSIONINTERFACE, "%s: %s",
msg, Pa_GetErrorText(pa_error));
}
int patestCallback( const void *inputBuffer, void *outputBuffer,
unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
void CEXIMic::StreamInit()
{
s16 *data = (s16*)inputBuffer;
//s16 *out = (s16*)outputBuffer;
// Setup the wonderful c-interfaced lib...
pa_stream = NULL;
if (!m_bInterruptSet && Sampling)
if ((pa_error = Pa_Initialize()) != paNoError)
StreamLog("Pa_Initialize");
stream_buffer = NULL;
samples_avail = stream_wpos = stream_rpos = 0;
}
void CEXIMic::StreamTerminate()
{
StreamStop();
if ((pa_error = Pa_Terminate()) != paNoError)
StreamLog("Pa_Terminate");
}
static int Pa_Callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
(void)outputBuffer;
(void)timeInfo;
(void)statusFlags;
CEXIMic *mic = (CEXIMic *)userData;
std::lock_guard<std::mutex> lk(mic->ring_lock);
if (mic->stream_wpos + mic->buff_size_samples > mic->stream_size)
mic->stream_wpos = 0;
s16 *buff_in = (s16 *)inputBuffer;
s16 *buff_out = &mic->stream_buffer[mic->stream_wpos];
if (buff_in == NULL)
{
for(unsigned int i = 0; i < SNum; ++i)
for (int i = 0; i < mic->buff_size_samples; i++)
{
inputData[i].word = data[i];
//out[i] = inputData[i].word;
buff_out[i] = 0;
}
m_bInterruptSet = true;
}
else
{
for (int i = 0; i < mic->buff_size_samples; i++)
{
buff_out[i] = buff_in[i];
}
}
mic->samples_avail += mic->buff_size_samples;
if (mic->samples_avail > mic->stream_size)
{
mic->samples_avail = 0;
mic->status.buff_ovrflw = 1;
}
mic->stream_wpos += mic->buff_size_samples;
mic->stream_wpos %= mic->stream_size;
return paContinue;
}
void CEXIMic::StreamStart()
{
// Open stream with current parameters
stream_size = buff_size_samples * 500;
stream_buffer = new s16[stream_size];
pa_error = Pa_OpenDefaultStream(&pa_stream, 1, 0, paInt16,
sample_rate, buff_size_samples, Pa_Callback, this);
StreamLog("Pa_OpenDefaultStream");
pa_error = Pa_StartStream(pa_stream);
StreamLog("Pa_StartStream");
}
void CEXIMic::StreamStop()
{
if (pa_stream != NULL && Pa_IsStreamActive(pa_stream) >= paNoError)
Pa_AbortStream(pa_stream);
delete [] stream_buffer;
stream_buffer = NULL;
}
void CEXIMic::StreamReadOne()
{
std::lock_guard<std::mutex> lk(ring_lock);
if (samples_avail >= buff_size_samples)
{
s16 *last_buffer = &stream_buffer[stream_rpos];
memcpy(ring_buffer, last_buffer, buff_size);
samples_avail -= buff_size_samples;
stream_rpos += buff_size_samples;
stream_rpos %= stream_size;
}
}
// EXI Mic Device
// This works by opening and starting a portaudio input stream when the is_active
// bit is set. The interrupt is scheduled in the future based on sample rate and
// buffer size settings. When the console handles the interrupt, it will send
// cmdGetBuffer, which is when we actually read data from a buffer filled
// in the background by Pa_Callback.
CEXIMic::CEXIMic(int _Index)
u8 const CEXIMic::exi_id[] = { 0, 0x0a, 0, 0, 0 };
CEXIMic::CEXIMic(int index)
: slot(index)
{
Index = _Index;
memset(&inputData, 0 , sizeof(inputData));
memset(&Status.U16, 0 , sizeof(u16));
m_position = 0;
command = 0;
m_uPosition = 0;
m_bInterruptSet = false;
MicButton = false;
IsOpen = false;
err = Pa_Initialize();
if (err != paNoError)
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: PortAudio Initialize error %s", Pa_GetErrorText(err));
status.U16 = 0;
sample_rate = rate_base;
buff_size = ring_base;
buff_size_samples = buff_size / sample_size;
ring_pos = 0;
memset(ring_buffer, 0, sizeof(ring_buffer));
next_int_ticks = 0;
StreamInit();
}
CEXIMic::~CEXIMic()
{
err = Pa_CloseStream( stream );
if (err != paNoError)
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: PortAudio Close error %s", Pa_GetErrorText(err));
err = Pa_Terminate();
if (err != paNoError)
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: PortAudio Terminate error %s", Pa_GetErrorText(err));
StreamTerminate();
}
bool CEXIMic::IsPresent()
bool CEXIMic::IsPresent()
{
return true;
}
@ -132,38 +182,26 @@ bool CEXIMic::IsPresent()
void CEXIMic::SetCS(int cs)
{
if (cs) // not-selected to selected
m_uPosition = 0;
else
{
switch (command)
{
case cmdID:
case cmdGetStatus:
case cmdSetStatus:
case cmdGetBuffer:
break;
case cmdWakeUp:
// This is probably not a command, but anyway...
// The command 0xff seems to be used to get in sync with the microphone or to wake it up.
// Normally, it is issued before any other command, or to recover from errors.
WARN_LOG(EXPANSIONINTERFACE, "EXI MIC: WakeUp cmd");
break;
default:
WARN_LOG(EXPANSIONINTERFACE, "EXI MIC: unknown CS command %02x\n", command);
break;
}
}
m_position = 0;
// Doesn't appear to do anything we care about
//else if (command == cmdReset)
}
void CEXIMic::Update()
void CEXIMic::UpdateNextInterruptTicks()
{
next_int_ticks = CoreTiming::GetTicks() +
(SystemTimers::GetTicksPerSecond() / sample_rate) * buff_size_samples;
}
bool CEXIMic::IsInterruptSet()
{
if(m_bInterruptSet)
if (next_int_ticks && CoreTiming::GetTicks() >= next_int_ticks)
{
m_bInterruptSet = false;
if (status.is_active)
UpdateNextInterruptTicks();
else
next_int_ticks = 0;
return true;
}
else
@ -174,124 +212,70 @@ bool CEXIMic::IsInterruptSet()
void CEXIMic::TransferByte(u8 &byte)
{
if (m_uPosition == 0)
if (m_position == 0)
{
command = byte; // first byte is command
byte = 0xFF; // would be tristate, but we don't care.
}
else
{
switch (command)
{
case cmdID:
if (m_uPosition == 1)
;//byte = 0x80; // dummy cycle - taken from memcard, it doesn't seem to need it here
else
byte = (u8)(EXI_DEVTYPE_MIC >> (24-(((m_uPosition-2) & 3) * 8)));
break;
case cmdGetStatus:
{
if (m_uPosition != 1 && m_uPosition != 2)
WARN_LOG(EXPANSIONINTERFACE, "EXI MIC: WARNING GetStatus @ pos: %d should never happen", m_uPosition);
if((!Status.button && MicButton)||(Status.button && !MicButton))
WARN_LOG(EXPANSIONINTERFACE, "EXI MIC: Mic button %s", MicButton ? "pressed" : "released");
Status.button = MicButton ? 1 : 0;
byte = Status.U8[ (m_uPosition - 1) ? 0 : 1];
INFO_LOG(EXPANSIONINTERFACE, "EXI MIC: Status is 0x%04x", Status.U16);
}
break;
case cmdSetStatus:
{
// 0x80 0xXX 0xYY
// cmd pos1 pos2
// Here we assign the byte to the proper place in Status and update portaudio settings
Status.U8[ (m_uPosition - 1) ? 0 : 1] = byte;
if(m_uPosition == 2)
{
Sampling = (Status.sampling == 1) ? true : false;
switch (Status.sRate)
{
case 0:
SFreq = 11025;
break;
case 1:
SFreq = 22050;
break;
case 2:
SFreq = 44100;
break;
default:
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: Trying to set unknown sampling rate");
SFreq = 44100;
break;
}
switch (Status.pLength)
{
case 0:
SNum = 32;
break;
case 1:
SNum = 64;
break;
case 2:
SNum = 128;
break;
default:
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: Trying to set unknown period length");
SNum = 128;
break;
}
DEBUG_LOG(EXPANSIONINTERFACE, "//////////////////////////////////////////////////////////////////////////");
DEBUG_LOG(EXPANSIONINTERFACE, "EXI MIC: Status is now 0x%04x", Status.U16);
DEBUG_LOG(EXPANSIONINTERFACE, "\tbutton %i\tsRate %i\tpLength %i\tsampling %i\n",
Status.button, SFreq, SNum, Status.sampling);
if(!IsOpen)
{
// Open Our PortAudio Stream
// (shuffle2) This (and the callback) could still be wrong
err = Pa_OpenDefaultStream(
&stream, // Our PaStream
1, // Input Channels
0, // Output Channels
paInt16, // Output format - GC wants PCM samples in signed 16-bit format
SFreq, // Sample Rate
SNum, // Period Length (frames per buffer)
patestCallback,// Our callback!
NULL); // Pointer passed to our callback
if (err != paNoError)
{
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: PortAudio error %s", Pa_GetErrorText(err));
}
else
IsOpen = true;
}
}
}
break;
case cmdGetBuffer:
{
int pos = m_uPosition - 1;
// (sonicadvance1)I think if we set the Interrupt to false, it reads another 64
// Will Look in to it.
// (shuffle2)Seems like games just continuously get the buffer as long as
// they're sampling and the mic is generating interrupts
byte = inputData[pos].byte[ (pos & 1) ? 0 : 1 ];
INFO_LOG(EXPANSIONINTERFACE, "EXI MIC: GetBuffer%s%d/%d byte: 0x%02x",
(pos > 9) ? " " : " ", pos, SNum, byte);
}
break;
default:
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: unknown command byte %02x\n", command);
break;
}
m_position++;
return;
}
m_uPosition++;
int pos = m_position - 1;
switch (command)
{
case cmdID:
byte = exi_id[pos];
break;
case cmdGetStatus:
if (pos == 0)
status.button = Pad::GetMicButton(slot);
byte = status.U8[pos ^ 1];
if (pos == 1)
status.buff_ovrflw = 0;
break;
case cmdSetStatus:
{
bool wasactive = status.is_active;
status.U8[pos ^ 1] = byte;
// safe to do since these can only be entered if both bytes of status have been written
if (!wasactive && status.is_active)
{
sample_rate = rate_base << status.sample_rate;
buff_size = ring_base << status.buff_size;
buff_size_samples = buff_size / sample_size;
UpdateNextInterruptTicks();
StreamStart();
}
else if (wasactive && !status.is_active)
{
StreamStop();
}
}
break;
case cmdGetBuffer:
{
if (ring_pos == 0)
StreamReadOne();
byte = ring_buffer[ring_pos ^ 1];
ring_pos = (ring_pos + 1) % buff_size;
}
break;
default:
ERROR_LOG(EXPANSIONINTERFACE, "EXI MIC: unknown command byte %02x", command);
break;
}
m_position++;
}
#endif

View File

@ -18,58 +18,106 @@
#ifndef _EXI_DEVICEMIC_H
#define _EXI_DEVICEMIC_H
#if HAVE_PORTAUDIO
#include "StdMutex.h"
class CEXIMic : public IEXIDevice
{
public:
CEXIMic(int _Index);
CEXIMic(const int index);
virtual ~CEXIMic();
void SetCS(int cs);
void Update();
bool IsInterruptSet();
bool IsPresent();
private:
static u8 const exi_id[];
static int const sample_size = sizeof(s16);
static int const rate_base = 11025;
static int const ring_base = 32;
enum
{
EXI_DEVTYPE_MIC = 0x0A000000
};
enum
{
cmdID = 0x00,
cmdGetStatus = 0x40,
cmdSetStatus = 0x80,
cmdGetBuffer = 0x20,
cmdWakeUp = 0xFF,
cmdReset = 0xFF,
};
// STATE_TO_SAVE
int interruptSwitch;
int slot;
u32 m_position;
int command;
union uStatus
union UStatus
{
u16 U16;
u8 U8[2];
struct
{
u16 :8; // Unknown
u16 button :1; // 1: Button Pressed
u16 unk1 :1; // 1 ? Overflow?
u16 unk2 :1; // Unknown related to 0 and 15 values It seems
u16 sRate :2; // Sample Rate, 00-11025, 01-22050, 10-44100, 11-??
u16 pLength :2; // Period Length, 00-32, 01-64, 10-128, 11-???
u16 sampling :1; // If We Are Sampling or Not
u16 out :4; // MICSet/GetOut...???
u16 id :1; // Used for MICGetDeviceID (always 0)
u16 button_unk :3; // Button bits which appear unused
u16 button :1; // The actual button on the mic
u16 buff_ovrflw :1; // Ring buffer wrote over bytes which weren't read by console
u16 gain :1; // Gain: 0dB or 15dB
u16 sample_rate :2; // Sample rate, 00-11025, 01-22050, 10-44100, 11-??
u16 buff_size :2; // Ring buffer size in bytes, 00-32, 01-64, 10-128, 11-???
u16 is_active :1; // If we are sampling or not
};
};
int Index;
u32 m_uPosition;
uStatus Status;
// 64 is the max size, can be 16 or 32 as well
int ring_pos;
u8 ring_buffer[64 * sample_size];
// 0 to disable interrupts, else it will be checked against current cpu ticks
// to determine if interrupt should be raised
u64 next_int_ticks;
void UpdateNextInterruptTicks();
// Streaming input interface
int pa_error; // PaError
void *pa_stream; // PaStream
void StreamLog(const char *msg);
void StreamInit();
void StreamTerminate();
void StreamStart();
void StreamStop();
void StreamReadOne();
public:
UStatus status;
std::mutex ring_lock;
// status bits converted to nice numbers
int sample_rate;
int buff_size;
int buff_size_samples;
// Arbitrarily small ringbuffer used by audio input backend in order to
// keep delay tolerable
s16 *stream_buffer;
int stream_size;
int stream_wpos;
int stream_rpos;
int samples_avail;
protected:
virtual void TransferByte(u8 &byte);
};
void SetMic(bool Value);
#else // HAVE_PORTAUDIO
class CEXIMic : public IEXIDevice
{
public:
CEXIMic(const int) {}
};
#endif
#endif // _EXI_DEVICEMIC_H

View File

@ -107,4 +107,15 @@ void Rumble(u8 _numPAD, unsigned int _uType, unsigned int _uStrength)
}
}
bool GetMicButton(u8 pad)
{
std::unique_lock<std::recursive_mutex> lk(g_plugin.controls_lock, std::try_to_lock);
if (!lk.owns_lock())
return false;
return ((GCPad*)g_plugin.controllers[pad])->GetMicButton();
}
}

View File

@ -33,6 +33,7 @@ InputPlugin *GetPlugin();
void GetStatus(u8 _numPAD, SPADStatus* _pPADStatus);
void Rumble(u8 _numPAD, unsigned int _uType, unsigned int _uStrength);
bool GetMicButton(u8 pad);
}
#endif

View File

@ -25,7 +25,8 @@ const u16 button_bitmasks[] =
PAD_BUTTON_X,
PAD_BUTTON_Y,
PAD_TRIGGER_Z,
PAD_BUTTON_START
PAD_BUTTON_START,
0 // MIC HAX
};
const u16 trigger_bitmasks[] =
@ -47,6 +48,7 @@ const char* const named_buttons[] =
"Y",
"Z",
_trans("Start"),
"Mic"
};
const char* const named_triggers[] =
@ -63,10 +65,11 @@ const char* const named_triggers[] =
GCPad::GCPad(const unsigned int index) : m_index(index)
{
int const mic_hax = index > 1;
// buttons
groups.push_back(m_buttons = new Buttons(_trans("Buttons")));
for (unsigned int i=0; i < sizeof(named_buttons)/sizeof(*named_buttons); ++i)
for (unsigned int i=0; i < sizeof(named_buttons)/sizeof(*named_buttons) - mic_hax; ++i)
m_buttons->controls.push_back(new ControlGroup::Input(named_buttons[i]));
// sticks
@ -203,3 +206,8 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface)
set_control(m_triggers, 0, "Q"); // L
set_control(m_triggers, 1, "W"); // R
}
bool GCPad::GetMicButton() const
{
return m_buttons->controls.back()->control_ref->State();
}

View File

@ -29,6 +29,8 @@ public:
GCPad(const unsigned int index);
void GetInput(SPADStatus* const pad);
void SetOutput(const bool on);
bool GetMicButton() const;
std::string GetName() const;

View File

@ -239,8 +239,6 @@ bool CSIDevice_GCController::GetData(u32& _Hi, u32& _Low)
}
}
SetMic(PadStatus.MicButton); // This is dumb and should not be here
return true;
}

View File

@ -483,10 +483,8 @@ void PlayController(SPADStatus *PadStatus, int controllerID)
// dtm files don't save the mic button or error bit. not sure if they're actually
// used, but better safe than sorry
bool m = PadStatus->MicButton;
signed char e = PadStatus->err;
memset(PadStatus, 0, sizeof(SPADStatus));
PadStatus->MicButton = m;
PadStatus->err = e;
memcpy(&g_padState, &(tmpInput[inputOffset]), 8);

View File

@ -206,14 +206,11 @@ void Jit64AsmRoutineManager::Generate()
ABI_CallFunction(reinterpret_cast<void *>(&CoreTiming::Advance));
testExceptions = GetCodePtr();
TEST(32, M((void *)&PowerPC::ppcState.Exceptions), Imm32(0xFFFFFFFF));
FixupBranch skipExceptions = J_CC(CC_Z);
MOV(32, R(EAX), M(&PC));
MOV(32, M(&NPC), R(EAX));
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckExceptions));
MOV(32, R(EAX), M(&NPC));
MOV(32, M(&PC), R(EAX));
SetJumpTarget(skipExceptions);
MOV(32, R(EAX), M(&PC));
MOV(32, M(&NPC), R(EAX));
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckExceptions));
MOV(32, R(EAX), M(&NPC));
MOV(32, M(&PC), R(EAX));
TEST(32, M((void*)PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
J_CC(CC_Z, outerLoop, true);

View File

@ -210,14 +210,11 @@ void JitILAsmRoutineManager::Generate()
ABI_CallFunction(reinterpret_cast<void *>(&CoreTiming::Advance));
testExceptions = GetCodePtr();
TEST(32, M((void *)&PowerPC::ppcState.Exceptions), Imm32(0xFFFFFFFF));
FixupBranch skipExceptions = J_CC(CC_Z);
MOV(32, R(EAX), M(&PC));
MOV(32, M(&NPC), R(EAX));
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckExceptions));
MOV(32, R(EAX), M(&NPC));
MOV(32, M(&PC), R(EAX));
SetJumpTarget(skipExceptions);
MOV(32, R(EAX), M(&PC));
MOV(32, M(&NPC), R(EAX));
ABI_CallFunction(reinterpret_cast<void *>(&PowerPC::CheckExceptions));
MOV(32, R(EAX), M(&NPC));
MOV(32, M(&PC), R(EAX));
TEST(32, M((void*)PowerPC::GetStatePtr()), Imm32(0xFFFFFFFF));
J_CC(CC_Z, outerLoop, true);

View File

@ -37,6 +37,7 @@
#include "CPUCoreBase.h"
#include "../Host.h"
#include "HW/EXI.h"
CPUCoreBase *cpu_core_base;
@ -268,10 +269,13 @@ void Stop()
void CheckExceptions()
{
// Make sure we are checking against the latest EXI status. This is required
// for devices which interrupt frequently, such as the gc mic
ExpansionInterface::UpdateInterrupts();
// Read volatile data once
u32 exceptions = ppcState.Exceptions;
// This check is unnecessary in JIT mode. However, it probably doesn't really hurt.
if (!exceptions)
return;