Volume.cxx 6.72 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "Volume.hxx"
21
#include "Silence.hxx"
22
#include "Traits.hxx"
23
#include "util/ConstBuffer.hxx"
24
#include "util/WritableBuffer.hxx"
25
#include "util/RuntimeError.hxx"
26
#include "util/TransformN.hxx"
27

28
#include "Dither.cxx" // including the .cxx file to get inlined templates
29

30
#include <cassert>
31
#include <cstdint>
32

33 34
#include <string.h>

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
#if GCC_OLDER_THAN(8,0)
/* GCC 6.3 emits this bogus warning in PcmVolumeConvert() because it
   checks an unreachable branch */
#pragma GCC diagnostic ignored "-Wshift-count-overflow"
#endif

/**
 * Apply software volume, converting to a different sample type.
 */
template<SampleFormat SF, SampleFormat DF,
	 class STraits=SampleTraits<SF>,
	 class DTraits=SampleTraits<DF>>
#if !GCC_OLDER_THAN(8,0)
constexpr
#endif
static typename DTraits::value_type
PcmVolumeConvert(typename STraits::value_type _sample, int volume) noexcept
{
	typename STraits::long_type sample(_sample);
	sample *= volume;

	static_assert(DTraits::BITS > STraits::BITS,
		      "Destination sample must be larger than source sample");

	/* after multiplying with the volume value, the "sample"
	   variable contains this number of precision bits: source
	   bits plus the volume bits */
	constexpr unsigned BITS = STraits::BITS + PCM_VOLUME_BITS;

	/* .. and now we need to scale to the requested destination
	   bits */

	typename DTraits::value_type result;
	if (BITS > DTraits::BITS)
		result = sample >> (BITS - DTraits::BITS);
	else if (BITS < DTraits::BITS)
		result = sample << (DTraits::BITS - BITS);
	else
		result = sample;

	return result;
}

78 79
template<SampleFormat F, class Traits=SampleTraits<F>>
static inline typename Traits::value_type
80 81
pcm_volume_sample(PcmDither &dither,
		  typename Traits::value_type _sample,
Max Kellermann's avatar
Max Kellermann committed
82
		  int volume) noexcept
83 84 85
{
	typename Traits::long_type sample(_sample);

86 87 88
	return dither.DitherShift<typename Traits::long_type,
				  Traits::BITS + PCM_VOLUME_BITS,
				  Traits::BITS>(sample * volume);
89 90
}

91
template<SampleFormat F, class Traits=SampleTraits<F>>
92
static void
93
pcm_volume_change(PcmDither &dither,
94 95
		  typename Traits::pointer dest,
		  typename Traits::const_pointer src,
96
		  size_t n,
Max Kellermann's avatar
Max Kellermann committed
97
		  int volume) noexcept
98
{
99 100 101 102 103
	transform_n(src, n, dest,
		    [&dither, volume](auto x){
			    return pcm_volume_sample<F, Traits>(dither, x,
								volume);
		    });
104 105 106
}

static void
107
pcm_volume_change_8(PcmDither &dither,
108
		    int8_t *dest, const int8_t *src, size_t n,
Max Kellermann's avatar
Max Kellermann committed
109
		    int volume) noexcept
110
{
111
	pcm_volume_change<SampleFormat::S8>(dither, dest, src, n, volume);
112 113 114
}

static void
115
pcm_volume_change_16(PcmDither &dither,
116
		     int16_t *dest, const int16_t *src, size_t n,
Max Kellermann's avatar
Max Kellermann committed
117
		     int volume) noexcept
118
{
119
	pcm_volume_change<SampleFormat::S16>(dither, dest, src, n, volume);
120 121
}

122 123 124 125
static void
PcmVolumeChange16to32(int32_t *dest, const int16_t *src, size_t n,
		      int volume) noexcept
{
126 127 128 129 130 131
	transform_n(src, n, dest,
		    [volume](auto x){
			    return PcmVolumeConvert<SampleFormat::S16,
						    SampleFormat::S24_P32>(x,
									   volume);
		    });
132 133
}

134
static void
135
pcm_volume_change_24(PcmDither &dither,
136
		     int32_t *dest, const int32_t *src, size_t n,
Max Kellermann's avatar
Max Kellermann committed
137
		     int volume) noexcept
