1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #ifndef MPD_PCM_REST_BUFFER_HXX
21 #define MPD_PCM_REST_BUFFER_HXX
22 
23 #include "ChannelDefs.hxx"
24 
25 #include <algorithm>
26 #include <cassert>
27 #include <cstddef>
28 
29 template<typename T> struct ConstBuffer;
30 class PcmBuffer;
31 
32 /**
33  * A buffer which helps with conversion classes which need to handle
34  * multiple frames at a time; it stores the rest of a previous
35  * operation in order to use it in the next call.
36  */
37 template<typename T, unsigned n_frames>
38 class PcmRestBuffer {
39 	unsigned size, capacity;
40 	T data[n_frames * MAX_CHANNELS];
41 
42 public:
Open(unsigned channels)43 	void Open(unsigned channels) noexcept {
44 		assert(audio_valid_channel_count(channels));
45 
46 		capacity = n_frames * channels;
47 		size = 0;
48 	}
49 
50 	/**
51 	 * @return the size of one input block in #T samples
52 	 */
GetInputBlockSize() const53 	size_t GetInputBlockSize() const noexcept {
54 		return capacity;
55 	}
56 
GetChannelCount() const57 	unsigned GetChannelCount() const noexcept {
58 		return capacity / n_frames;
59 	}
60 
Reset()61 	void Reset() noexcept {
62 		assert(audio_valid_channel_count(capacity / n_frames));
63 
64 		size = 0;
65 	}
66 
67 private:
Complete(ConstBuffer<T> & src)68 	ConstBuffer<T> Complete(ConstBuffer<T> &src) noexcept {
69 		assert(audio_valid_channel_count(GetChannelCount()));
70 		assert(src.size % GetChannelCount() == 0);
71 
72 		if (size == 0)
73 			return nullptr;
74 
75 		size_t missing = capacity - size;
76 		size_t n = std::min(missing, src.size);
77 		std::copy_n(src.begin(), n, &data[size]);
78 		src.skip_front(n);
79 		size += n;
80 
81 		if (size < capacity)
82 			return nullptr;
83 
84 		size = 0;
85 		return {data, capacity};
86 	}
87 
Append(ConstBuffer<T> src)88 	void Append(ConstBuffer<T> src) noexcept {
89 		assert(audio_valid_channel_count(GetChannelCount()));
90 		assert(src.size % GetChannelCount() == 0);
91 		assert(size + src.size < capacity);
92 
93 		std::copy_n(src.begin(), src.size, &data[size]);
94 		size += src.size;
95 	}
96 
97 public:
98 	/**
99 	 * A helper function which attempts to complete the rest
100 	 * buffer, allocates a destination buffer and invokes the
101 	 * given function for both the rest buffer and the new source
102 	 * buffer.  In the end, it copies more remaining data to the
103 	 * rest buffer.
104 	 *
105 	 * @param U the destination data type
106 	 * @param F the inner process function
107 	 * @param dest_block_size how many destination samples in one block?
108 	 * @return the destination buffer (allocated from #buffer);
109 	 * may be empty
110 	 */
111 	template<typename U, typename F>
Process(PcmBuffer & buffer,ConstBuffer<T> src,size_t dest_block_size,F && f)112 	ConstBuffer<U> Process(PcmBuffer &buffer, ConstBuffer<T> src,
113 			       size_t dest_block_size,
114 			       F &&f) {
115 		assert(dest_block_size % GetChannelCount() == 0);
116 
117 		const auto previous_rest = Complete(src);
118 		assert(previous_rest.size == 0 ||
119 		       previous_rest.size == capacity);
120 
121 		const size_t previous_rest_blocks = !previous_rest.empty();
122 		const size_t src_blocks = src.size / capacity;
123 		const size_t next_rest_samples = src.size % capacity;
124 		const size_t dest_blocks = previous_rest_blocks + src_blocks;
125 		const size_t dest_samples = dest_blocks * dest_block_size;
126 
127 		const auto dest0 = buffer.GetT<U>(dest_samples);
128 		auto dest = dest0;
129 
130 		if (!previous_rest.empty()) {
131 			f(dest, previous_rest.data, previous_rest_blocks);
132 			dest += dest_block_size;
133 		}
134 
135 		f(dest, src.data, src_blocks);
136 
137 		if (next_rest_samples > 0)
138 			Append({src.data + src_blocks * capacity,
139 				next_rest_samples});
140 
141 		return { dest0, dest_samples };
142 	}
143 };
144 
145 #endif
146