diff options
| author | 2026-04-05 01:26:31 +0200 | |
|---|---|---|
| committer | 2026-04-05 01:27:05 +0200 | |
| commit | ad86783ce19b346c6d987c35a2670640d6563736 (patch) | |
| tree | a23290b02262ce02e0c12c6beaecd8efbffe481c /src | |
| parent | ddaf4bc23874813057c33c7bdea8f7b2dee46e55 (diff) | |
sound_module: crossfade abuffer on overrun
Diffstat (limited to 'src')
| -rw-r--r-- | src/osd/modules/sound/sound_module.cpp | 80 | ||||
| -rw-r--r-- | src/osd/modules/sound/sound_module.h | 9 |
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; |
