From 5dc866bfc9c178b1a490012d04f9ef3d499a8d76 Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Mon, 3 Oct 2011 23:30:29 -0700 Subject: [PATCH] Fix gamecube microphone (button not implemented, yet :p). Calls ExpansionInterface::UpdateInterrupts just before checking exceptions now. --- Source/Core/Core/Src/HW/EXI_Device.cpp | 2 +- Source/Core/Core/Src/HW/EXI_DeviceMic.cpp | 378 ++++++++---------- Source/Core/Core/Src/HW/EXI_DeviceMic.h | 74 +++- Source/Core/Core/Src/PowerPC/Jit64/JitAsm.cpp | 13 +- Source/Core/Core/Src/PowerPC/PowerPC.cpp | 5 + 5 files changed, 226 insertions(+), 246 deletions(-) diff --git a/Source/Core/Core/Src/HW/EXI_Device.cpp b/Source/Core/Core/Src/HW/EXI_Device.cpp index fc73f4e0d8..8660d84720 100644 --- a/Source/Core/Core/Src/HW/EXI_Device.cpp +++ b/Source/Core/Core/Src/HW/EXI_Device.cpp @@ -127,7 +127,7 @@ IEXIDevice* EXIDevice_Create(TEXIDevices _EXIDevice) break; case EXIDEVICE_MIC: - return new CEXIMic(1); + return new CEXIMic(); break; case EXIDEVICE_ETH: diff --git a/Source/Core/Core/Src/HW/EXI_DeviceMic.cpp b/Source/Core/Core/Src/HW/EXI_DeviceMic.cpp index 5979463176..4bafd51de6 100644 --- a/Source/Core/Core/Src/HW/EXI_DeviceMic.cpp +++ b/Source/Core/Core/Src/HW/EXI_DeviceMic.cpp @@ -16,115 +16,131 @@ // http://code.google.com/p/dolphin-emu/ #include "Common.h" + +#if HAVE_PORTAUDIO + #include "FileUtil.h" #include "StringUtil.h" #include "../Core.h" #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 -#ifdef _WIN32 -#pragma comment(lib, "C:/Users/Shawn/Desktop/portaudio/portaudio-v19/portaudio_x64.lib") -#endif +static bool pa_init = false; -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; - - if (!m_bInterruptSet && Sampling) + // Setup the wonderful c-interfaced lib... + pa_error = paNoError; + if (!pa_init) { - for(unsigned int i = 0; i < SNum; ++i) + pa_error = Pa_Initialize(); + + if (pa_error != paNoError) { - inputData[i].word = data[i]; - //out[i] = inputData[i].word; + StreamLog("Pa_Initialize"); } - m_bInterruptSet = true; + else + pa_init = true; } - return paContinue; + + mic_count++; } +void CEXIMic::StreamTerminate() +{ + // TODO keep track of number of mics... + if (pa_init && --mic_count <= 0) + pa_error = Pa_Terminate(); + + if (pa_error != paNoError) + { + StreamLog("Pa_Terminate"); + } + else + pa_init = false; +} + +void CEXIMic::StreamStart() +{ + // Open stream with current parameters + if (pa_init) + { + pa_error = Pa_OpenDefaultStream(&pa_stream, 1, 0, paInt16, + sample_rate, buff_size_samples, NULL, NULL); + StreamLog("Pa_OpenDefaultStream"); + pa_error = Pa_StartStream(pa_stream); + StreamLog("Pa_StartStream"); + } +} + +void CEXIMic::StreamStop() +{ + // Acts as if Pa_AbortStream was called + pa_error = Pa_CloseStream(pa_stream); + StreamLog("Pa_CloseStream"); +} + +void CEXIMic::StreamReadOne() +{ + // Returns num samples or error + pa_error = Pa_GetStreamReadAvailable(pa_stream); + if (pa_error >= buff_size_samples) + { + pa_error = Pa_ReadStream(pa_stream, ring_buffer, buff_size_samples); + + if (pa_error != paNoError) + { + status.buff_ovrflw = 1; + // Input overflowed - is re-setting the stream the only to recover? + StreamLog("Pa_ReadStream"); + + StreamStop(); + StreamStart(); + } + } +} // 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 portaudio fills +// in the background (ie on demand instead of realtime). Because of this we need +// to clear portaudio's buffer if emulation speed drops below realtime, or else +// a bad audio lag develops. It's actually kind of convenient because it gives +// us a way to detect if buff_ovrflw should be set. -CEXIMic::CEXIMic(int _Index) +u8 const CEXIMic::exi_id[] = { 0, 0x0a, 0, 0, 0 }; +int CEXIMic::mic_count = 0; + +CEXIMic::CEXIMic() { - Index = _Index; - - memset(&inputData, 0 , sizeof(inputData)); - memset(&Status.U16, 0 , sizeof(u16)); + status.U16 = 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)); + m_position = 0; + ring_pos = 0; + 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 +148,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 +178,66 @@ 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: + byte = status.U8[pos ^ 1]; + if (pos == 1 && status.buff_ovrflw) + 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) + { + // Can set buff_ovrflw + 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 diff --git a/Source/Core/Core/Src/HW/EXI_DeviceMic.h b/Source/Core/Core/Src/HW/EXI_DeviceMic.h index 3fa463e722..e87d39f1a5 100644 --- a/Source/Core/Core/Src/HW/EXI_DeviceMic.h +++ b/Source/Core/Core/Src/HW/EXI_DeviceMic.h @@ -18,58 +18,90 @@ #ifndef _EXI_DEVICEMIC_H #define _EXI_DEVICEMIC_H +#if HAVE_PORTAUDIO + class CEXIMic : public IEXIDevice { public: - CEXIMic(int _Index); + CEXIMic(); virtual ~CEXIMic(); void SetCS(int cs); - void Update(); bool IsInterruptSet(); bool IsPresent(); private: + static int mic_count; + 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; + 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 button :5; // Buttons. Top bit is mic button. Lowest bit is used for MICGetDeviceID (always 0) + 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; + UStatus status; + + // status bits converted to nice numbers + int sample_rate; + int buff_size; + int buff_size_samples; + + // 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(); protected: virtual void TransferByte(u8 &byte); }; -void SetMic(bool Value); +#else // HAVE_PORTAUDIO + +class CEXIMic : public IEXIDevice +{ +public: + CEXIMic() {} +}; #endif + +#endif // _EXI_DEVICEMIC_H diff --git a/Source/Core/Core/Src/PowerPC/Jit64/JitAsm.cpp b/Source/Core/Core/Src/PowerPC/Jit64/JitAsm.cpp index 4515cd5a79..b9aaa2df6a 100644 --- a/Source/Core/Core/Src/PowerPC/Jit64/JitAsm.cpp +++ b/Source/Core/Core/Src/PowerPC/Jit64/JitAsm.cpp @@ -206,14 +206,11 @@ void Jit64AsmRoutineManager::Generate() ABI_CallFunction(reinterpret_cast(&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(&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(&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); diff --git a/Source/Core/Core/Src/PowerPC/PowerPC.cpp b/Source/Core/Core/Src/PowerPC/PowerPC.cpp index 567f3ff98e..590881bb96 100644 --- a/Source/Core/Core/Src/PowerPC/PowerPC.cpp +++ b/Source/Core/Core/Src/PowerPC/PowerPC.cpp @@ -37,6 +37,7 @@ #include "CPUCoreBase.h" #include "../Host.h" +#include "HW/EXI.h" CPUCoreBase *cpu_core_base; @@ -268,6 +269,10 @@ 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;