summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd
diff options
context:
space:
mode:
author hap <happppp@users.noreply.github.com>2026-04-05 01:26:31 +0200
committer hap <happppp@users.noreply.github.com>2026-04-05 01:27:05 +0200
commitad86783ce19b346c6d987c35a2670640d6563736 (patch)
treea23290b02262ce02e0c12c6beaecd8efbffe481c /src/osd
parentddaf4bc23874813057c33c7bdea8f7b2dee46e55 (diff)
sound_module: crossfade abuffer on overrun
Diffstat (limited to 'src/osd')
-rw-r--r--src/osd/modules/sound/sound_module.cpp80
-rw-r--r--src/osd/modules/sound/sound_module.h9
2 files changed, 64 insertions, 25 deletions
diff --git a/src/osd/modules/sound/sound_module.cpp b/src/osd/modules/sound/sound_module.cpp
index de914e51080..93fd418fc98 100644
--- a/src/osd/modules/sound/sound_module.cpp
+++ b/src/osd/modules/sound/sound_module.cpp
@@ -19,6 +19,7 @@ sound_module::abuffer::abuffer(uint32_t channels, uint32_t rate) noexcept :
m_rate(rate),
m_max_buffers(8),
m_hindex(0),
+ m_internal_get(false),
m_last_sample(channels, 0)
{
clear();
@@ -36,7 +37,7 @@ void sound_module::abuffer::clear()
m_used_buffers = 0;
m_used_buffers_prev = 0;
m_overrun = false;
- std::fill(m_history.begin(), m_history.end(), 0);
+ m_history.fill(0);
m_delta = 0;
m_delta2 = 0;
@@ -44,12 +45,13 @@ void sound_module::abuffer::clear()
m_overruns = 0;
}
-void sound_module::abuffer::get(int16_t *data, uint32_t samples) noexcept
+void sound_module::abuffer::get(int16_t *data, uint32_t samples, bool internal) noexcept
{
m_delta -= samples;
m_delta2 -= samples;
uint32_t pos = 0;
while(pos != samples) {
+ // on underrun, fill buffer with last sample to prevent audible pop
if(!m_used_buffers) {
m_delta2 += samples - pos;
m_underruns++;
@@ -62,35 +64,79 @@ void sound_module::abuffer::get(int16_t *data, uint32_t samples) noexcept
}
auto &buf = m_buffers.front();
- if(buf.m_data.empty()) {
+ if(buf.data.empty()) {
pop_buffer();
continue;
}
- uint32_t avail = (buf.m_data.size() / m_channels) - buf.m_cpos;
+ uint32_t avail = (buf.data.size() / m_channels) - buf.cpos;
if(avail > (samples - pos)) {
avail = samples - pos;
- std::copy_n(buf.m_data.data() + (buf.m_cpos * m_channels), avail * m_channels, data);
- buf.m_cpos += avail;
+ std::copy_n(buf.data.data() + (buf.cpos * m_channels), avail * m_channels, data);
+ buf.cpos += avail;
break;
}
- std::copy_n(buf.m_data.data() + (buf.m_cpos * m_channels), avail * m_channels, data);
+ std::copy_n(buf.data.data() + (buf.cpos * m_channels), avail * m_channels, data);
pop_buffer();
pos += avail;
data += avail * m_channels;
}
+ m_internal_get = internal;
//printf("%d -%d +%d # %d %d\n", m_used_buffers, m_underruns, m_overruns, m_delta, m_delta2);
}
+void sound_module::abuffer::flush_buffers(uint32_t remain)
+{
+ for(uint32_t i = 0; i != m_used_buffers - remain; i++)
+ m_delta2 -= (m_buffers[i].data.size() / m_channels - m_buffers[i].cpos);
+
+ // number of samples to crossfade (eg. 128 when samplerate is 48000)
+ const uint32_t samples = std::max(m_rate / 375, 2U);
+
+ // get crossfade source chunk
+ if(!m_internal_get || m_last_fade.size() != samples * m_channels) {
+ m_last_fade.resize(samples * m_channels);
+ get(m_last_fade.data(), samples, true);
+ m_delta += samples;
+ m_delta2 += samples;
+ }
+
+ // flush buffers until [remain] are left
+ if(m_used_buffers > remain) {
+ for(uint32_t i = 0; i < remain; i++) {
+ using std::swap;
+ swap(m_buffers[i], m_buffers[m_used_buffers + i - remain]);
+ }
+ m_used_buffers = remain;
+ }
+
+ if (!m_used_buffers || samples > (m_buffers[0].data.size() / m_channels - m_buffers[0].cpos))
+ return;
+ int16_t *dest = &m_buffers[0].data[m_channels * m_buffers[0].cpos];
+
+ // crossfade into front of buffer to reduce clicks
+ for(uint32_t i = 0; i < samples; i++) {
+ int32_t gain_b = (i << 15) / (samples - 1);
+ int32_t gain_a = 32768 - gain_b;
+
+ for(uint32_t ch = 0; ch < m_channels; ch++) {
+ uint32_t j = i * m_channels + ch;
+
+ int32_t mix = (m_last_fade[j] * gain_a + dest[j] * gain_b) >> 15;
+ dest[j] = static_cast<int16_t>(mix);
+ }
+ }
+}
+
void sound_module::abuffer::push(const int16_t *data, uint32_t samples)
{
m_delta += samples;
m_delta2 += samples;
auto &buf = push_buffer();
- buf.m_cpos = 0;
- buf.m_data.resize(samples * m_channels);
- std::copy_n(data, samples * m_channels, buf.m_data.data());
+ buf.cpos = 0;
+ buf.data.resize(samples * m_channels);
+ std::copy_n(data, samples * m_channels, buf.data.data());
std::copy_n(data + ((samples - 1) * m_channels), m_channels, m_last_sample.data());
// maximum number of buffers relative to samples
@@ -104,21 +150,11 @@ void sound_module::abuffer::push(const int16_t *data, uint32_t samples)
m_history[m_hindex] = m_used_buffers_prev - m_used_buffers;
m_hindex = (m_hindex + 1) % m_history.size();
- auto flush_buffers = [&](uint32_t n) {
- for(uint32_t i = 0; i != m_used_buffers - n; i++)
- m_delta2 -= (m_buffers[i].m_data.size() / m_channels - m_buffers[i].m_cpos);
- for(uint32_t i = 0; i < n; i++) {
- using std::swap;
- swap(m_buffers[i], m_buffers[m_used_buffers + i - n]);
- }
- m_used_buffers = n;
- };
-
if(m_overrun && std::accumulate(m_history.begin(), m_history.end(), 0) >= -2) {
if(m_used_buffers > min_buffers) {
// once it's stabilized after an overrun, reduce buffers to minimum latency
flush_buffers(min_buffers);
- std::fill(m_history.begin(), m_history.end(), 0);
+ m_history.fill(0);
}
m_overrun = false;
} else if(m_used_buffers > max_buffers) {
@@ -136,7 +172,7 @@ uint32_t sound_module::abuffer::available() const noexcept
{
uint32_t result = 0;
for(uint32_t i = 0; m_used_buffers > i; ++i)
- result += (m_buffers[i].m_data.size() / m_channels) - m_buffers[i].m_cpos;
+ result += (m_buffers[i].data.size() / m_channels) - m_buffers[i].cpos;
return result;
}
diff --git a/src/osd/modules/sound/sound_module.h b/src/osd/modules/sound/sound_module.h
index b160a14dd1b..7279d922439 100644
--- a/src/osd/modules/sound/sound_module.h
+++ b/src/osd/modules/sound/sound_module.h
@@ -41,15 +41,15 @@ protected:
abuffer(uint32_t channels, uint32_t rate) noexcept;
void set_latency(float latency);
void clear();
- void get(int16_t *data, uint32_t samples) noexcept;
+ void get(int16_t *data, uint32_t samples, bool internal = false) noexcept;
void push(const int16_t *data, uint32_t samples);
uint32_t channels() const noexcept { return m_channels; }
uint32_t available() const noexcept;
private:
struct buffer {
- uint32_t m_cpos;
- std::vector<int16_t> m_data;
+ uint32_t cpos;
+ std::vector<int16_t> data;
buffer() noexcept = default;
buffer(const buffer &) = default;
@@ -59,6 +59,7 @@ protected:
};
void pop_buffer() noexcept;
+ void flush_buffers(uint32_t remain);
buffer &push_buffer();
uint32_t m_channels;
@@ -69,6 +70,8 @@ protected:
std::array<int32_t, 8> m_history;
uint8_t m_hindex;
bool m_overrun;
+ bool m_internal_get;
+ std::vector<int16_t> m_last_fade;
std::vector<int16_t> m_last_sample;
std::vector<buffer> m_buffers;