diff --git a/CMakeLists.txt b/CMakeLists.txt index b8a981711..59bcb3ad7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,12 @@ if (ENABLE_GLFW) endif() endif() +set(OPENAL_PREFIX "${CMAKE_BINARY_DIR}/externals/openal-soft-1.17.1-bin") +set(OPENAL_INCLUDE_DIRS "${OPENAL_PREFIX}/include" CACHE PATH "Path to OpenAL-Soft headers") +set(OPENAL_LIBRARY_DIRS "${OPENAL_PREFIX}/libs/Win64" CACHE PATH "Path to OpenAL-Soft libraries") +set(OPENAL_LIBRARIES OpenAL32) +include_directories(${OPENAL_INCLUDE_DIRS}) + IF (APPLE) FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related FIND_LIBRARY(IOKIT_LIBRARY IOKit) # GLFW dependency diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index e7f8a17f9..da4dca405 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -14,11 +14,13 @@ set(HEADERS create_directory_groups(${SRCS} ${HEADERS}) include_directories(${GLFW_INCLUDE_DIRS}) +include_directories(${OPENAL_INCLUDE_DIRS}) link_directories(${GLFW_LIBRARY_DIRS}) +link_directories(${OPENAL_LIBRARY_DIRS}) add_executable(citra ${SRCS} ${HEADERS}) target_link_libraries(citra core video_core common) -target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad) +target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad ${OPENAL_LIBRARIES}) if (MSVC) target_link_libraries(citra getopt) endif() diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d186ba8f8..5196b6c80 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -54,6 +54,7 @@ namespace Log { SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ + CLS(Audio) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 2d9323a7b..ff0e12ee1 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -69,6 +69,7 @@ enum class Class : ClassType { HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation + Audio, ///< Emulator audio output Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 861b711c7..f5e8a0f07 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpsingle.cpp + audio/stream.cpp core.cpp core_timing.cpp file_sys/archive_backend.cpp @@ -137,6 +138,7 @@ set(HEADERS arm/skyeye_common/vfp/asm_vfp.h arm/skyeye_common/vfp/vfp.h arm/skyeye_common/vfp/vfp_helper.h + audio/stream.h core.h core_timing.h file_sys/archive_backend.h diff --git a/src/core/audio/stream.cpp b/src/core/audio/stream.cpp new file mode 100644 index 000000000..8dbb83a10 --- /dev/null +++ b/src/core/audio/stream.cpp @@ -0,0 +1,186 @@ + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "common/logging/log.h" + +#include "core/audio/stream.h" + +#include +#include + +namespace Audio { + static const int BASE_SAMPLE_RATE = 22050; + + struct Buffer { + u16 id; + ALuint buffer; + bool is_looping; + + bool operator < (const Buffer& other) const { + if ((other.id - id) > 1000) return true; + if ((id - other.id) > 1000) return false; + return id > other.id; + } + }; + + struct OutputChannel { + ALuint source; + int mono_or_stereo; + Format format; + std::priority_queue queue; + std::queue playing; + u16 last_bufid; + }; + + OutputChannel chans[24]; + + int InitAL(void) + { + ALCdevice *device; + ALCcontext *ctx; + + /* Open and initialize a device with default settings */ + device = alcOpenDevice(NULL); + if (!device) + { + LOG_CRITICAL(Audio, "Could not open a device!"); + return 1; + } + + ctx = alcCreateContext(device, NULL); + if (ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE) + { + if (ctx != NULL) + alcDestroyContext(ctx); + alcCloseDevice(device); + LOG_CRITICAL(Audio, "Could not set a context!"); + return 1; + } + + LOG_INFO(Audio, "Opened \"%s\"", alcGetString(device, ALC_DEVICE_SPECIFIER)); + return 0; + } + + ALuint source, buffer; + ALCint dev_rate; + + void Init() { + InitAL(); + + { + ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); + alcGetIntegerv(device, ALC_FREQUENCY, 1, &dev_rate); + if (alcGetError(device) != ALC_NO_ERROR) LOG_CRITICAL(Audio, "Failed to get device sample rate"); + LOG_INFO(Audio, "Device Frequency: %i", dev_rate); + } + + for (int i = 0; i < 24; i++) { + alGenSources(1, &chans[i].source); + if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to setup sound source"); + } + } + + void Shutdown() {} + + void UpdateFormat(int chanid, int mono_or_stereo, Format format) { + chans[chanid].mono_or_stereo = mono_or_stereo; + chans[chanid].format = format; + + LOG_WARNING(Audio, "(STUB)"); + } + + void EnqueueBuffer(int chanid, u16 buffer_id, + void* data, int sample_count, + bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2], + bool is_looping) { + + if (chans[chanid].format != FORMAT_PCM16) { + LOG_ERROR(Audio, "Unimplemented format"); + return; + } + + // TODO: ADPCM processing should happen here + + ALuint b; + alGenBuffers(1, &b); + alBufferData(b, AL_FORMAT_MONO16, data, sample_count*2, BASE_SAMPLE_RATE); + if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer"); + + chans[chanid].queue.emplace( Buffer { buffer_id, b, is_looping }); + } + + void Tick(int chanid) { + auto& c = chans[chanid]; + + if (!c.queue.empty()) { + while (!c.queue.empty()) { + alSourceQueueBuffers(c.source, 1, &c.queue.top().buffer); + if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to enqueue buffer"); + c.playing.emplace(c.queue.top()); + LOG_INFO(Audio, "Enqueued buffer id %i", c.queue.top().id); + c.queue.pop(); + } + + ALint state; + alGetSourcei(c.source, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) { + alSourcePlay(c.source); + } + } + + if (!c.playing.empty()) { + c.last_bufid = c.playing.front().id; + } + + ALint processed; + alGetSourcei(c.source, AL_BUFFERS_PROCESSED, &processed); + while (processed > 0) { + ALuint buf; + alSourceUnqueueBuffers(c.source, 1, &buf); + processed--; + + LOG_INFO(Audio, "Finished buffer id %i", c.playing.front().id); + + while (!c.playing.empty() && c.playing.front().buffer != buf) { + c.playing.pop(); + LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Desynced queue.)"); + } + + if (!c.playing.empty()) { + c.last_bufid = c.playing.front().id; + c.playing.pop(); + } else { + LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Empty queue.)"); + } + + alDeleteBuffers(1, &buf); + } + + if (!c.playing.empty()) { + c.last_bufid = c.playing.front().id; + } + } + + std::tuple GetStatus(int chanid) { + auto& c = chans[chanid]; + + bool isplaying = false; + u16 bufid = 0; + u32 pos = 0; + + ALint state, samples; + alGetSourcei(c.source, AL_SOURCE_STATE, &state); + alGetSourcei(c.source, AL_SAMPLE_OFFSET, &samples); + + if (state == AL_PLAYING) isplaying = true; + + bufid = c.last_bufid; + + pos = samples; + + return std::make_tuple(isplaying, bufid, pos); + } + +}; \ No newline at end of file diff --git a/src/core/audio/stream.h b/src/core/audio/stream.h new file mode 100644 index 000000000..341377b2f --- /dev/null +++ b/src/core/audio/stream.h @@ -0,0 +1,32 @@ +#pragma once + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "common/common_types.h" + +#include + +namespace Audio { + void Init(); + void Play(void* buf, size_t size); + void Shutdown(); + + enum Format : u16 { + FORMAT_PCM8 = 0, + FORMAT_PCM16 = 1, + FORMAT_ADPCM = 2 + }; + + void UpdateFormat(int chanid, int mono_or_stereo, Format format); + + void EnqueueBuffer(int chanid, u16 buffer_id, + void* data, int sample_count, + bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2], + bool is_looping); + + void Tick(int chanid); + + std::tuple GetStatus(int chanid); +}; \ No newline at end of file diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp index d6b8d1318..88b117c21 100644 --- a/src/core/hle/service/dsp_dsp.cpp +++ b/src/core/hle/service/dsp_dsp.cpp @@ -2,30 +2,299 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/bit_field.h" #include "common/logging/log.h" +#include "core/audio/stream.h" +#include "core/core_timing.h" #include "core/hle/hle.h" #include "core/hle/kernel/event.h" #include "core/hle/service/dsp_dsp.h" +#include + //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace DSP_DSP namespace DSP_DSP { +struct PairHash { +public: + template + std::size_t operator()(const std::pair &x) const { + return std::hash()(x.first) ^ std::hash()(x.second); + } +}; + static u32 read_pipe_count; + static Kernel::SharedPtr semaphore_event; -static Kernel::SharedPtr interrupt_event; +static u32 semaphore_mask; -void SignalInterrupt() { - // TODO(bunnei): This is just a stub, it does not do anything other than signal to the emulated - // application that a DSP interrupt occurred, without specifying which one. Since we do not - // emulate the DSP yet (and how it works is largely unknown), this is a work around to get games - // that check the DSP interrupt signal event to run. We should figure out the different types of - // DSP interrupts, and trigger them at the appropriate times. +static std::unordered_map, Kernel::SharedPtr, PairHash> interrupt_events; - if (interrupt_event != 0) - interrupt_event->Signal(); +static const u64 frame_tick = 1310252ull; +static int tick_event; + +// Addresses of various things +static const VAddr BASE_ADDR_0 = Memory::DSP_RAM_VADDR + 0x40000; +static const VAddr BASE_ADDR_1 = Memory::DSP_RAM_VADDR + 0x60000; +static constexpr VAddr DspAddrToVAddr(VAddr base, u32 dsp_addr) { + return (VAddr(dsp_addr) << 1) + base; +} +static const u32 DSPADDR0 = 0xBFFF; // Frame Counter +static const u32 DSPADDR1 = 0x9E92; // Channel Context (x24) +static const u32 DSPADDR2 = 0x8680; // Channel Status (x24) +static const u32 DSPADDR3 = 0xA792; // ADPCM Coefficients (x24) +static const u32 DSPADDR4 = 0x9430; // Context +static const u32 DSPADDR5 = 0x8400; // Status +static const u32 DSPADDR6 = 0x8540; // Loopback Samples +static const u32 DSPADDR7 = 0x9494; +static const u32 DSPADDR8 = 0x8710; +static const u32 DSPADDR9 = 0x8410; // ??? +static const u32 DSPADDR10 = 0xA912; +static const u32 DSPADDR11 = 0xAA12; +static const u32 DSPADDR12 = 0xAAD2; +static const u32 DSPADDR13 = 0xAC52; +static const u32 DSPADDR14 = 0xAC5C; +static const u32 DSPADDR_frame_counter = DSPADDR0; + +static const int NUM_CHANNELS = 24; + +/** + * DSP_DSP::DspEndian + * Care must be taken when reading/writing 32-bit values. The DSP has a 16-bit wordsize and is big-endian. + * The bytes in each word when viewed from the ARM11, however, are in little-endian. + * Thus we have what appears to be a middle-endian encoding. + * + * The below function is its own inverse. + */ +struct dsp_u32 { + static constexpr u32 Convert(u32 value) { + return ((value & 0x0000FFFF) << 16) | ((value & 0xFFFF0000) >> 16); + } + operator u32() { + return Convert(value); + } + void operator=(u32 newvalue) { + value = Convert(newvalue); + } +private: + u32 value; +}; + +#define INSERT_PADDING_DSPWORDS(num_words) u16 CONCAT2(pad, __LINE__)[(num_words)] +#define ASSERT_STRUCT(name, size) \ + static_assert(std::is_standard_layout::value, "Structure doesn't use standard layout"); \ + static_assert(sizeof(name) == (size), "Unexpected struct size") + +/* + * ADPCM seems to be the usual Nintendo format. + * ps = predictor / scaler + * yn[0,1] = sample history + * Coefficients are found at DSPADDR3 + */ + +struct Buffer { + dsp_u32 physical_address; + dsp_u32 sample_count; + u16 adpcm_ps; + s16 adpcm_yn[2]; + u8 has_adpcm; + u8 is_looping; + u16 buffer_id; + INSERT_PADDING_DSPWORDS(1); +}; + +// Userland mainly controls the values in this structure +struct ChannelContext { + u32 dirty; + + // Effects + INSERT_PADDING_DSPWORDS(35); + + // Buffer Queue + u16 buffers_dirty; //< Which of those queued buffers is dirty (bit i == buffers[i]) + Buffer buffers[4]; //< Queued Buffers + + INSERT_PADDING_DSPWORDS(2); + u16 is_active; //< Lower 8 bits == 0x01 if true. + u16 sync; + INSERT_PADDING_DSPWORDS(4); + + // Current Buffer + dsp_u32 physical_address; + dsp_u32 sample_count; + union { + BitField<0, 2, u16> mono_or_stereo; + BitField<2, 2, Audio::Format> format; + }; + u16 adpcm_ps; + s16 adpcm_yn[2]; + union { + BitField<0, 1, u16> has_adpcm; + BitField<1, 1, u16> is_looping; + }; + u16 buffer_id; +}; +ASSERT_STRUCT(ChannelContext, 192); + +// The DSP controls the values in this structure +struct ChannelStatus { + u16 is_playing; + u16 sync; + dsp_u32 buffer_position; + u16 current_buffer_id; + u16 previous_buffer_id; +}; +ASSERT_STRUCT(ChannelStatus, 12); + +struct AdpcmCoefficients { + u16 coeff[16]; +}; +ASSERT_STRUCT(AdpcmCoefficients, 32); + +template +static inline bool TestAndUnsetBit(T& value, size_t bitno) { + T mask = 1 << bitno; + bool ret = (value & mask) == mask; + value &= ~mask; + return ret; +} + +static void AudioTick(u64, int cycles_late) { + VAddr current_base; + + { + int id0 = (int)Memory::Read16(DspAddrToVAddr(BASE_ADDR_0, DSPADDR_frame_counter)); + int id1 = (int)Memory::Read16(DspAddrToVAddr(BASE_ADDR_1, DSPADDR_frame_counter)); + + // The frame id increments once per audio frame, with wraparound at 65,535. + // I am uncertain whether the real DSP actually does something like this, + // or merely checks for a certan id for wraparound. TODO: Verify. + if (id1 - id0 > 10000 && id0 < 10) { + current_base = BASE_ADDR_0; + } else if (id0 - id1 > 10000 && id1 < 10) { + current_base = BASE_ADDR_1; + } else if (id1 > id0) { + current_base = BASE_ADDR_1; + } else { + current_base = BASE_ADDR_0; + } + } + + auto channel_contexes = (ChannelContext*) Memory::GetPointer(DspAddrToVAddr(current_base, DSPADDR1)); + auto channel_status0 = (ChannelStatus*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_0, DSPADDR2)); + auto channel_status1 = (ChannelStatus*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_1, DSPADDR2)); + auto channel_adpcm_coeffs = (AdpcmCoefficients*) Memory::GetPointer(DspAddrToVAddr(current_base, DSPADDR3)); + + for (int chanid=0; chanidSignal(); + + CoreTiming::ScheduleEvent(frame_tick-cycles_late, tick_event, 0); } /** @@ -42,9 +311,7 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) { u32 addr = cmd_buff[1]; cmd_buff[1] = 0; // No error - cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); - - LOG_WARNING(Service_DSP, "(STUBBED) called with address 0x%08X", addr); + cmd_buff[2] = DspAddrToVAddr(BASE_ADDR_0, addr); } /** @@ -122,8 +389,8 @@ static void FlushDataCache(Service::Interface* self) { /** * DSP_DSP::RegisterInterruptEvents service function * Inputs: - * 1 : Parameter 0 (purpose unknown) - * 2 : Parameter 1 (purpose unknown) + * 1 : Interrupt + * 2 : Number * 4 : Interrupt event handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code @@ -131,22 +398,28 @@ static void FlushDataCache(Service::Interface* self) { static void RegisterInterruptEvents(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 param0 = cmd_buff[1]; - u32 param1 = cmd_buff[2]; + u32 interrupt = cmd_buff[1]; // TODO(merry): Confirm the purpose of each interrupt. Presumably there would be one interrupt that would allow for ARM11 modification of the output. + u32 number = cmd_buff[2]; u32 event_handle = cmd_buff[4]; - auto evt = Kernel::g_handle_table.Get(cmd_buff[4]); - if (evt != nullptr) { - interrupt_event = evt; - cmd_buff[1] = 0; // No error + if (!event_handle) { + // Unregister the event for this interrupt and number + interrupt_events.erase(std::make_pair(interrupt, number)); + cmd_buff[1] = RESULT_SUCCESS.raw; } else { - LOG_ERROR(Service_DSP, "called with invalid handle=%08X", cmd_buff[4]); + auto evt = Kernel::g_handle_table.Get(event_handle); + if (evt != nullptr) { + interrupt_events[std::make_pair(interrupt, number)] = evt; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + } else { + LOG_ERROR(Service_DSP, "called with invalid handle=%08X", event_handle); - // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf - cmd_buff[1] = -1; + // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf + cmd_buff[1] = -1; + } } - LOG_WARNING(Service_DSP, "(STUBBED) called param0=%u, param1=%u, event_handle=0x%08X", param0, param1, event_handle); + LOG_WARNING(Service_DSP, "(STUBBED) called interrupt=%u, number=%u, event_handle=0x%08X", interrupt, number, event_handle); } /** @@ -155,12 +428,12 @@ static void RegisterInterruptEvents(Service::Interface* self) { * 1 : Unknown (observed only half word used) * Outputs: * 1 : Result of function, 0 on success, otherwise error code + * Notes: + * Games do not seem to rely on the DSP semaphore very much */ static void SetSemaphore(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - SignalInterrupt(); - cmd_buff[1] = 0; // No error LOG_WARNING(Service_DSP, "(STUBBED) called"); @@ -205,16 +478,35 @@ static void WriteProcessPipe(Service::Interface* self) { static void ReadPipeIfPossible(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 unk1 = cmd_buff[1]; + u32 pipe = cmd_buff[1]; u32 unk2 = cmd_buff[2]; u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size VAddr addr = cmd_buff[0x41]; + if (pipe != 2) { + LOG_ERROR(Service_DSP, "I'm not sure what to do when pipe=0x%08x\n", pipe); + } + // Canned DSP responses that games expect. These were taken from HW by 3dmoo team. // TODO: Remove this hack :) + // FIXME(merry): Incorrect behaviour; the read buffer isn't a single stream, nor does it behave like a stream. static const std::array canned_read_pipe = {{ - 0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540, - 0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58 + 0x000F, + DSPADDR0, + DSPADDR1, + DSPADDR2, + DSPADDR3, + DSPADDR4, + DSPADDR5, + DSPADDR6, + DSPADDR7, + DSPADDR8, + DSPADDR9, + DSPADDR10, + DSPADDR11, + DSPADDR12, + DSPADDR13, + DSPADDR14, }}; u32 initial_size = read_pipe_count; @@ -232,8 +524,8 @@ static void ReadPipeIfPossible(Service::Interface* self) { cmd_buff[1] = 0; // No error cmd_buff[2] = (read_pipe_count - initial_size) * sizeof(u16); - LOG_WARNING(Service_DSP, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", - unk1, unk2, size, addr); + LOG_WARNING(Service_DSP, "(STUBBED) called pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", + pipe, unk2, size, addr); } /** @@ -248,6 +540,8 @@ static void SetSemaphoreMask(Service::Interface* self) { u32 mask = cmd_buff[1]; + semaphore_mask = mask; + cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask); @@ -271,6 +565,46 @@ static void GetHeadphoneStatus(Service::Interface* self) { LOG_DEBUG(Service_DSP, "(STUBBED) called"); } +/** +* DSP_DSP::RecvData service function +* Inputs: +* 1 : Register Number +* Outputs: +* 1 : Result of function, 0 on success, otherwise error code +* 2 : Value in the register +*/ +static void RecvData(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 registerNo = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 1; + + LOG_WARNING(Service_DSP, "(STUBBED) called register=%u", registerNo); +} + +/** +* DSP_DSP::RecvDataIsReady service function +* Inputs: +* 1 : Register Number +* Outputs: +* 1 : Result of function, 0 on success, otherwise error code +* 2 : non-zero == ready +* Notes: +* Seems to be mainly called when going into sleep mode. +*/ +static void RecvDataIsReady(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u32 registerNo = cmd_buff[1]; + + cmd_buff[1] = RESULT_SUCCESS.raw; // No error + cmd_buff[2] = 1; + + LOG_WARNING(Service_DSP, "(STUBBED) called register=%u", registerNo); +} + const Interface::FunctionInfo FunctionTable[] = { {0x00010040, nullptr, "RecvData"}, {0x00020040, nullptr, "RecvDataIsReady"}, @@ -312,15 +646,20 @@ const Interface::FunctionInfo FunctionTable[] = { Interface::Interface() { semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event"); - interrupt_event = nullptr; + interrupt_events.clear(); read_pipe_count = 0; Register(FunctionTable); + + tick_event = CoreTiming::RegisterEvent("DSP_DSP::tick_event", AudioTick); + CoreTiming::ScheduleEvent(frame_tick, tick_event, 0); } Interface::~Interface() { semaphore_event = nullptr; - interrupt_event = nullptr; + interrupt_events.clear(); + + CoreTiming::UnscheduleEvent(tick_event, 0); } } // namespace diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 4bd3a632d..a180401d8 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -414,11 +414,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) { GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); - // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but - // until we can emulate DSP interrupts, this is probably the only reasonable place to do - // this. Certain games expect this to be periodically signaled. - DSP_DSP::SignalInterrupt(); - // Check for user input updates Service::HID::Update(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 7e9c56538..2c9f964de 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/audio/stream.h" #include "core/core.h" #include "core/core_timing.h" #include "core/system.h" @@ -24,11 +25,13 @@ void Init(EmuWindow* emu_window) { Kernel::Init(); HLE::Init(); VideoCore::Init(emu_window); + Audio::Init(); GDBStub::Init(); } void Shutdown() { GDBStub::Shutdown(); + Audio::Shutdown(); VideoCore::Shutdown(); HLE::Shutdown(); Kernel::Shutdown();