DSP/Audio: First pass at implementing audio. Supports PCM16 only.

This commit is contained in:
MerryMage 2016-01-24 09:58:47 +00:00
parent 2c663fbc3e
commit aa57048a0e
10 changed files with 606 additions and 39 deletions

View File

@ -159,6 +159,12 @@ if (ENABLE_GLFW)
endif() endif()
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) IF (APPLE)
FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
FIND_LIBRARY(IOKIT_LIBRARY IOKit) # GLFW dependency FIND_LIBRARY(IOKIT_LIBRARY IOKit) # GLFW dependency

View File

@ -14,11 +14,13 @@ set(HEADERS
create_directory_groups(${SRCS} ${HEADERS}) create_directory_groups(${SRCS} ${HEADERS})
include_directories(${GLFW_INCLUDE_DIRS}) include_directories(${GLFW_INCLUDE_DIRS})
include_directories(${OPENAL_INCLUDE_DIRS})
link_directories(${GLFW_LIBRARY_DIRS}) link_directories(${GLFW_LIBRARY_DIRS})
link_directories(${OPENAL_LIBRARY_DIRS})
add_executable(citra ${SRCS} ${HEADERS}) add_executable(citra ${SRCS} ${HEADERS})
target_link_libraries(citra core video_core common) 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) if (MSVC)
target_link_libraries(citra getopt) target_link_libraries(citra getopt)
endif() endif()

View File

@ -54,6 +54,7 @@ namespace Log {
SUB(HW, Memory) \ SUB(HW, Memory) \
SUB(HW, LCD) \ SUB(HW, LCD) \
SUB(HW, GPU) \ SUB(HW, GPU) \
CLS(Audio) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Software) \ SUB(Render, Software) \

View File

@ -69,6 +69,7 @@ enum class Class : ClassType {
HW_Memory, ///< Memory-map and address translation HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation HW_GPU, ///< GPU control emulation
Audio, ///< Emulator audio output
Frontend, ///< Emulator UI Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend Render_Software, ///< Software renderer backend

View File

@ -11,6 +11,7 @@ set(SRCS
arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpdouble.cpp
arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpinstr.cpp
arm/skyeye_common/vfp/vfpsingle.cpp arm/skyeye_common/vfp/vfpsingle.cpp
audio/stream.cpp
core.cpp core.cpp
core_timing.cpp core_timing.cpp
file_sys/archive_backend.cpp file_sys/archive_backend.cpp
@ -137,6 +138,7 @@ set(HEADERS
arm/skyeye_common/vfp/asm_vfp.h arm/skyeye_common/vfp/asm_vfp.h
arm/skyeye_common/vfp/vfp.h arm/skyeye_common/vfp/vfp.h
arm/skyeye_common/vfp/vfp_helper.h arm/skyeye_common/vfp/vfp_helper.h
audio/stream.h
core.h core.h
core_timing.h core_timing.h
file_sys/archive_backend.h file_sys/archive_backend.h

186
src/core/audio/stream.cpp Normal file
View File

@ -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 <array>
#include <queue>
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<Buffer> queue;
std::queue<Buffer> 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<bool, u16, u32> 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);
}
};

32
src/core/audio/stream.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "common/common_types.h"
#include <tuple>
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<bool, u16, u32> GetStatus(int chanid);
};

View File

@ -2,30 +2,299 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/bit_field.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/audio/stream.h"
#include "core/core_timing.h"
#include "core/hle/hle.h" #include "core/hle/hle.h"
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/service/dsp_dsp.h" #include "core/hle/service/dsp_dsp.h"
#include <unordered_map>
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace DSP_DSP // Namespace DSP_DSP
namespace DSP_DSP { namespace DSP_DSP {
struct PairHash {
public:
template <typename T, typename U>
std::size_t operator()(const std::pair<T, U> &x) const {
return std::hash<T>()(x.first) ^ std::hash<U>()(x.second);
}
};
static u32 read_pipe_count; static u32 read_pipe_count;
static Kernel::SharedPtr<Kernel::Event> semaphore_event; static Kernel::SharedPtr<Kernel::Event> semaphore_event;
static Kernel::SharedPtr<Kernel::Event> interrupt_event; static u32 semaphore_mask;
void SignalInterrupt() { static std::unordered_map<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, PairHash> interrupt_events;
// 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.
if (interrupt_event != 0) static const u64 frame_tick = 1310252ull;
interrupt_event->Signal(); 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<name>::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 <typename T>
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; chanid<NUM_CHANNELS; chanid++) {
ChannelContext& ctx = channel_contexes[chanid];
ChannelStatus& status0 = channel_status0[chanid];
ChannelStatus& status1 = channel_status1[chanid];
if (ctx.dirty) {
if (TestAndUnsetBit(ctx.dirty, 29)) {
// First time init
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 29");
}
if (TestAndUnsetBit(ctx.dirty, 16)) {
// Is Active?
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 16");
}
if (TestAndUnsetBit(ctx.dirty, 2)) {
// Update ADPCM coefficients
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 2");
AdpcmCoefficients& coeff = channel_adpcm_coeffs[chanid];
}
if (TestAndUnsetBit(ctx.dirty, 17)) {
// Interpolation type
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 17");
}
if (TestAndUnsetBit(ctx.dirty, 18)) {
// Rate
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 18");
}
if (TestAndUnsetBit(ctx.dirty, 22)) {
// IIR
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 22");
}
if (TestAndUnsetBit(ctx.dirty, 28)) {
// Sync count
LOG_WARNING(Service_DSP, "(STUB) Update Sync Count");
status0.sync = ctx.sync;
status1.sync = ctx.sync;
}
if (TestAndUnsetBit(ctx.dirty, 25) | TestAndUnsetBit(ctx.dirty, 26) | TestAndUnsetBit(ctx.dirty, 27)) {
// Mix
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 25/26/27");
}
if (TestAndUnsetBit(ctx.dirty, 4) | TestAndUnsetBit(ctx.dirty, 21) | TestAndUnsetBit(ctx.dirty, 30)) {
// TODO(merry): One of these bits might merely signify an update to the format. Verify this.
// Embedded Buffer Changed
Audio::UpdateFormat(chanid, ctx.mono_or_stereo, ctx.format);
Audio::EnqueueBuffer(chanid, ctx.buffer_id,
Memory::GetPhysicalPointer(ctx.physical_address), ctx.sample_count,
ctx.has_adpcm, ctx.adpcm_ps, ctx.adpcm_yn,
ctx.is_looping);
status0.is_playing |= 0x100; // TODO: This is supposed to flicker on then turn off.
}
if (TestAndUnsetBit(ctx.dirty, 19)) {
// Buffer queue
for (int i = 0; i < 4; i++) {
if (TestAndUnsetBit(ctx.buffers_dirty, i)) {
auto& b = ctx.buffers[i];
Audio::EnqueueBuffer(chanid, b.buffer_id,
Memory::GetPhysicalPointer(b.physical_address), b.sample_count,
b.has_adpcm, b.adpcm_ps, b.adpcm_yn,
b.is_looping);
}
}
if (ctx.buffers_dirty) {
LOG_ERROR(Service_DSP, "Unknown channel buffer dirty bits: 0x%04x", ctx.buffers_dirty);
}
ctx.buffers_dirty = 0;
status0.is_playing |= 0x100; // TODO: This is supposed to flicker on then turn off.
}
if (ctx.dirty) {
LOG_ERROR(Service_DSP, "Unknown channel dirty bits: 0x%08x", ctx.dirty);
}
ctx.dirty = 0;
}
Audio::Tick(chanid);
// Update channel status
bool playing = false;
std::tie(playing, status0.current_buffer_id, status0.buffer_position) = Audio::GetStatus(chanid);
if (playing) {
status0.is_playing |= 1;
} else {
status0.is_playing = 0;
}
status1 = status0;
}
for (auto interrupt_event : interrupt_events)
interrupt_event.second->Signal();
CoreTiming::ScheduleEvent(frame_tick-cycles_late, tick_event, 0);
} }
/** /**
@ -42,9 +311,7 @@ static void ConvertProcessAddressFromDspDram(Service::Interface* self) {
u32 addr = cmd_buff[1]; u32 addr = cmd_buff[1];
cmd_buff[1] = 0; // No error cmd_buff[1] = 0; // No error
cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); cmd_buff[2] = DspAddrToVAddr(BASE_ADDR_0, addr);
LOG_WARNING(Service_DSP, "(STUBBED) called with address 0x%08X", addr);
} }
/** /**
@ -122,8 +389,8 @@ static void FlushDataCache(Service::Interface* self) {
/** /**
* DSP_DSP::RegisterInterruptEvents service function * DSP_DSP::RegisterInterruptEvents service function
* Inputs: * Inputs:
* 1 : Parameter 0 (purpose unknown) * 1 : Interrupt
* 2 : Parameter 1 (purpose unknown) * 2 : Number
* 4 : Interrupt event handle * 4 : Interrupt event handle
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 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) { static void RegisterInterruptEvents(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 param0 = cmd_buff[1]; 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 param1 = cmd_buff[2]; u32 number = cmd_buff[2];
u32 event_handle = cmd_buff[4]; u32 event_handle = cmd_buff[4];
auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); if (!event_handle) {
if (evt != nullptr) { // Unregister the event for this interrupt and number
interrupt_event = evt; interrupt_events.erase(std::make_pair(interrupt, number));
cmd_buff[1] = 0; // No error cmd_buff[1] = RESULT_SUCCESS.raw;
} else { } else {
LOG_ERROR(Service_DSP, "called with invalid handle=%08X", cmd_buff[4]); auto evt = Kernel::g_handle_table.Get<Kernel::Event>(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 // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf
cmd_buff[1] = -1; 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) * 1 : Unknown (observed only half word used)
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 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) { static void SetSemaphore(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
SignalInterrupt();
cmd_buff[1] = 0; // No error cmd_buff[1] = 0; // No error
LOG_WARNING(Service_DSP, "(STUBBED) called"); LOG_WARNING(Service_DSP, "(STUBBED) called");
@ -205,16 +478,35 @@ static void WriteProcessPipe(Service::Interface* self) {
static void ReadPipeIfPossible(Service::Interface* self) { static void ReadPipeIfPossible(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 unk1 = cmd_buff[1]; u32 pipe = cmd_buff[1];
u32 unk2 = cmd_buff[2]; u32 unk2 = cmd_buff[2];
u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size
VAddr addr = cmd_buff[0x41]; 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. // Canned DSP responses that games expect. These were taken from HW by 3dmoo team.
// TODO: Remove this hack :) // 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<u16, 16> canned_read_pipe = {{ static const std::array<u16, 16> canned_read_pipe = {{
0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540, 0x000F,
0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58 DSPADDR0,
DSPADDR1,
DSPADDR2,
DSPADDR3,
DSPADDR4,
DSPADDR5,
DSPADDR6,
DSPADDR7,
DSPADDR8,
DSPADDR9,
DSPADDR10,
DSPADDR11,
DSPADDR12,
DSPADDR13,
DSPADDR14,
}}; }};
u32 initial_size = read_pipe_count; u32 initial_size = read_pipe_count;
@ -232,8 +524,8 @@ static void ReadPipeIfPossible(Service::Interface* self) {
cmd_buff[1] = 0; // No error cmd_buff[1] = 0; // No error
cmd_buff[2] = (read_pipe_count - initial_size) * sizeof(u16); 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", LOG_WARNING(Service_DSP, "(STUBBED) called pipe=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X",
unk1, unk2, size, addr); pipe, unk2, size, addr);
} }
/** /**
@ -248,6 +540,8 @@ static void SetSemaphoreMask(Service::Interface* self) {
u32 mask = cmd_buff[1]; u32 mask = cmd_buff[1];
semaphore_mask = mask;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask); 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"); 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[] = { const Interface::FunctionInfo FunctionTable[] = {
{0x00010040, nullptr, "RecvData"}, {0x00010040, nullptr, "RecvData"},
{0x00020040, nullptr, "RecvDataIsReady"}, {0x00020040, nullptr, "RecvDataIsReady"},
@ -312,15 +646,20 @@ const Interface::FunctionInfo FunctionTable[] = {
Interface::Interface() { Interface::Interface() {
semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event"); semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event");
interrupt_event = nullptr; interrupt_events.clear();
read_pipe_count = 0; read_pipe_count = 0;
Register(FunctionTable); Register(FunctionTable);
tick_event = CoreTiming::RegisterEvent("DSP_DSP::tick_event", AudioTick);
CoreTiming::ScheduleEvent(frame_tick, tick_event, 0);
} }
Interface::~Interface() { Interface::~Interface() {
semaphore_event = nullptr; semaphore_event = nullptr;
interrupt_event = nullptr; interrupt_events.clear();
CoreTiming::UnscheduleEvent(tick_event, 0);
} }
} // namespace } // namespace

View File

@ -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::PDC0);
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); 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 // Check for user input updates
Service::HID::Update(); Service::HID::Update();

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "core/audio/stream.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/system.h" #include "core/system.h"
@ -24,11 +25,13 @@ void Init(EmuWindow* emu_window) {
Kernel::Init(); Kernel::Init();
HLE::Init(); HLE::Init();
VideoCore::Init(emu_window); VideoCore::Init(emu_window);
Audio::Init();
GDBStub::Init(); GDBStub::Init();
} }
void Shutdown() { void Shutdown() {
GDBStub::Shutdown(); GDBStub::Shutdown();
Audio::Shutdown();
VideoCore::Shutdown(); VideoCore::Shutdown();
HLE::Shutdown(); HLE::Shutdown();
Kernel::Shutdown(); Kernel::Shutdown();