#include "stdafx.h" #include "Emu/Memory/Memory.h" #include "Emu/System.h" #include "Emu/SysCalls/Modules.h" #include "Emu/SysCalls/Callback.h" #include "Emu/Memory/atomic_type.h" #include "rpcs3/Ini.h" #include "Emu/SysCalls/lv2/sleep_queue_type.h" #include "Emu/SysCalls/lv2/sys_time.h" #include "Emu/SysCalls/lv2/sys_event.h" #include "Emu/Event.h" #include "Emu/Audio/AudioManager.h" #include "Emu/Audio/AudioDumper.h" #include "cellAudio.h" Module *cellAudio = nullptr; AudioConfig g_audio; s32 cellAudioInit() { cellAudio->Warning("cellAudioInit()"); if (!g_audio.state.compare_and_swap_test(AUDIO_STATE_NOT_INITIALIZED, AUDIO_STATE_INITIALIZED)) { return CELL_AUDIO_ERROR_ALREADY_INIT; } // clear ports for (auto& port : g_audio.ports) { port.state.write_relaxed(AUDIO_PORT_STATE_CLOSED); } // reset variables g_audio.start_time = 0; g_audio.counter = 0; g_audio.keys.clear(); g_audio.start_time = get_system_time(); // alloc memory (only once until the emulator is stopped) g_audio.buffer = g_audio.buffer ? g_audio.buffer : vm::cast(Memory.MainMem.AllocAlign(AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT, 4096)); g_audio.indexes = g_audio.indexes ? g_audio.indexes : vm::cast(Memory.MainMem.AllocAlign(sizeof(u64) * AUDIO_PORT_COUNT, __alignof(u64))); // clear memory memset(vm::get_ptr(g_audio.buffer), 0, AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT); memset(vm::get_ptr(g_audio.indexes), 0, sizeof(u64) * AUDIO_PORT_COUNT); // start audio thread g_audio.audio_thread.start([]() { const bool do_dump = Ini.AudioDumpToFile.GetValue(); AudioDumper m_dump; if (do_dump && !m_dump.Init(2)) // Init AudioDumper for 2 channels { throw "AudioDumper::Init() failed"; } float buf2ch[2 * BUFFER_SIZE]; // intermediate buffer for 2 channels float buf8ch[8 * BUFFER_SIZE]; // intermediate buffer for 8 channels static const size_t out_buffer_size = 8 * BUFFER_SIZE; // output buffer for 8 channels std::unique_ptr out_buffer[BUFFER_NUM]; for (u32 i = 0; i < BUFFER_NUM; i++) { out_buffer[i].reset(new float[out_buffer_size] {}); } squeue_t out_queue; std::vector keys; thread_t iat("Internal Audio Thread", true /* autojoin */, [&out_queue]() { const bool use_u16 = Ini.AudioConvertToU16.GetValue(); Emu.GetAudioManager().GetAudioOut().Init(); bool opened = false; float* buffer; while (out_queue.pop(buffer, [](){ return g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED; })) { if (use_u16) { // convert the data from float to u16 with clipping: // 2x MULPS // 2x MAXPS (optional) // 2x MINPS (optional) // 2x CVTPS2DQ (converts float to s32) // PACKSSDW (converts s32 to s16 with signed saturation) u16 buf_u16[out_buffer_size]; for (size_t i = 0; i < out_buffer_size; i += 8) { static const __m128 float2u16 = { 0x8000, 0x8000, 0x8000, 0x8000 }; (__m128i&)(buf_u16[i]) = _mm_packs_epi32( _mm_cvtps_epi32(_mm_mul_ps((__m128&)(buffer[i]), float2u16)), _mm_cvtps_epi32(_mm_mul_ps((__m128&)(buffer[i + 4]), float2u16))); } if (!opened) { Emu.GetAudioManager().GetAudioOut().Open(buf_u16, out_buffer_size * sizeof(u16)); opened = true; } else { Emu.GetAudioManager().GetAudioOut().AddData(buf_u16, out_buffer_size * sizeof(u16)); } } else { if (!opened) { Emu.GetAudioManager().GetAudioOut().Open(buffer, out_buffer_size * sizeof(float)); opened = true; } else { Emu.GetAudioManager().GetAudioOut().AddData(buffer, out_buffer_size * sizeof(float)); } } } Emu.GetAudioManager().GetAudioOut().Quit(); }); u64 last_pause_time; std::atomic added_time(0); NamedThreadBase* audio_thread = GetCurrentNamedThread(); PauseCallbackRegisterer pcb(Emu.GetCallbackManager(), [&last_pause_time, &added_time, audio_thread](bool is_paused) { if (is_paused) { last_pause_time = get_system_time(); } else { added_time += get_system_time() - last_pause_time; audio_thread->Notify(); } }); while (g_audio.state.read_relaxed() == AUDIO_STATE_INITIALIZED && !Emu.IsStopped()) { if (Emu.IsPaused()) { GetCurrentNamedThread()->WaitForAnySignal(); continue; } if (added_time) { g_audio.start_time += added_time.exchange(0); } const u64 stamp0 = get_system_time(); // TODO: send beforemix event (in ~2,6 ms before mixing) // precise time of sleeping: 5,(3) ms (or 256/48000 sec) const u64 expected_time = g_audio.counter * AUDIO_SAMPLES * MHZ / 48000; if (expected_time >= stamp0 - g_audio.start_time) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } // crutch to hide giant lags caused by debugger const u64 missed_time = stamp0 - g_audio.start_time - expected_time; if (missed_time > AUDIO_SAMPLES * MHZ / 48000) { cellAudio->Notice("%f ms adjusted", (float)missed_time / 1000); g_audio.start_time += missed_time; } g_audio.counter++; const u32 out_pos = g_audio.counter % BUFFER_NUM; //if (Emu.IsPaused()) //{ // continue; //} bool first_mix = true; // mixing: for (auto& port : g_audio.ports) { if (port.state.read_relaxed() != AUDIO_PORT_STATE_STARTED) continue; const u32 block_size = port.channel * AUDIO_SAMPLES; const u32 position = port.tag % port.block; // old value const u32 buf_addr = port.addr + position * block_size * sizeof(float); auto buf = vm::get_ptr>(buf_addr); static const float k = 1.0f; // may be 1.0f const float& m = port.level; auto step_volume = [](AudioPortConfig& port) // part of cellAudioSetPortLevel functionality { if (port.level_inc) { port.level += port.level_inc; if (port.level_inc > 0.0f) { if (port.level_set - port.level <= 0.0f) { port.level = port.level_set; port.level_inc = 0.0f; } } else { if (port.level_set - port.level >= 0.0f) { port.level = port.level_set; port.level_inc = 0.0f; } } } }; if (port.channel == 2) { if (first_mix) { for (u32 i = 0; i < (sizeof(buf2ch) / sizeof(float)); i += 2) { step_volume(port); // reverse byte order const float left = buf[i + 0] * m; const float right = buf[i + 1] * m; buf2ch[i + 0] = left; buf2ch[i + 1] = right; buf8ch[i * 4 + 0] = left; buf8ch[i * 4 + 1] = right; buf8ch[i * 4 + 2] = 0.0f; buf8ch[i * 4 + 3] = 0.0f; buf8ch[i * 4 + 4] = 0.0f; buf8ch[i * 4 + 5] = 0.0f; buf8ch[i * 4 + 6] = 0.0f; buf8ch[i * 4 + 7] = 0.0f; } first_mix = false; } else { for (u32 i = 0; i < (sizeof(buf2ch) / sizeof(float)); i += 2) { step_volume(port); const float left = buf[i + 0] * m; const float right = buf[i + 1] * m; buf2ch[i + 0] += left; buf2ch[i + 1] += right; buf8ch[i * 4 + 0] += left; buf8ch[i * 4 + 1] += right; } } } else if (port.channel == 8) { if (first_mix) { for (u32 i = 0; i < (sizeof(buf2ch) / sizeof(float)); i += 2) { step_volume(port); const float left = buf[i * 4 + 0] * m; const float right = buf[i * 4 + 1] * m; const float center = buf[i * 4 + 2] * m; const float low_freq = buf[i * 4 + 3] * m; const float rear_left = buf[i * 4 + 4] * m; const float rear_right = buf[i * 4 + 5] * m; const float side_left = buf[i * 4 + 6] * m; const float side_right = buf[i * 4 + 7] * m; const float mid = (center + low_freq) * 0.708f; buf2ch[i + 0] = (left + rear_left + side_left + mid) * k; buf2ch[i + 1] = (right + rear_right + side_right + mid) * k; buf8ch[i * 4 + 0] = left; buf8ch[i * 4 + 1] = right; buf8ch[i * 4 + 2] = center; buf8ch[i * 4 + 3] = low_freq; buf8ch[i * 4 + 4] = rear_left; buf8ch[i * 4 + 5] = rear_right; buf8ch[i * 4 + 6] = side_left; buf8ch[i * 4 + 7] = side_right; } first_mix = false; } else { for (u32 i = 0; i < (sizeof(buf2ch) / sizeof(float)); i += 2) { step_volume(port); const float left = buf[i * 4 + 0] * m; const float right = buf[i * 4 + 1] * m; const float center = buf[i * 4 + 2] * m; const float low_freq = buf[i * 4 + 3] * m; const float rear_left = buf[i * 4 + 4] * m; const float rear_right = buf[i * 4 + 5] * m; const float side_left = buf[i * 4 + 6] * m; const float side_right = buf[i * 4 + 7] * m; const float mid = (center + low_freq) * 0.708f; buf2ch[i + 0] += (left + rear_left + side_left + mid) * k; buf2ch[i + 1] += (right + rear_right + side_right + mid) * k; buf8ch[i * 4 + 0] += left; buf8ch[i * 4 + 1] += right; buf8ch[i * 4 + 2] += center; buf8ch[i * 4 + 3] += low_freq; buf8ch[i * 4 + 4] += rear_left; buf8ch[i * 4 + 5] += rear_right; buf8ch[i * 4 + 6] += side_left; buf8ch[i * 4 + 7] += side_right; } } } else { throw fmt::format("Unknown channel count (port=%d, channel=%d)", &port - g_audio.ports, port.channel); } memset(buf, 0, block_size * sizeof(float)); } if (!first_mix) { // copy output data (2 ch) //for (u32 i = 0; i < (sizeof(buf2ch) / sizeof(float)); i++) //{ // out_buffer[out_pos][i] = buf2ch[i]; //} // copy output data (8 ch) for (u32 i = 0; i < (sizeof(buf8ch) / sizeof(float)); i++) { out_buffer[out_pos][i] = buf8ch[i]; } } //const u64 stamp1 = get_system_time(); if (first_mix) { memset(out_buffer[out_pos].get(), 0, out_buffer_size * sizeof(float)); } if (!out_queue.push(out_buffer[out_pos].get(), [](){ return g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED; })) { break; } //const u64 stamp2 = get_system_time(); // send aftermix event (normal audio event) { std::lock_guard lock(g_audio.mutex); // update indexes: auto indexes = vm::ptr::make(g_audio.indexes); for (u32 i = 0; i < AUDIO_PORT_COUNT; i++) { AudioPortConfig& port = g_audio.ports[i]; if (port.state.read_relaxed() != AUDIO_PORT_STATE_STARTED) continue; u32 position = port.tag % port.block; // old value port.counter = g_audio.counter; port.tag++; // absolute index of block that will be read indexes[i] = (position + 1) % port.block; // write new value } // load keys: keys.resize(g_audio.keys.size()); memcpy(keys.data(), g_audio.keys.data(), sizeof(u64) * keys.size()); } for (u32 i = 0; i < keys.size(); i++) { // TODO: check event source Emu.GetEventManager().SendEvent(keys[i], 0x10103000e010e07, 0, 0, 0); } //const u64 stamp3 = get_system_time(); if (do_dump && !first_mix) { if (m_dump.GetCh() == 8) { if (m_dump.WriteData(&buf8ch, sizeof(buf8ch)) != sizeof(buf8ch)) // write file data (8 ch) { throw "AudioDumper::WriteData() failed (8 ch)"; } } else if (m_dump.GetCh() == 2) { if (m_dump.WriteData(&buf2ch, sizeof(buf2ch)) != sizeof(buf2ch)) // write file data (2 ch) { throw "AudioDumper::WriteData() failed (2 ch)"; } } else { throw fmt::format("AudioDumper::GetCh() returned unknown value (%d)", m_dump.GetCh()); } } //LOG_NOTICE(HLE, "Audio perf: start=%d (access=%d, AddData=%d, events=%d, dump=%d)", //stamp0 - m_config.start_time, stamp1 - stamp0, stamp2 - stamp1, stamp3 - stamp2, get_system_time() - stamp3); } }); return CELL_OK; } s32 cellAudioQuit() { cellAudio->Warning("cellAudioQuit()"); if (!g_audio.state.compare_and_swap_test(AUDIO_STATE_INITIALIZED, AUDIO_STATE_FINALIZED)) { return CELL_AUDIO_ERROR_NOT_INIT; } g_audio.audio_thread.join(); g_audio.state.exchange(AUDIO_STATE_NOT_INITIALIZED); return CELL_OK; } s32 cellAudioPortOpen(vm::ptr audioParam, vm::ptr portNum) { cellAudio->Warning("cellAudioPortOpen(audioParam=0x%x, portNum=0x%x)", audioParam, portNum); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (!audioParam || !portNum) { return CELL_AUDIO_ERROR_PARAM; } const u64 channel = audioParam->nChannel; const u64 block = audioParam->nBlock; const u64 attr = audioParam->attr; // check attributes if (channel != CELL_AUDIO_PORT_2CH && channel != CELL_AUDIO_PORT_8CH && channel) { return CELL_AUDIO_ERROR_PARAM; } if (block != CELL_AUDIO_BLOCK_8 && block != CELL_AUDIO_BLOCK_16 && block != 2 && block != 4 && block != 32) { return CELL_AUDIO_ERROR_PARAM; } // list unsupported flags if (attr & CELL_AUDIO_PORTATTR_BGM) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_BGM"); } if (attr & CELL_AUDIO_PORTATTR_OUT_SECONDARY) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_SECONDARY"); } if (attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_0) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_0"); } if (attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_1) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_1"); } if (attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_2) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_2"); } if (attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_3) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_3"); } if (attr & CELL_AUDIO_PORTATTR_OUT_NO_ROUTE) { cellAudio->Todo("cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_NO_ROUTE"); } if (attr & 0xFFFFFFFFF0EFEFEEULL) { cellAudio->Todo("cellAudioPortOpen(): unknown attributes set (0x%llx)", attr); } // open audio port const u32 port_index = g_audio.open_port(); if (!~port_index) { return CELL_AUDIO_ERROR_PORT_FULL; } AudioPortConfig& port = g_audio.ports[port_index]; port.channel = (u32)channel; port.block = (u32)block; port.attr = attr; port.addr = g_audio.buffer + AUDIO_PORT_OFFSET * port_index; port.read_index_addr = g_audio.indexes + sizeof(u64) * port_index; port.size = port.channel * port.block * AUDIO_SAMPLES * sizeof(float); port.tag = 0; if (port.attr & CELL_AUDIO_PORTATTR_INITLEVEL) { port.level = audioParam->level; } else { port.level = 1.0f; } port.level_set = port.level; port.level_inc = 0.0f; *portNum = port_index; cellAudio->Warning("*** audio port opened(nChannel=%d, nBlock=%d, attr=0x%llx, level=%f): port = %d", channel, block, attr, port.level, port_index); return CELL_OK; } s32 cellAudioGetPortConfig(u32 portNum, vm::ptr portConfig) { cellAudio->Warning("cellAudioGetPortConfig(portNum=0x%x, portConfig=0x%x)", portNum, portConfig); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (!portConfig || portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } AudioPortConfig& port = g_audio.ports[portNum]; portConfig->readIndexAddr = port.read_index_addr; switch (auto state = port.state.read_sync()) { case AUDIO_PORT_STATE_CLOSED: portConfig->status = CELL_AUDIO_STATUS_CLOSE; break; case AUDIO_PORT_STATE_OPENED: portConfig->status = CELL_AUDIO_STATUS_READY; break; case AUDIO_PORT_STATE_STARTED: portConfig->status = CELL_AUDIO_STATUS_RUN; break; default: throw fmt::format("cellAudioGetPortConfig(%d): invalid port state (0x%x)", portNum, state); } portConfig->nChannel = port.channel; portConfig->nBlock = port.block; portConfig->portSize = port.size; portConfig->portAddr = port.addr; return CELL_OK; } s32 cellAudioPortStart(u32 portNum) { cellAudio->Warning("cellAudioPortStart(portNum=0x%x)", portNum); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } switch (auto state = g_audio.ports[portNum].state.compare_and_swap(AUDIO_PORT_STATE_OPENED, AUDIO_PORT_STATE_STARTED)) { case AUDIO_PORT_STATE_CLOSED: return CELL_AUDIO_ERROR_PORT_NOT_OPEN; case AUDIO_PORT_STATE_STARTED: return CELL_AUDIO_ERROR_PORT_ALREADY_RUN; case AUDIO_PORT_STATE_OPENED: return CELL_OK; default: throw fmt::format("cellAudioPortStart(%d): invalid port state (0x%x)", portNum, state); } } s32 cellAudioPortClose(u32 portNum) { cellAudio->Warning("cellAudioPortClose(portNum=0x%x)", portNum); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } switch (auto state = g_audio.ports[portNum].state.exchange(AUDIO_PORT_STATE_CLOSED)) { case AUDIO_PORT_STATE_CLOSED: return CELL_AUDIO_ERROR_PORT_NOT_OPEN; case AUDIO_PORT_STATE_STARTED: return CELL_OK; case AUDIO_PORT_STATE_OPENED: return CELL_OK; default: throw fmt::format("cellAudioPortClose(%d): invalid port state (0x%x)", portNum, state); } } s32 cellAudioPortStop(u32 portNum) { cellAudio->Warning("cellAudioPortStop(portNum=0x%x)", portNum); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } switch (auto state = g_audio.ports[portNum].state.compare_and_swap(AUDIO_PORT_STATE_STARTED, AUDIO_PORT_STATE_OPENED)) { case AUDIO_PORT_STATE_CLOSED: return CELL_AUDIO_ERROR_PORT_NOT_RUN; case AUDIO_PORT_STATE_STARTED: return CELL_OK; case AUDIO_PORT_STATE_OPENED: return CELL_AUDIO_ERROR_PORT_NOT_RUN; default: throw fmt::format("cellAudioPortStop(%d): invalid port state (0x%x)", portNum, state); } } s32 cellAudioGetPortTimestamp(u32 portNum, u64 tag, vm::ptr stamp) { cellAudio->Log("cellAudioGetPortTimestamp(portNum=0x%x, tag=0x%llx, stamp=0x%x)", portNum, tag, stamp); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } AudioPortConfig& port = g_audio.ports[portNum]; if (port.state.read_relaxed() == AUDIO_PORT_STATE_CLOSED) { return CELL_AUDIO_ERROR_PORT_NOT_OPEN; } // TODO: check tag (CELL_AUDIO_ERROR_TAG_NOT_FOUND error) std::lock_guard lock(g_audio.mutex); *stamp = g_audio.start_time + (port.counter + (tag - port.tag)) * 256000000 / 48000; return CELL_OK; } s32 cellAudioGetPortBlockTag(u32 portNum, u64 blockNo, vm::ptr tag) { cellAudio->Log("cellAudioGetPortBlockTag(portNum=0x%x, blockNo=0x%llx, tag=0x%x)", portNum, blockNo, tag); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } AudioPortConfig& port = g_audio.ports[portNum]; if (port.state.read_relaxed() == AUDIO_PORT_STATE_CLOSED) { return CELL_AUDIO_ERROR_PORT_NOT_OPEN; } if (blockNo >= port.block) { return CELL_AUDIO_ERROR_PARAM; } std::lock_guard lock(g_audio.mutex); u64 tag_base = port.tag; if (tag_base % port.block > blockNo) { tag_base &= ~(port.block - 1); tag_base += port.block; } else { tag_base &= ~(port.block - 1); } *tag = tag_base + blockNo; return CELL_OK; } s32 cellAudioSetPortLevel(u32 portNum, float level) { cellAudio->Log("cellAudioSetPortLevel(portNum=0x%x, level=%f)", portNum, level); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT) { return CELL_AUDIO_ERROR_PARAM; } AudioPortConfig& port = g_audio.ports[portNum]; if (port.state.read_relaxed() == AUDIO_PORT_STATE_CLOSED) { return CELL_AUDIO_ERROR_PORT_NOT_OPEN; } if (level >= 0.0f) { std::lock_guard lock(g_audio.mutex); port.level_set = level; port.level_inc = (port.level - level) / 624.0f; } else { cellAudio->Todo("cellAudioSetPortLevel(portNum=0x%x): negative level value (%f)", portNum, level); } return CELL_OK; } // Utility Functions int cellAudioCreateNotifyEventQueue(vm::ptr id, vm::ptr key) { cellAudio->Warning("cellAudioCreateNotifyEventQueue(id_addr=0x%x, key_addr=0x%x)", id.addr(), key.addr()); std::lock_guard lock(g_audio.mutex); u64 event_key = 0; while (Emu.GetEventManager().CheckKey((event_key << 48) | 0x80004d494f323221)) { event_key++; // experimental //return CELL_AUDIO_ERROR_EVENT_QUEUE; } event_key = (event_key << 48) | 0x80004d494f323221; // left part: 0x8000, 0x8001, 0x8002 ... std::shared_ptr eq(new EventQueue(SYS_SYNC_FIFO, SYS_PPU_QUEUE, event_key, event_key, 32)); if (!Emu.GetEventManager().RegisterKey(eq, event_key)) { return CELL_AUDIO_ERROR_EVENT_QUEUE; } *id = cellAudio->GetNewId(eq); *key = event_key; return CELL_OK; } int cellAudioCreateNotifyEventQueueEx(vm::ptr id, vm::ptr key, u32 iFlags) { cellAudio->Todo("cellAudioCreateNotifyEventQueueEx(id_addr=0x%x, key_addr=0x%x, iFlags=0x%x)", id.addr(), key.addr(), iFlags); return CELL_OK; } int cellAudioSetNotifyEventQueue(u64 key) { cellAudio->Warning("cellAudioSetNotifyEventQueue(key=0x%llx)", key); std::lock_guard lock(g_audio.mutex); for (u32 i = 0; i < g_audio.keys.size(); i++) // check for duplicates { if (g_audio.keys[i] == key) { return CELL_AUDIO_ERROR_PARAM; } } g_audio.keys.push_back(key); /*EventQueue* eq; if (!Emu.GetEventManager().GetEventQueue(key, eq)) { return CELL_AUDIO_ERROR_PARAM; }*/ // TODO: connect port (?????) return CELL_OK; } int cellAudioSetNotifyEventQueueEx(u64 key, u32 iFlags) { cellAudio->Todo("cellAudioSetNotifyEventQueueEx(key=0x%llx, iFlags=0x%x)", key, iFlags); return CELL_OK; } int cellAudioRemoveNotifyEventQueue(u64 key) { cellAudio->Warning("cellAudioRemoveNotifyEventQueue(key=0x%llx)", key); std::lock_guard lock(g_audio.mutex); bool found = false; for (u32 i = 0; i < g_audio.keys.size(); i++) { if (g_audio.keys[i] == key) { g_audio.keys.erase(g_audio.keys.begin() + i); found = true; break; } } if (!found) { // ??? return CELL_AUDIO_ERROR_PARAM; } /*EventQueue* eq; if (!Emu.GetEventManager().GetEventQueue(key, eq)) { return CELL_AUDIO_ERROR_PARAM; }*/ // TODO: disconnect port return CELL_OK; } int cellAudioRemoveNotifyEventQueueEx(u64 key, u32 iFlags) { cellAudio->Todo("cellAudioRemoveNotifyEventQueueEx(key=0x%llx, iFlags=0x%x)", key, iFlags); return CELL_OK; } s32 cellAudioAddData(u32 portNum, vm::ptr src, u32 samples, float volume) { cellAudio->Log("cellAudioAddData(portNum=%d, src=0x%x, samples=%d, volume=%f)", portNum, src, samples, volume); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT || !src || src.addr() % 4) { return CELL_AUDIO_ERROR_PARAM; } if (samples != 256) { // despite the docs, seems that only fixed value is supported cellAudio->Error("cellAudioAddData(): invalid samples value (0x%x)", samples); return CELL_AUDIO_ERROR_PARAM; } const AudioPortConfig& port = g_audio.ports[portNum]; const auto dst = vm::ptr::make(port.addr + (port.tag % port.block) * port.channel * 256 * sizeof(float)); for (u32 i = 0; i < samples * port.channel; i++) { dst[i] += src[i] * volume; // mix all channels } return CELL_OK; } s32 cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, float volume) { cellAudio->Log("cellAudioAdd2chData(portNum=%d, src=0x%x, samples=%d, volume=%f)", portNum, src, samples, volume); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT || !src || src.addr() % 4) { return CELL_AUDIO_ERROR_PARAM; } if (samples != 256) { // despite the docs, seems that only fixed value is supported cellAudio->Error("cellAudioAdd2chData(): invalid samples value (0x%x)", samples); return CELL_AUDIO_ERROR_PARAM; } const AudioPortConfig& port = g_audio.ports[portNum]; const auto dst = vm::ptr::make(port.addr + (port.tag % port.block) * port.channel * 256 * sizeof(float)); if (port.channel == 2) { cellAudio->Error("cellAudioAdd2chData(portNum=%d): port.channel = 2", portNum); } else if (port.channel == 6) { for (u32 i = 0; i < samples; i++) { dst[i * 6 + 0] += src[i * 2 + 0] * volume; // mix L ch dst[i * 6 + 1] += src[i * 2 + 1] * volume; // mix R ch //dst[i * 6 + 2] += 0.0f; // center //dst[i * 6 + 3] += 0.0f; // LFE //dst[i * 6 + 4] += 0.0f; // rear L //dst[i * 6 + 5] += 0.0f; // rear R } } else if (port.channel == 8) { for (u32 i = 0; i < samples; i++) { dst[i * 8 + 0] += src[i * 2 + 0] * volume; // mix L ch dst[i * 8 + 1] += src[i * 2 + 1] * volume; // mix R ch //dst[i * 8 + 2] += 0.0f; // center //dst[i * 8 + 3] += 0.0f; // LFE //dst[i * 8 + 4] += 0.0f; // rear L //dst[i * 8 + 5] += 0.0f; // rear R //dst[i * 8 + 6] += 0.0f; // side L //dst[i * 8 + 7] += 0.0f; // side R } } else { cellAudio->Error("cellAudioAdd2chData(portNum=%d): invalid port.channel value (%d)", portNum, port.channel); } return CELL_OK; } s32 cellAudioAdd6chData(u32 portNum, vm::ptr src, float volume) { cellAudio->Log("cellAudioAdd6chData(portNum=%d, src=0x%x, volume=%f)", portNum, src, volume); if (g_audio.state.read_relaxed() != AUDIO_STATE_INITIALIZED) { return CELL_AUDIO_ERROR_NOT_INIT; } if (portNum >= AUDIO_PORT_COUNT || !src || src.addr() % 4) { return CELL_AUDIO_ERROR_PARAM; } const AudioPortConfig& port = g_audio.ports[portNum]; const auto dst = vm::ptr::make(port.addr + (port.tag % port.block) * port.channel * 256 * sizeof(float)); if (port.channel == 2 || port.channel == 6) { cellAudio->Error("cellAudioAdd2chData(portNum=%d): port.channel = %d", portNum, port.channel); } else if (port.channel == 8) { for (u32 i = 0; i < 256; i++) { dst[i * 8 + 0] += src[i * 6 + 0] * volume; // mix L ch dst[i * 8 + 1] += src[i * 6 + 1] * volume; // mix R ch dst[i * 8 + 2] += src[i * 6 + 2] * volume; // mix center dst[i * 8 + 3] += src[i * 6 + 3] * volume; // mix LFE dst[i * 8 + 4] += src[i * 6 + 4] * volume; // mix rear L dst[i * 8 + 5] += src[i * 6 + 5] * volume; // mix rear R //dst[i * 8 + 6] += 0.0f; // side L //dst[i * 8 + 7] += 0.0f; // side R } } else { cellAudio->Error("cellAudioAdd6chData(portNum=%d): invalid port.channel value (%d)", portNum, port.channel); } return CELL_OK; } int cellAudioMiscSetAccessoryVolume(u32 devNum, float volume) { cellAudio->Todo("cellAudioMiscSetAccessoryVolume(devNum=0x%x, volume=%f)", devNum, volume); return CELL_OK; } int cellAudioSendAck(u64 data3) { cellAudio->Todo("cellAudioSendAck(data3=0x%llx)", data3); return CELL_OK; } int cellAudioSetPersonalDevice(int iPersonalStream, int iDevice) { cellAudio->Todo("cellAudioSetPersonalDevice(iPersonalStream=0x%x, iDevice=0x%x)", iPersonalStream, iDevice); return CELL_OK; } int cellAudioUnsetPersonalDevice(int iPersonalStream) { cellAudio->Todo("cellAudioUnsetPersonalDevice(iPersonalStream=0x%x)", iPersonalStream); return CELL_OK; } void cellAudio_init(Module *pxThis) { cellAudio = pxThis; g_audio.state.write_relaxed(AUDIO_STATE_NOT_INITIALIZED); g_audio.buffer = 0; g_audio.indexes = 0; REG_FUNC(cellAudio, cellAudioInit); REG_FUNC(cellAudio, cellAudioPortClose); REG_FUNC(cellAudio, cellAudioPortStop); REG_FUNC(cellAudio, cellAudioGetPortConfig); REG_FUNC(cellAudio, cellAudioPortStart); REG_FUNC(cellAudio, cellAudioQuit); REG_FUNC(cellAudio, cellAudioPortOpen); REG_FUNC(cellAudio, cellAudioSetPortLevel); REG_FUNC(cellAudio, cellAudioCreateNotifyEventQueue); REG_FUNC(cellAudio, cellAudioCreateNotifyEventQueueEx); REG_FUNC(cellAudio, cellAudioMiscSetAccessoryVolume); REG_FUNC(cellAudio, cellAudioSetNotifyEventQueue); REG_FUNC(cellAudio, cellAudioSetNotifyEventQueueEx); REG_FUNC(cellAudio, cellAudioGetPortTimestamp); REG_FUNC(cellAudio, cellAudioAdd2chData); REG_FUNC(cellAudio, cellAudioAdd6chData); REG_FUNC(cellAudio, cellAudioAddData); REG_FUNC(cellAudio, cellAudioGetPortBlockTag); REG_FUNC(cellAudio, cellAudioRemoveNotifyEventQueue); REG_FUNC(cellAudio, cellAudioRemoveNotifyEventQueueEx); REG_FUNC(cellAudio, cellAudioSendAck); REG_FUNC(cellAudio, cellAudioSetPersonalDevice); REG_FUNC(cellAudio, cellAudioUnsetPersonalDevice); } void cellAudio_load() { // CELL_SYSMODULE AUDIO module is rarely loaded manually, so cellAudio_load() won't be called in every case }