summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/audio_core.cpp4
-rw-r--r--src/audio_core/audio_core.h3
-rw-r--r--src/audio_core/hle/dsp.cpp45
-rw-r--r--src/audio_core/hle/dsp.h8
-rw-r--r--src/audio_core/null_sink.h2
-rw-r--r--src/audio_core/sdl2_sink.cpp6
-rw-r--r--src/audio_core/sdl2_sink.h2
-rw-r--r--src/audio_core/sink.h5
-rw-r--r--src/citra/config.cpp1
-rw-r--r--src/citra/default_ini.h6
-rw-r--r--src/citra_qt/config.cpp2
-rw-r--r--src/citra_qt/configure_audio.cpp3
-rw-r--r--src/citra_qt/configure_audio.ui10
-rw-r--r--src/core/hle/service/apt/apt.cpp7
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.cpp79
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.h12
-rw-r--r--src/core/settings.cpp1
-rw-r--r--src/core/settings.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp1
19 files changed, 152 insertions, 46 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index d42249ebd..8e19ec0c4 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -71,6 +71,10 @@ void SelectSink(std::string sink_id) {
DSP::HLE::SetSink(iter->factory());
}
+void EnableStretching(bool enable) {
+ DSP::HLE::EnableStretching(enable);
+}
+
void Shutdown() {
CoreTiming::UnscheduleEvent(tick_event, 0);
DSP::HLE::Shutdown();
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index f618361f3..7e678aba5 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -23,6 +23,9 @@ void AddAddressSpace(Kernel::VMManager& vm_manager);
/// Select the sink to use based on sink id.
void SelectSink(std::string sink_id);
+/// Enable/Disable stretching.
+void EnableStretching(bool enable);
+
/// Shutdown Audio Core
void Shutdown();
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
index 0640e1eff..0cddeb82a 100644
--- a/src/audio_core/hle/dsp.cpp
+++ b/src/audio_core/hle/dsp.cpp
@@ -85,12 +85,45 @@ static StereoFrame16 GenerateCurrentFrame() {
// Audio output
+static bool perform_time_stretching = true;
static std::unique_ptr<AudioCore::Sink> sink;
static AudioCore::TimeStretcher time_stretcher;
+static void FlushResidualStretcherAudio() {
+ time_stretcher.Flush();
+ while (true) {
+ std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
+ if (residual_audio.empty())
+ break;
+ sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2);
+ }
+}
+
static void OutputCurrentFrame(const StereoFrame16& frame) {
- time_stretcher.AddSamples(&frame[0][0], frame.size());
- sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue()));
+ if (perform_time_stretching) {
+ time_stretcher.AddSamples(&frame[0][0], frame.size());
+ std::vector<s16> stretched_samples = time_stretcher.Process(sink->SamplesInQueue());
+ sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2);
+ } else {
+ constexpr size_t maximum_sample_latency = 1024; // about 32 miliseconds
+ if (sink->SamplesInQueue() > maximum_sample_latency) {
+ // This can occur if we're running too fast and samples are starting to back up.
+ // Just drop the samples.
+ return;
+ }
+
+ sink->EnqueueSamples(&frame[0][0], frame.size());
+ }
+}
+
+void EnableStretching(bool enable) {
+ if (perform_time_stretching == enable)
+ return;
+
+ if (!enable) {
+ FlushResidualStretcherAudio();
+ }
+ perform_time_stretching = enable;
}
// Public Interface
@@ -111,12 +144,8 @@ void Init() {
}
void Shutdown() {
- time_stretcher.Flush();
- while (true) {
- std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
- if (residual_audio.empty())
- break;
- sink->EnqueueSamples(residual_audio);
+ if (perform_time_stretching) {
+ FlushResidualStretcherAudio();
}
}
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
index 9275cd7de..565f20b6f 100644
--- a/src/audio_core/hle/dsp.h
+++ b/src/audio_core/hle/dsp.h
@@ -544,5 +544,13 @@ bool Tick();
*/
void SetSink(std::unique_ptr<AudioCore::Sink> sink);
+/**
+ * Enables/Disables audio-stretching.
+ * Audio stretching is an enhancement that stretches audio to match emulation
+ * speed to prevent stuttering at the cost of some audio latency.
+ * @param enable true to enable, false to disable.
+ */
+void EnableStretching(bool enable);
+
} // namespace HLE
} // namespace DSP
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index faf0ee4e1..9931c4778 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -19,7 +19,7 @@ public:
return native_sample_rate;
}
- void EnqueueSamples(const std::vector<s16>&) override {}
+ void EnqueueSamples(const s16*, size_t) override {}
size_t SamplesInQueue() const override {
return 0;
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
index dc75c04ee..311dd5b59 100644
--- a/src/audio_core/sdl2_sink.cpp
+++ b/src/audio_core/sdl2_sink.cpp
@@ -71,14 +71,12 @@ unsigned int SDL2Sink::GetNativeSampleRate() const {
return impl->sample_rate;
}
-void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) {
+void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) {
if (impl->audio_device_id <= 0)
return;
- ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)");
-
SDL_LockAudioDevice(impl->audio_device_id);
- impl->queue.emplace_back(samples);
+ impl->queue.emplace_back(samples, samples + sample_count * 2);
SDL_UnlockAudioDevice(impl->audio_device_id);
}
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
index 0f296b673..b13827214 100644
--- a/src/audio_core/sdl2_sink.h
+++ b/src/audio_core/sdl2_sink.h
@@ -18,7 +18,7 @@ public:
unsigned int GetNativeSampleRate() const override;
- void EnqueueSamples(const std::vector<s16>& samples) override;
+ void EnqueueSamples(const s16* samples, size_t sample_count) override;
size_t SamplesInQueue() const override;
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
index 1c881c3d2..a06fc3dcc 100644
--- a/src/audio_core/sink.h
+++ b/src/audio_core/sink.h
@@ -23,9 +23,10 @@ public:
/**
* Feed stereo samples to sink.
- * @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two.
+ * @param samples Samples in interleaved stereo PCM16 format.
+ * @param sample_count Number of samples.
*/
- virtual void EnqueueSamples(const std::vector<s16>& samples) = 0;
+ virtual void EnqueueSamples(const s16* samples, size_t sample_count) = 0;
/// Samples enqueued that have not been played yet.
virtual std::size_t SamplesInQueue() const = 0;
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index d71045646..110b883fb 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -79,6 +79,7 @@ void Config::ReadValues() {
// Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
+ Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
// Data Storage
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index b10700e62..2031620a5 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -70,6 +70,12 @@ bg_green =
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
output_engine =
+# Whether or not to enable the audio-stretching post-processing effect.
+# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
+# at the cost of increasing audio latency.
+# 0: No, 1 (default): Yes
+enable_audio_stretching =
+
[Data Storage]
# Whether to create a virtual SD card.
# 1 (default): Yes, 0: No
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index ff7af445e..fa3fa210c 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -57,6 +57,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Audio");
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
+ Settings::values.enable_audio_stretching = qt_config->value("enable_audio_stretching", true).toBool();
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
@@ -150,6 +151,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Audio");
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
+ qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp
index cedfa2f2a..7100be158 100644
--- a/src/citra_qt/configure_audio.cpp
+++ b/src/citra_qt/configure_audio.cpp
@@ -36,9 +36,12 @@ void ConfigureAudio::setConfiguration() {
}
}
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
+
+ ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
}
void ConfigureAudio::applyConfiguration() {
Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString();
+ Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
Settings::Apply();
}
diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui
index d7f6946ca..3e2b4635f 100644
--- a/src/citra_qt/configure_audio.ui
+++ b/src/citra_qt/configure_audio.ui
@@ -25,6 +25,16 @@
</item>
</layout>
</item>
+ <item>
+ <widget class="QCheckBox" name="toggle_audio_stretching">
+ <property name="text">
+ <string>Enable audio stretching</string>
+ </property>
+ <property name="toolTip">
+ <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 5af0d49ba..4d2956638 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -81,13 +81,8 @@ void GetSharedFont(Service::Interface* self) {
// The shared font has to be relocated to the new address before being passed to the application.
VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
- // The shared font dumped by 3dsutils (https://github.com/citra-emu/3dsutils) uses this address as base,
- // so we relocate it from there to our real address.
- // TODO(Subv): This address is wrong if the shared font is dumped from a n3DS,
- // we need a way to automatically calculate the original address of the font from the file.
- static const VAddr SHARED_FONT_VADDR = 0x18000000;
if (!shared_font_relocated) {
- BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address);
+ BCFNT::RelocateSharedFont(shared_font_mem, target_address);
shared_font_relocated = true;
}
cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
index b0d39d4a5..57eb39d75 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -9,60 +9,97 @@ namespace Service {
namespace APT {
namespace BCFNT {
-void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address) {
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address) {
static const u32 SharedFontStartOffset = 0x80;
- u8* data = shared_font->GetPointer(SharedFontStartOffset);
+ const u8* cfnt_ptr = shared_font->GetPointer(SharedFontStartOffset);
CFNT cfnt;
- memcpy(&cfnt, data, sizeof(cfnt));
+ memcpy(&cfnt, cfnt_ptr, sizeof(cfnt));
- // Advance past the header
- data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size);
+ u32 assumed_cmap_offset = 0;
+ u32 assumed_cwdh_offset = 0;
+ u32 assumed_tglp_offset = 0;
+ u32 first_cmap_offset = 0;
+ u32 first_cwdh_offset = 0;
+ u32 first_tglp_offset = 0;
+ // First discover the location of sections so that the rebase offset can be auto-detected
+ u32 current_offset = SharedFontStartOffset + cfnt.header_size;
for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
+ const u8* data = shared_font->GetPointer(current_offset);
+
+ SectionHeader section_header;
+ memcpy(&section_header, data, sizeof(section_header));
+
+ if (first_cmap_offset == 0 && memcmp(section_header.magic, "CMAP", 4) == 0) {
+ first_cmap_offset = current_offset;
+ } else if (first_cwdh_offset == 0 && memcmp(section_header.magic, "CWDH", 4) == 0) {
+ first_cwdh_offset = current_offset;
+ } else if (first_tglp_offset == 0 && memcmp(section_header.magic, "TGLP", 4) == 0) {
+ first_tglp_offset = current_offset;
+ } else if (memcmp(section_header.magic, "FINF", 4) == 0) {
+ BCFNT::FINF finf;
+ memcpy(&finf, data, sizeof(finf));
+
+ assumed_cmap_offset = finf.cmap_offset - sizeof(SectionHeader);
+ assumed_cwdh_offset = finf.cwdh_offset - sizeof(SectionHeader);
+ assumed_tglp_offset = finf.tglp_offset - sizeof(SectionHeader);
+ }
+
+ current_offset += section_header.section_size;
+ }
+
+ u32 previous_base = assumed_cmap_offset - first_cmap_offset;
+ ASSERT(previous_base == assumed_cwdh_offset - first_cwdh_offset);
+ ASSERT(previous_base == assumed_tglp_offset - first_tglp_offset);
+
+ u32 offset = new_address - previous_base;
+
+ // Reset pointer back to start of sections and do the actual rebase
+ current_offset = SharedFontStartOffset + cfnt.header_size;
+ for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
+ u8* data = shared_font->GetPointer(current_offset);
+
+ SectionHeader section_header;
+ memcpy(&section_header, data, sizeof(section_header));
- u32 section_size = 0;
- if (memcmp(data, "FINF", 4) == 0) {
+ if (memcmp(section_header.magic, "FINF", 4) == 0) {
BCFNT::FINF finf;
memcpy(&finf, data, sizeof(finf));
- section_size = finf.section_size;
// Relocate the offsets in the FINF section
- finf.cmap_offset += new_address - previous_address;
- finf.cwdh_offset += new_address - previous_address;
- finf.tglp_offset += new_address - previous_address;
+ finf.cmap_offset += offset;
+ finf.cwdh_offset += offset;
+ finf.tglp_offset += offset;
memcpy(data, &finf, sizeof(finf));
- } else if (memcmp(data, "CMAP", 4) == 0) {
+ } else if (memcmp(section_header.magic, "CMAP", 4) == 0) {
BCFNT::CMAP cmap;
memcpy(&cmap, data, sizeof(cmap));
- section_size = cmap.section_size;
// Relocate the offsets in the CMAP section
- cmap.next_cmap_offset += new_address - previous_address;
+ cmap.next_cmap_offset += offset;
memcpy(data, &cmap, sizeof(cmap));
- } else if (memcmp(data, "CWDH", 4) == 0) {
+ } else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
BCFNT::CWDH cwdh;
memcpy(&cwdh, data, sizeof(cwdh));
- section_size = cwdh.section_size;
// Relocate the offsets in the CWDH section
- cwdh.next_cwdh_offset += new_address - previous_address;
+ cwdh.next_cwdh_offset += offset;
memcpy(data, &cwdh, sizeof(cwdh));
- } else if (memcmp(data, "TGLP", 4) == 0) {
+ } else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
BCFNT::TGLP tglp;
memcpy(&tglp, data, sizeof(tglp));
- section_size = tglp.section_size;
// Relocate the offsets in the TGLP section
- tglp.sheet_data_offset += new_address - previous_address;
+ tglp.sheet_data_offset += offset;
memcpy(data, &tglp, sizeof(tglp));
}
- data += section_size;
+ current_offset += section_header.section_size;
}
}
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.h b/src/core/hle/service/apt/bcfnt/bcfnt.h
index 388c6bea0..8936dcf63 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.h
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.h
@@ -22,6 +22,11 @@ struct CFNT {
u32_le num_blocks;
};
+struct SectionHeader {
+ u8 magic[4];
+ u32_le section_size;
+};
+
struct FINF {
u8 magic[4];
u32_le section_size;
@@ -75,12 +80,13 @@ struct CWDH {
};
/**
- * Relocates the internal addresses of the BCFNT Shared Font to the new base.
+ * Relocates the internal addresses of the BCFNT Shared Font to the new base. The current base will
+ * be auto-detected based on the file headers.
+ *
* @param shared_font SharedMemory object that contains the Shared Font
- * @param previous_address Previous address at which the offsets in the structure were based.
* @param new_address New base for the offsets in the structure.
*/
-void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address);
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address);
} // namespace BCFNT
} // namespace APT
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 77261eafe..1b6733a79 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -24,6 +24,7 @@ void Apply() {
VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution;
AudioCore::SelectSink(values.sink_id);
+ AudioCore::EnableStretching(values.enable_audio_stretching);
}
diff --git a/src/core/settings.h b/src/core/settings.h
index 34ff22f3f..fb3fbe391 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -82,6 +82,7 @@ struct Values {
// Audio
std::string sink_id;
+ bool enable_audio_stretching;
// Debugging
bool use_gdbstub;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 36513dedc..3de372f67 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -400,6 +400,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
default:
LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input);
UNIMPLEMENTED();
+ index = "0.0";
break;
}