summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/sound/sound_module.cpp
blob: c1e93a26b280d70604de4c36f8a95468882dec59 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// license:BSD-3-Clause
// copyright-holders:O. Galibert


#include "sound_module.h"

#include <algorithm>
#include <cassert>
#include <utility>


sound_module::~sound_module()
{
	// implementing this here forces the vtable and inline virtual member functions to be instantiated
}

sound_module::abuffer::abuffer(uint32_t channels) noexcept : m_channels(channels), m_used_buffers(0), m_last_sample(channels, 0)
{
	m_delta = 0;
	m_delta2 = 0;
}

void sound_module::abuffer::get(int16_t *data, uint32_t samples) noexcept
{
	m_delta -= samples;
	m_delta2 -= samples;
	uint32_t pos = 0;
	while(pos != samples) {
		if(!m_used_buffers) {
			m_delta2 += samples - pos;
			while(pos != samples) {
				std::copy_n(m_last_sample.data(), m_channels, data);
				data += m_channels;
				pos++;
			}
			break;
		}

		auto &buf = m_buffers.front();
		if(buf.m_data.empty()) {
			pop_buffer();
			continue;
		}

		uint32_t avail = (buf.m_data.size() / m_channels) - buf.m_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;
			break;
		}

		std::copy_n(buf.m_data.data() + (buf.m_cpos * m_channels), avail * m_channels, data);
		pop_buffer();
		pos += avail;
		data += avail * m_channels;
	}
	//  printf("# %d %d\n", m_delta, m_delta2);
}

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());
	std::copy_n(data + ((samples - 1) * m_channels), m_channels, m_last_sample.data());

	if(m_used_buffers > 10) {
		for(uint32_t i=0; i != m_used_buffers-10; i++)
			m_delta2 -= (m_buffers[i].m_data.size()/m_channels - m_buffers[i].m_cpos);
		// If there are way too many buffers, drop some so only 10 are left (roughly 0.2s)
		for(unsigned i = 0; 10 > i; ++i) {
			using std::swap;
			swap(m_buffers[i], m_buffers[m_used_buffers + i - 10]);
		}
		m_used_buffers = 10;
	} else if(m_used_buffers >= 5) {
		// If there are too many buffers, remove five samples per buffer
		// to slowly resync to reduce latency (4 seconds to
		// compensate one buffer, roughly)
		m_delta2 -= std::max<uint32_t>(samples / 200, 1);
		buf.m_cpos = std::max<uint32_t>(samples / 200, 1);
	}
	//  printf("# %d %d\n", m_delta, m_delta2);
}

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;
	return result;
}

inline void sound_module::abuffer::pop_buffer() noexcept
{
	assert(m_used_buffers);
	if(--m_used_buffers) {
		auto temp(std::move(m_buffers.front()));
		for(uint32_t i = 0; m_used_buffers > i; ++i)
			m_buffers[i] = std::move(m_buffers[i + 1]);
		m_buffers[m_used_buffers] = std::move(temp);
	}
}

inline sound_module::abuffer::buffer &sound_module::abuffer::push_buffer()
{
	if(m_buffers.size() > m_used_buffers) {
		return m_buffers[m_used_buffers++];
	} else {
		assert(m_buffers.size() == m_used_buffers);
		++m_used_buffers;
		return m_buffers.emplace_back();
	}
}