Merge branch 'master' into qt-setting

This commit is contained in:
wheremyfoodat 2024-02-24 17:28:02 +02:00
commit edd6af8948
17 changed files with 399 additions and 15 deletions

View file

@ -9,6 +9,7 @@
#include "helpers.hpp"
#include "logger.hpp"
#include "scheduler.hpp"
#include "ring_buffer.hpp"
// The DSP core must have access to the DSP service to be able to trigger interrupts properly
class DSPService;
@ -23,16 +24,23 @@ namespace Audio {
static constexpr u64 lleSlice = 16384;
class DSPCore {
using Samples = Common::RingBuffer<s16, 1024>;
protected:
Memory& mem;
Scheduler& scheduler;
DSPService& dspService;
Samples sampleBuffer;
bool audioEnabled = false;
MAKE_LOG_FUNCTION(log, dspLogger)
public:
enum class Type { Null, Teakra };
DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {}
DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService)
: mem(mem), scheduler(scheduler), dspService(dspService) {}
virtual ~DSPCore() {}
virtual void reset() = 0;
virtual void runAudioFrame() = 0;
@ -49,6 +57,9 @@ namespace Audio {
static Audio::DSPCore::Type typeFromString(std::string inString);
static const char* typeToString(Audio::DSPCore::Type type);
Samples& getSamples() { return sampleBuffer; }
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
};
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);

View file

@ -0,0 +1,31 @@
#pragma once
#include <atomic>
#include <string>
#include <vector>
#include "miniaudio.h"
#include "ring_buffer.hpp"
class MiniAudioDevice {
using Samples = Common::RingBuffer<ma_int16, 1024>;
static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate
static constexpr ma_uint32 channelCount = 2; // Audio output is stereo
ma_context context;
ma_device_config deviceConfig;
ma_device device;
ma_resampler resampler;
Samples* samples = nullptr;
bool initialized = false;
bool running = false;
std::vector<std::string> audioDevices;
public:
MiniAudioDevice();
// If safe is on, we create a null audio device
void init(Samples& samples, bool safe = false);
void start();
void stop();
};

View file

@ -24,6 +24,7 @@ namespace Audio {
public:
NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {}
~NullDSP() override {}
void reset() override;
void runAudioFrame() override;

View file

@ -1,4 +1,6 @@
#pragma once
#include <array>
#include "audio/dsp_core.hpp"
#include "memory.hpp"
#include "swap.hpp"
@ -10,6 +12,11 @@ namespace Audio {
u32 pipeBaseAddr;
bool running; // Is the DSP running?
bool loaded; // Have we finished loading a binary with LoadComponent?
bool signalledData;
bool signalledSemaphore;
uint audioFrameIndex = 0; // Index in our audio frame
std::array<s16, 160 * 2> audioFrame;
// Get a pointer to a data memory address
u8* getDataPointer(u32 address) { return getDspMemory() + Memory::DSP_DATA_MEMORY_OFFSET + address; }
@ -62,10 +69,6 @@ namespace Audio {
std::memcpy(statusAddress + 6, &status.writePointer, sizeof(u16));
}
}
bool signalledData;
bool signalledSemaphore;
// Run 1 slice of DSP instructions
void runSlice() {
if (running) {
@ -75,6 +78,7 @@ namespace Audio {
public:
TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService);
~TeakraDSP() override {}
void reset() override;
@ -84,6 +88,7 @@ namespace Audio {
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
}
void setAudioEnabled(bool enable) override;
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }
u16 recvData(u32 regId) override { return teakra.RecvData(regId); }

View file

@ -22,6 +22,9 @@ struct EmulatorConfig {
bool sdWriteProtected = false;
bool usePortableBuild = false;
bool audioEnabled = false;
bool vsyncEnabled = true;
bool chargerPlugged = true;
// Default to 3% battery to make users suffer
int batteryPercentage = 3;

View file

@ -8,6 +8,7 @@
#include "PICA/gpu.hpp"
#include "audio/dsp_core.hpp"
#include "audio/miniaudio_device.hpp"
#include "cheats.hpp"
#include "config.hpp"
#include "cpu.hpp"
@ -47,6 +48,7 @@ class Emulator {
Scheduler scheduler;
Crypto::AESEngine aesEngine;
MiniAudioDevice audioDevice;
Cheats cheats;
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
@ -75,6 +77,7 @@ class Emulator {
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
Discord::RPC discordRpc;
#endif
void setAudioEnabled(bool enable);
void updateDiscord();
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it

111
include/ring_buffer.hpp Normal file
View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <atomic>
#include <cstddef>
#include <cstring>
#include <limits>
#include <new>
#include <span>
#include <type_traits>
#include <vector>
namespace Common {
/// SPSC ring buffer
/// @tparam T Element type
/// @tparam capacity Number of slots in ring buffer
template <typename T, std::size_t capacity>
class RingBuffer {
/// A "slot" is made of a single `T`.
static constexpr std::size_t slot_size = sizeof(T);
// T must be safely memcpy-able and have a trivial default constructor.
static_assert(std::is_trivial_v<T>);
// Ensure capacity is sensible.
static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2);
static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two");
// Ensure lock-free.
static_assert(std::atomic_size_t::is_always_lock_free);
public:
/// Pushes slots into the ring buffer
/// @param new_slots Pointer to the slots to push
/// @param slot_count Number of slots to push
/// @returns The number of slots actually pushed
std::size_t push(const void* new_slots, std::size_t slot_count) {
const std::size_t write_index = m_write_index.load();
const std::size_t slots_free = capacity + m_read_index.load() - write_index;
const std::size_t push_count = std::min(slot_count, slots_free);
const std::size_t pos = write_index % capacity;
const std::size_t first_copy = std::min(capacity - pos, push_count);
const std::size_t second_copy = push_count - first_copy;
const char* in = static_cast<const char*>(new_slots);
std::memcpy(m_data.data() + pos, in, first_copy * slot_size);
in += first_copy * slot_size;
std::memcpy(m_data.data(), in, second_copy * slot_size);
m_write_index.store(write_index + push_count);
return push_count;
}
std::size_t push(std::span<const T> input) { return push(input.data(), input.size()); }
/// Pops slots from the ring buffer
/// @param output Where to store the popped slots
/// @param max_slots Maximum number of slots to pop
/// @returns The number of slots actually popped
std::size_t pop(void* output, std::size_t max_slots = ~std::size_t(0)) {
const std::size_t read_index = m_read_index.load();
const std::size_t slots_filled = m_write_index.load() - read_index;
const std::size_t pop_count = std::min(slots_filled, max_slots);
const std::size_t pos = read_index % capacity;
const std::size_t first_copy = std::min(capacity - pos, pop_count);
const std::size_t second_copy = pop_count - first_copy;
char* out = static_cast<char*>(output);
std::memcpy(out, m_data.data() + pos, first_copy * slot_size);
out += first_copy * slot_size;
std::memcpy(out, m_data.data(), second_copy * slot_size);
m_read_index.store(read_index + pop_count);
return pop_count;
}
std::vector<T> pop(std::size_t max_slots = ~std::size_t(0)) {
std::vector<T> out(std::min(max_slots, capacity));
const std::size_t count = Pop(out.data(), out.size());
out.resize(count);
return out;
}
/// @returns Number of slots used
[[nodiscard]] std::size_t size() const { return m_write_index.load() - m_read_index.load(); }
/// @returns Maximum size of ring buffer
[[nodiscard]] constexpr std::size_t Capacity() const { return capacity; }
private:
// It is important to align the below variables for performance reasons:
// Having them on the same cache-line would result in false-sharing between them.
// TODO: Remove this ifdef whenever clang and GCC support
// std::hardware_destructive_interference_size.
#if defined(__cpp_lib_hardware_interference_size) && !defined(__ANDROID__)
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0};
alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0};
#else
alignas(128) std::atomic_size_t m_read_index{0};
alignas(128) std::atomic_size_t m_write_index{0};
#endif
std::array<T, capacity> m_data;
};
} // namespace Common