138
{
139
	pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, n,
140
						 volume);
141 142
}

143
static void
144
pcm_volume_change_32(PcmDither &dither,
145
		     int32_t *dest, const int32_t *src, size_t n,
Max Kellermann's avatar
Max Kellermann committed
146
		     int volume) noexcept
147
{
148
	pcm_volume_change<SampleFormat::S32>(dither, dest, src, n, volume);
149 150
}

151
static void
152
pcm_volume_change_float(float *dest, const float *src, size_t n,
Max Kellermann's avatar
Max Kellermann committed
153
			float volume) noexcept
154
{
155 156
	transform_n(src, n, dest,
		    [volume](float x){ return x * volume; });
157 158
}

159
SampleFormat
160
PcmVolume::Open(SampleFormat _format, bool allow_convert)
161
{
162
	assert(format == SampleFormat::UNDEFINED);
163

164 165
	convert = false;

166 167
	switch (_format) {
	case SampleFormat::UNDEFINED:
168 169
		throw FormatRuntimeError("Software volume for %s is not implemented",
					 sample_format_to_string(_format));
170 171

	case SampleFormat::S8:
172 173
		break;

174
	case SampleFormat::S16:
175 176 177 178 179 180 181 182 183 184
		if (allow_convert) {
			/* convert S16 to S24 to avoid discarding too
			   many bits of precision in this stage */
			format = _format;
			convert = true;
			return SampleFormat::S24_P32;
		}

		break;

185 186 187 188
	case SampleFormat::S24_P32:
	case SampleFormat::S32:
	case SampleFormat::FLOAT:
		break;
189 190 191 192

	case SampleFormat::DSD:
		// TODO: implement this; currently, it's a no-op
		break;
193 194
	}

195
	return format = _format;
196 197 198
}

ConstBuffer<void>
199
PcmVolume::Apply(ConstBuffer<void> src) noexcept
200
{
201
	if (volume == PCM_VOLUME_1 && !convert)
202 203
		return src;

204
	size_t dest_size = src.size;
205 206 207 208 209 210 211
	if (convert) {
		assert(format == SampleFormat::S16);

		/* converting to S24_P32 */
		dest_size *= 2;
	}

212
	void *data = buffer.Get(dest_size);
213 214 215

	if (volume == 0) {
		/* optimized special case: 0% volume = memset(0) */
216 217
		PcmSilence({data, dest_size}, format);
		return { data, dest_size };
218 219
	}

220
	switch (format) {
221
	case SampleFormat::UNDEFINED:
222 223
		assert(false);
		gcc_unreachable();
224

225
	case SampleFormat::S8:
226
		pcm_volume_change_8(dither, (int8_t *)data,
227
				    (const int8_t *)src.data,
228
				    src.size / sizeof(int8_t),
Max Kellermann's avatar
Max Kellermann committed
229
				    volume);
230
		break;
231

232
	case SampleFormat::S16:
233 234 235 236 237 238 239 240 241 242
		if (convert)
			PcmVolumeChange16to32((int32_t *)data,
					      (const int16_t *)src.data,
					      src.size / sizeof(int16_t),
					      volume);
		else
			pcm_volume_change_16(dither, (int16_t *)data,
					     (const int16_t *)src.data,
					     src.size / sizeof(int16_t),
					     volume);
243
		break;
244

245
	case SampleFormat::S24_P32:
246
		pcm_volume_change_24(dither, (int32_t *)data,
247
				     (const int32_t *)src.data,
248
				     src.size / sizeof(int32_t),
Max Kellermann's avatar
Max Kellermann committed
249
				     volume);
250
		break;
251

252
	case SampleFormat::S32:
253
		pcm_volume_change_32(dither, (int32_t *)data,
254
				     (const int32_t *)src.data,
255
				     src.size / sizeof(int32_t),
Max Kellermann's avatar
Max Kellermann committed
256
				     volume);
257
		break;
258

259
	case SampleFormat::FLOAT:
260 261
		pcm_volume_change_float((float *)data,
					(const float *)src.data,
262
					src.size / sizeof(float),
263
					pcm_volume_to_float(volume));
264
		break;
265 266 267 268

	case SampleFormat::DSD:
		// TODO: implement this; currently, it's a no-op
		return src;
269
	}
270

271
	return { data, dest_size };
272
}