commit
cae913d3d5
@ -10,13 +10,189 @@
|
||||
|
||||
namespace Service::CSND {
|
||||
|
||||
enum class CommandId : u16 {
|
||||
Start = 0x000,
|
||||
Pause = 0x001,
|
||||
SetEncoding = 0x002,
|
||||
SetSecondBlock = 0x003,
|
||||
SetLoopMode = 0x004,
|
||||
// unknown = 0x005,
|
||||
SetLinearInterpolation = 0x006,
|
||||
SetPsgDuty = 0x007,
|
||||
SetSampleRate = 0x008,
|
||||
SetVolume = 0x009,
|
||||
SetFirstBlock = 0x00A,
|
||||
SetFirstBlockAdpcmState = 0x00B,
|
||||
SetSecondBlockAdpcmState = 0x00C,
|
||||
SetSecondBlockAdpcmReload = 0x00D,
|
||||
ConfigureChannel = 0x00E,
|
||||
ConfigurePsg = 0x00F,
|
||||
ConfigurePsgNoise = 0x010,
|
||||
// 0x10x commands are audio capture related
|
||||
// unknown = 0x200
|
||||
UpdateState = 0x300,
|
||||
};
|
||||
|
||||
struct Type0Command {
|
||||
u16_le next_command_offset;
|
||||
enum_le<CommandId> command_id;
|
||||
u8 finished;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
union {
|
||||
struct {
|
||||
u32_le channel;
|
||||
u32_le value;
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
} start;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u32_le value;
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
} pause;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
Encoding value;
|
||||
INSERT_PADDING_BYTES(0x13);
|
||||
} set_encoding;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
LoopMode value;
|
||||
INSERT_PADDING_BYTES(0x13);
|
||||
} set_loop_mode;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u32_le value;
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
} set_linear_interpolation;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u8 value;
|
||||
INSERT_PADDING_BYTES(0x13);
|
||||
} set_psg_duty;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u32_le value;
|
||||
INSERT_PADDING_BYTES(0x10);
|
||||
} set_sample_rate;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u16_le left_channel_volume;
|
||||
u16_le right_channel_volume;
|
||||
u16_le left_capture_volume;
|
||||
u16_le right_capture_volume;
|
||||
INSERT_PADDING_BYTES(0xC);
|
||||
} set_volume;
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u32_le address;
|
||||
u32_le size;
|
||||
INSERT_PADDING_BYTES(0xC);
|
||||
} set_block; // for either first block or second block
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
s16_le predictor;
|
||||
u8 step_index;
|
||||
INSERT_PADDING_BYTES(0x11);
|
||||
} set_adpcm_state; // for either first block or second block
|
||||
|
||||
struct {
|
||||
u32_le channel;
|
||||
u8 value;
|
||||
INSERT_PADDING_BYTES(0x13);
|
||||
} set_second_block_adpcm_reload;
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 6, u32> channel;
|
||||
BitField<6, 1, u32> linear_interpolation;
|
||||
|
||||
BitField<10, 2, u32> loop_mode;
|
||||
BitField<12, 2, u32> encoding;
|
||||
BitField<14, 1, u32> enable_playback;
|
||||
|
||||
BitField<16, 16, u32> sample_rate;
|
||||
};
|
||||
|
||||
u16_le left_channel_volume;
|
||||
u16_le right_channel_volume;
|
||||
u16_le left_capture_volume;
|
||||
u16_le right_capture_volume;
|
||||
u32_le block1_address;
|
||||
u32_le block2_address;
|
||||
u32_le size;
|
||||
} configure_channel;
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 6, u32> channel;
|
||||
BitField<14, 1, u32> enable_playback;
|
||||
BitField<16, 16, u32> sample_rate;
|
||||
};
|
||||
u16_le left_channel_volume;
|
||||
u16_le right_channel_volume;
|
||||
u16_le left_capture_volume;
|
||||
u16_le right_capture_volume;
|
||||
u32_le duty;
|
||||
INSERT_PADDING_BYTES(0x8);
|
||||
} configure_psg;
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 6, u32> channel;
|
||||
BitField<14, 1, u32> enable_playback;
|
||||
};
|
||||
u16_le left_channel_volume;
|
||||
u16_le right_channel_volume;
|
||||
u16_le left_capture_volume;
|
||||
u16_le right_capture_volume;
|
||||
INSERT_PADDING_BYTES(0xC);
|
||||
} configure_psg_noise;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Type0Command) == 0x20, "Type0Command structure size is wrong");
|
||||
|
||||
struct MasterState {
|
||||
u32_le unknown_channel_flag;
|
||||
u32_le unknown;
|
||||
};
|
||||
static_assert(sizeof(MasterState) == 0x8, "MasterState structure size is wrong");
|
||||
|
||||
struct ChannelState {
|
||||
u8 active;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
s16_le adpcm_predictor;
|
||||
u8 adpcm_step_index;
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
|
||||
// 3dbrew says this is the current physical address. However the assembly of CSND module
|
||||
// from 11.3 system shows this is simply assigned as 0, which is also documented on ctrulib.
|
||||
u32_le zero;
|
||||
};
|
||||
static_assert(sizeof(ChannelState) == 0xC, "ChannelState structure size is wrong");
|
||||
|
||||
struct CaptureState {
|
||||
u8 active;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
u32_le zero;
|
||||
};
|
||||
static_assert(sizeof(CaptureState) == 0x8, "CaptureState structure size is wrong");
|
||||
|
||||
void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x01, 5, 0);
|
||||
const u32 size = Common::AlignUp(rp.Pop<u32>(), Memory::PAGE_SIZE);
|
||||
const u32 offset0 = rp.Pop<u32>();
|
||||
const u32 offset1 = rp.Pop<u32>();
|
||||
const u32 offset2 = rp.Pop<u32>();
|
||||
const u32 offset3 = rp.Pop<u32>();
|
||||
master_state_offset = rp.Pop<u32>();
|
||||
channel_state_offset = rp.Pop<u32>();
|
||||
capture_state_offset = rp.Pop<u32>();
|
||||
type1_command_offset = rp.Pop<u32>();
|
||||
|
||||
using Kernel::MemoryPermission;
|
||||
mutex = system.Kernel().CreateMutex(false, "CSND:mutex");
|
||||
@ -32,8 +208,10 @@ void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
LOG_WARNING(Service_CSND,
|
||||
"(STUBBED) called, size=0x{:08X} "
|
||||
"offset0=0x{:08X} offset1=0x{:08X} offset2=0x{:08X} offset3=0x{:08X}",
|
||||
size, offset0, offset1, offset2, offset3);
|
||||
"master_state_offset=0x{:08X} channel_state_offset=0x{:08X} "
|
||||
"capture_state_offset=0x{:08X} type1_command_offset=0x{:08X}",
|
||||
size, master_state_offset, channel_state_offset, capture_state_offset,
|
||||
type1_command_offset);
|
||||
}
|
||||
|
||||
void CSND_SND::Shutdown(Kernel::HLERequestContext& ctx) {
|
||||
@ -53,31 +231,178 @@ void CSND_SND::Shutdown(Kernel::HLERequestContext& ctx) {
|
||||
void CSND_SND::ExecuteCommands(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x03, 1, 0);
|
||||
const u32 addr = rp.Pop<u32>();
|
||||
LOG_WARNING(Service_CSND, "(STUBBED) called, addr=0x{:08X}", addr);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
if (!shared_memory) {
|
||||
rb.Push<u32>(1);
|
||||
rb.Push(ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CSND,
|
||||
ErrorSummary::InvalidState, ErrorLevel::Status));
|
||||
LOG_ERROR(Service_CSND, "called, shared memory not allocated");
|
||||
} else {
|
||||
u8* ptr = shared_memory->GetPointer(addr);
|
||||
Type0Command command;
|
||||
|
||||
std::memcpy(&command, ptr, sizeof(Type0Command));
|
||||
command.finished |= 1;
|
||||
std::memcpy(ptr, &command, sizeof(Type0Command));
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_CSND, "(STUBBED) called, addr=0x{:08X}", addr);
|
||||
u32 offset = addr;
|
||||
while (offset != 0xFFFF) {
|
||||
Type0Command command;
|
||||
u8* ptr = shared_memory->GetPointer(offset);
|
||||
std::memcpy(&command, ptr, sizeof(Type0Command));
|
||||
offset = command.next_command_offset;
|
||||
|
||||
switch (command.command_id) {
|
||||
case CommandId::Start:
|
||||
// TODO: start/stop the sound
|
||||
break;
|
||||
case CommandId::Pause:
|
||||
// TODO: pause/resume the sound
|
||||
break;
|
||||
case CommandId::SetEncoding:
|
||||
channels[command.set_encoding.channel].encoding = command.set_encoding.value;
|
||||
break;
|
||||
case CommandId::SetSecondBlock:
|
||||
channels[command.set_block.channel].block2_address = command.set_block.address;
|
||||
channels[command.set_block.channel].block2_size = command.set_block.size;
|
||||
break;
|
||||
case CommandId::SetLoopMode:
|
||||
channels[command.set_loop_mode.channel].loop_mode = command.set_loop_mode.value;
|
||||
break;
|
||||
case CommandId::SetLinearInterpolation:
|
||||
channels[command.set_linear_interpolation.channel].linear_interpolation =
|
||||
command.set_linear_interpolation.value != 0;
|
||||
break;
|
||||
case CommandId::SetPsgDuty:
|
||||
channels[command.set_psg_duty.channel].psg_duty = command.set_psg_duty.value;
|
||||
break;
|
||||
case CommandId::SetSampleRate:
|
||||
channels[command.set_sample_rate.channel].sample_rate = command.set_sample_rate.value;
|
||||
break;
|
||||
case CommandId::SetVolume:
|
||||
channels[command.set_volume.channel].left_channel_volume =
|
||||
command.set_volume.left_channel_volume;
|
||||
channels[command.set_volume.channel].right_channel_volume =
|
||||
command.set_volume.right_channel_volume;
|
||||
channels[command.set_volume.channel].left_capture_volume =
|
||||
command.set_volume.left_capture_volume;
|
||||
channels[command.set_volume.channel].right_capture_volume =
|
||||
command.set_volume.right_capture_volume;
|
||||
break;
|
||||
case CommandId::SetFirstBlock:
|
||||
channels[command.set_block.channel].block1_address = command.set_block.address;
|
||||
channels[command.set_block.channel].block1_size = command.set_block.size;
|
||||
break;
|
||||
case CommandId::SetFirstBlockAdpcmState:
|
||||
channels[command.set_adpcm_state.channel].block1_adpcm_state = {
|
||||
command.set_adpcm_state.predictor, command.set_adpcm_state.step_index};
|
||||
channels[command.set_adpcm_state.channel].block2_adpcm_state = {};
|
||||
channels[command.set_adpcm_state.channel].block2_adpcm_reload = false;
|
||||
break;
|
||||
case CommandId::SetSecondBlockAdpcmState:
|
||||
channels[command.set_adpcm_state.channel].block2_adpcm_state = {
|
||||
command.set_adpcm_state.predictor, command.set_adpcm_state.step_index};
|
||||
channels[command.set_adpcm_state.channel].block2_adpcm_reload = true;
|
||||
break;
|
||||
case CommandId::SetSecondBlockAdpcmReload:
|
||||
channels[command.set_second_block_adpcm_reload.channel].block2_adpcm_reload =
|
||||
command.set_second_block_adpcm_reload.value != 0;
|
||||
break;
|
||||
case CommandId::ConfigureChannel: {
|
||||
auto& configure = command.configure_channel;
|
||||
auto& channel = channels[configure.channel];
|
||||
channel.linear_interpolation = configure.linear_interpolation != 0;
|
||||
channel.loop_mode = static_cast<LoopMode>(configure.loop_mode.Value());
|
||||
channel.encoding = static_cast<Encoding>(configure.encoding.Value());
|
||||
channel.sample_rate = configure.sample_rate;
|
||||
channel.left_channel_volume = configure.left_channel_volume;
|
||||
channel.right_channel_volume = configure.right_channel_volume;
|
||||
channel.left_capture_volume = configure.left_capture_volume;
|
||||
channel.right_capture_volume = configure.right_capture_volume;
|
||||
channel.block1_address = configure.block1_address;
|
||||
channel.block2_address = configure.block2_address;
|
||||
channel.block1_size = channel.block2_size = configure.size;
|
||||
if (configure.enable_playback) {
|
||||
// TODO: startthe sound
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommandId::ConfigurePsg: {
|
||||
auto& configure = command.configure_psg;
|
||||
auto& channel = channels[configure.channel];
|
||||
channel.encoding = Encoding::Psg;
|
||||
channel.psg_duty = configure.duty;
|
||||
channel.sample_rate = configure.sample_rate;
|
||||
channel.left_channel_volume = configure.left_channel_volume;
|
||||
channel.right_channel_volume = configure.right_channel_volume;
|
||||
channel.left_capture_volume = configure.left_capture_volume;
|
||||
channel.right_capture_volume = configure.right_capture_volume;
|
||||
if (configure.enable_playback) {
|
||||
// TODO: startthe sound
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommandId::ConfigurePsgNoise: {
|
||||
auto& configure = command.configure_psg_noise;
|
||||
auto& channel = channels[configure.channel];
|
||||
channel.encoding = Encoding::Psg;
|
||||
channel.left_channel_volume = configure.left_channel_volume;
|
||||
channel.right_channel_volume = configure.right_channel_volume;
|
||||
channel.left_capture_volume = configure.left_capture_volume;
|
||||
channel.right_capture_volume = configure.right_capture_volume;
|
||||
if (configure.enable_playback) {
|
||||
// TODO: startthe sound
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CommandId::UpdateState: {
|
||||
MasterState master{0, 0};
|
||||
std::memcpy(shared_memory->GetPointer(master_state_offset), &master, sizeof(master));
|
||||
|
||||
u32 output_index = 0;
|
||||
for (u32 i = 0; i < ChannelCount; ++i) {
|
||||
if ((acquired_channel_mask & (1 << i)) == 0)
|
||||
continue;
|
||||
ChannelState state;
|
||||
state.active = false;
|
||||
state.adpcm_predictor = channels[i].block1_adpcm_state.predictor;
|
||||
state.adpcm_predictor = channels[i].block1_adpcm_state.step_index;
|
||||
state.zero = 0;
|
||||
std::memcpy(
|
||||
shared_memory->GetPointer(channel_state_offset + sizeof(state) * output_index),
|
||||
&state, sizeof(state));
|
||||
++output_index;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < MaxCaptureUnits; ++i) {
|
||||
if (!capture_units[i])
|
||||
continue;
|
||||
CaptureState state;
|
||||
state.active = false;
|
||||
state.zero = 0;
|
||||
std::memcpy(shared_memory->GetPointer(capture_state_offset + sizeof(state) * i),
|
||||
&state, sizeof(state));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Service_CSND, "Unimplemented command ID 0x{:X}",
|
||||
static_cast<u16>(command.command_id));
|
||||
}
|
||||
}
|
||||
|
||||
*shared_memory->GetPointer(addr + offsetof(Type0Command, finished)) = 1;
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x05, 0, 0);
|
||||
|
||||
// This is "almost" hardcoded, as in CSND initializes this with some code during sysmodule
|
||||
// startup, but it always compute to the same value.
|
||||
acquired_channel_mask = 0xFFFFFF00;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0xFFFFFF00);
|
||||
rb.Push(acquired_channel_mask);
|
||||
|
||||
LOG_WARNING(Service_CSND, "(STUBBED) called");
|
||||
}
|
||||
@ -85,6 +410,8 @@ void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) {
|
||||
void CSND_SND::ReleaseSoundChannels(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx, 0x06, 0, 0);
|
||||
|
||||
acquired_channel_mask = 0;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
|
@ -15,6 +15,45 @@ class System;
|
||||
|
||||
namespace Service::CSND {
|
||||
|
||||
enum class Encoding : u8 {
|
||||
Pcm8 = 0,
|
||||
Pcm16 = 1,
|
||||
Adpcm = 2,
|
||||
Psg = 3,
|
||||
};
|
||||
|
||||
enum class LoopMode : u8 {
|
||||
Manual = 0, // Play block 1 endlessly ignoring the size
|
||||
Normal = 1, // Play block 1 once, then repeat with block 2. Block size is reloaded every time a
|
||||
// new block is started
|
||||
OneShot = 2, // Play block 1 once and stop
|
||||
ConstantSize = 3, // Similar to Normal, but only load block size once at the beginning
|
||||
};
|
||||
|
||||
struct AdpcmState {
|
||||
s16 predictor = 0;
|
||||
u8 step_index = 0;
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
PAddr block1_address = 0;
|
||||
PAddr block2_address = 0;
|
||||
u32 block1_size = 0;
|
||||
u32 block2_size = 0;
|
||||
AdpcmState block1_adpcm_state;
|
||||
AdpcmState block2_adpcm_state;
|
||||
bool block2_adpcm_reload = false;
|
||||
u16 left_channel_volume = 0;
|
||||
u16 right_channel_volume = 0;
|
||||
u16 left_capture_volume = 0;
|
||||
u16 right_capture_volume = 0;
|
||||
u32 sample_rate = 0;
|
||||
bool linear_interpolation = false;
|
||||
LoopMode loop_mode = LoopMode::Manual;
|
||||
Encoding encoding = Encoding::Pcm8;
|
||||
u8 psg_duty = 0;
|
||||
};
|
||||
|
||||
class CSND_SND final : public ServiceFramework<CSND_SND> {
|
||||
public:
|
||||
explicit CSND_SND(Core::System& system);
|
||||
@ -26,10 +65,10 @@ private:
|
||||
* Inputs:
|
||||
* 0 : Header Code[0x00010140]
|
||||
* 1 : Shared memory block size, for mem-block creation
|
||||
* 2 : Offset0 located in the shared-memory, region size=8
|
||||
* 3 : Offset1 located in the shared-memory, region size=12*num_channels
|
||||
* 4 : Offset2 located in the shared-memory, region size=8*num_capturedevices
|
||||
* 5 : Offset3 located in the shared-memory.
|
||||
* 2 : offset to master state located in the shared-memory, region size=8
|
||||
* 3 : offset to channel state located in the shared-memory, region size=12*num_channels
|
||||
* 4 : offset to capture state located in the shared-memory, region size=8*num_captures
|
||||
* 5 : offset to type 1 commands (?) located in the shared-memory.
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : Handle-list header
|
||||
@ -166,15 +205,6 @@ private:
|
||||
*/
|
||||
void Reset(Kernel::HLERequestContext& ctx);
|
||||
|
||||
struct Type0Command {
|
||||
// command id and next command offset
|
||||
u32 command_id;
|
||||
u32 finished;
|
||||
u32 flags;
|
||||
u8 parameters[20];
|
||||
};
|
||||
static_assert(sizeof(Type0Command) == 0x20, "Type0Command structure size is wrong");
|
||||
|
||||
Core::System& system;
|
||||
|
||||
std::shared_ptr<Kernel::Mutex> mutex = nullptr;
|
||||
@ -182,6 +212,16 @@ private:
|
||||
|
||||
static constexpr u32 MaxCaptureUnits = 2;
|
||||
std::array<bool, MaxCaptureUnits> capture_units = {false, false};
|
||||
|
||||
static constexpr u32 ChannelCount = 32;
|
||||
std::array<Channel, ChannelCount> channels;
|
||||
|
||||
u32 master_state_offset = 0;
|
||||
u32 channel_state_offset = 0;
|
||||
u32 capture_state_offset = 0;
|
||||
u32 type1_command_offset = 0;
|
||||
|
||||
u32 acquired_channel_mask = 0;
|
||||
};
|
||||
|
||||
/// Initializes the CSND_SND Service
|
||||
|
Loading…
Reference in New Issue
Block a user