DSP/Audio: First pass at implementing audio. Supports PCM16 only.
This commit is contained in:
parent
7468a88671
commit
d3a48335b8
10 changed files with 606 additions and 39 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace Log {
|
|||
SUB(HW, Memory) \
|
||||
SUB(HW, LCD) \
|
||||
SUB(HW, GPU) \
|
||||
CLS(Audio) \
|
||||
CLS(Frontend) \
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
186
src/core/audio/stream.cpp
Normal file
186
src/core/audio/stream.cpp
Normal 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
32
src/core/audio/stream.h
Normal 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);
|
||||
};
|
|
@ -2,29 +2,298 @@
|
|||
// 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/kernel/event.h"
|
||||
#include "core/hle/service/dsp_dsp.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 Kernel::SharedPtr<Kernel::Event> semaphore_event;
|
||||
static Kernel::SharedPtr<Kernel::Event> 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<std::pair<u32, u32>, Kernel::SharedPtr<Kernel::Event>, 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<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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,9 +310,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,8 +388,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
|
||||
|
@ -130,22 +397,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<Kernel::Event>(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<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
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,12 +427,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");
|
||||
|
@ -204,16 +477,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<u16, 16> 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;
|
||||
|
@ -231,8 +523,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,6 +539,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);
|
||||
|
@ -270,6 +564,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"},
|
||||
|
@ -311,15 +645,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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue