1 /* FAudio - XAudio Reimplementation for FNA
2  *
3  * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team
4  *
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not
14  * claim that you wrote the original software. If you use this software in a
15  * product, an acknowledgment in the product documentation would be
16  * appreciated but is not required.
17  *
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  * misrepresented as being the original software.
20  *
21  * 3. This notice may not be removed or altered from any source distribution.
22  *
23  * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
24  *
25  */
26 
27 #include "FAudioFX.h"
28 #include "FAudio_internal.h"
29 
30 /* Volume Meter FAPO Implementation */
31 
32 const FAudioGUID FAudioFX_CLSID_AudioVolumeMeter = /* 2.7 */
33 {
34 	0xCAC1105F,
35 	0x619B,
36 	0x4D04,
37 	{
38 		0x83,
39 		0x1A,
40 		0x44,
41 		0xE1,
42 		0xCB,
43 		0xF1,
44 		0x2D,
45 		0x57
46 	}
47 };
48 
49 static FAPORegistrationProperties VolumeMeterProperties =
50 {
51 	/* .clsid = */ {0},
52 	/* .FriendlyName = */
53 	{
54 		'V', 'o', 'l', 'u', 'm', 'e', 'M', 'e', 't', 'e', 'r', '\0'
55 	},
56 	/*.CopyrightInfo = */
57 	{
58 		'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')',
59 		'E', 't', 'h', 'a', 'n', ' ', 'L', 'e', 'e', '\0'
60 	},
61 	/*.MajorVersion = */ 0,
62 	/*.MinorVersion = */ 0,
63 	/*.Flags = */(
64 		FAPO_FLAG_CHANNELS_MUST_MATCH |
65 		FAPO_FLAG_FRAMERATE_MUST_MATCH |
66 		FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH |
67 		FAPO_FLAG_BUFFERCOUNT_MUST_MATCH |
68 		FAPO_FLAG_INPLACE_SUPPORTED |
69 		FAPO_FLAG_INPLACE_REQUIRED
70 	),
71 	/*.MinInputBufferCount = */ 1,
72 	/*.MaxInputBufferCount = */  1,
73 	/*.MinOutputBufferCount = */ 1,
74 	/*.MaxOutputBufferCount =*/ 1
75 };
76 
77 typedef struct FAudioFXVolumeMeter
78 {
79 	FAPOBase base;
80 	uint16_t channels;
81 } FAudioFXVolumeMeter;
82 
FAudioFXVolumeMeter_LockForProcess(FAudioFXVolumeMeter * fapo,uint32_t InputLockedParameterCount,const FAPOLockForProcessBufferParameters * pInputLockedParameters,uint32_t OutputLockedParameterCount,const FAPOLockForProcessBufferParameters * pOutputLockedParameters)83 uint32_t FAudioFXVolumeMeter_LockForProcess(
84 	FAudioFXVolumeMeter *fapo,
85 	uint32_t InputLockedParameterCount,
86 	const FAPOLockForProcessBufferParameters *pInputLockedParameters,
87 	uint32_t OutputLockedParameterCount,
88 	const FAPOLockForProcessBufferParameters *pOutputLockedParameters
89 ) {
90 	FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*)
91 		fapo->base.m_pParameterBlocks;
92 
93 	/* Verify parameter counts... */
94 	if (	InputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinInputBufferCount ||
95 		InputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxInputBufferCount ||
96 		OutputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinOutputBufferCount ||
97 		OutputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxOutputBufferCount	)
98 	{
99 		return FAUDIO_E_INVALID_ARG;
100 	}
101 
102 
103 	/* Validate input/output formats */
104 	#define VERIFY_FORMAT_FLAG(flag, prop) \
105 		if (	(fapo->base.m_pRegistrationProperties->Flags & flag) && \
106 			(pInputLockedParameters->pFormat->prop != pOutputLockedParameters->pFormat->prop)	) \
107 		{ \
108 			return FAUDIO_E_INVALID_ARG; \
109 		}
110 	VERIFY_FORMAT_FLAG(FAPO_FLAG_CHANNELS_MUST_MATCH, nChannels)
111 	VERIFY_FORMAT_FLAG(FAPO_FLAG_FRAMERATE_MUST_MATCH, nSamplesPerSec)
112 	VERIFY_FORMAT_FLAG(FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH, wBitsPerSample)
113 	#undef VERIFY_FORMAT_FLAG
114 	if (	(fapo->base.m_pRegistrationProperties->Flags & FAPO_FLAG_BUFFERCOUNT_MUST_MATCH) &&
115 		(InputLockedParameterCount != OutputLockedParameterCount)	)
116 	{
117 		return FAUDIO_E_INVALID_ARG;
118 	}
119 
120 	/* Allocate volume meter arrays */
121 	fapo->channels = pInputLockedParameters->pFormat->nChannels;
122 	levels[0].pPeakLevels = (float*) fapo->base.pMalloc(
123 		fapo->channels * sizeof(float) * 6
124 	);
125 	FAudio_zero(levels[0].pPeakLevels, fapo->channels * sizeof(float) * 6);
126 	levels[0].pRMSLevels = levels[0].pPeakLevels + fapo->channels;
127 	levels[1].pPeakLevels = levels[0].pPeakLevels + (fapo->channels * 2);
128 	levels[1].pRMSLevels = levels[0].pPeakLevels + (fapo->channels * 3);
129 	levels[2].pPeakLevels = levels[0].pPeakLevels + (fapo->channels * 4);
130 	levels[2].pRMSLevels = levels[0].pPeakLevels + (fapo->channels * 5);
131 
132 	fapo->base.m_fIsLocked = 1;
133 	return 0;
134 }
135 
FAudioFXVolumeMeter_UnlockForProcess(FAudioFXVolumeMeter * fapo)136 void FAudioFXVolumeMeter_UnlockForProcess(FAudioFXVolumeMeter *fapo)
137 {
138 	FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*)
139 		fapo->base.m_pParameterBlocks;
140 	fapo->base.pFree(levels[0].pPeakLevels);
141 	fapo->base.m_fIsLocked = 0;
142 }
143 
FAudioFXVolumeMeter_Process(FAudioFXVolumeMeter * fapo,uint32_t InputProcessParameterCount,const FAPOProcessBufferParameters * pInputProcessParameters,uint32_t OutputProcessParameterCount,FAPOProcessBufferParameters * pOutputProcessParameters,int32_t IsEnabled)144 void FAudioFXVolumeMeter_Process(
145 	FAudioFXVolumeMeter *fapo,
146 	uint32_t InputProcessParameterCount,
147 	const FAPOProcessBufferParameters* pInputProcessParameters,
148 	uint32_t OutputProcessParameterCount,
149 	FAPOProcessBufferParameters* pOutputProcessParameters,
150 	int32_t IsEnabled
151 ) {
152 	float peak;
153 	float total;
154 	float *buffer;
155 	uint32_t i, j;
156 	FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*)
157 		FAPOBase_BeginProcess(&fapo->base);
158 
159 	/* TODO: This could probably be SIMD-ified... */
160 	for (i = 0; i < fapo->channels; i += 1)
161 	{
162 		peak = 0.0f;
163 		total = 0.0f;
164 		buffer = ((float*) pInputProcessParameters->pBuffer) + i;
165 		for (j = 0; j < pInputProcessParameters->ValidFrameCount; j += 1, buffer += fapo->channels)
166 		{
167 			const float sampleAbs = FAudio_fabsf(*buffer);
168 			if (sampleAbs > peak)
169 			{
170 				peak = sampleAbs;
171 			}
172 			total += (*buffer) * (*buffer);
173 		}
174 		levels->pPeakLevels[i] = peak;
175 		levels->pRMSLevels[i] = FAudio_sqrtf(
176 			total / pInputProcessParameters->ValidFrameCount
177 		);
178 	}
179 
180 	FAPOBase_EndProcess(&fapo->base);
181 }
182 
FAudioFXVolumeMeter_GetParameters(FAudioFXVolumeMeter * fapo,FAudioFXVolumeMeterLevels * pParameters,uint32_t ParameterByteSize)183 void FAudioFXVolumeMeter_GetParameters(
184 	FAudioFXVolumeMeter *fapo,
185 	FAudioFXVolumeMeterLevels *pParameters,
186 	uint32_t ParameterByteSize
187 ) {
188 	FAudioFXVolumeMeterLevels *levels = (FAudioFXVolumeMeterLevels*)
189 		fapo->base.m_pCurrentParameters;
190 	FAudio_assert(ParameterByteSize == sizeof(FAudioFXVolumeMeterLevels));
191 	FAudio_assert(pParameters->ChannelCount == fapo->channels);
192 
193 	/* Copy what's current as of the last Process */
194 	if (pParameters->pPeakLevels != NULL)
195 	{
196 		FAudio_memcpy(
197 			pParameters->pPeakLevels,
198 			levels->pPeakLevels,
199 			fapo->channels * sizeof(float)
200 		);
201 	}
202 	if (pParameters->pRMSLevels != NULL)
203 	{
204 		FAudio_memcpy(
205 			pParameters->pRMSLevels,
206 			levels->pRMSLevels,
207 			fapo->channels * sizeof(float)
208 		);
209 	}
210 }
211 
FAudioFXVolumeMeter_Free(void * fapo)212 void FAudioFXVolumeMeter_Free(void* fapo)
213 {
214 	FAudioFXVolumeMeter *volumemeter = (FAudioFXVolumeMeter*) fapo;
215 	volumemeter->base.pFree(volumemeter->base.m_pParameterBlocks);
216 	volumemeter->base.pFree(fapo);
217 }
218 
219 /* Public API */
220 
FAudioCreateVolumeMeter(FAPO ** ppApo,uint32_t Flags)221 uint32_t FAudioCreateVolumeMeter(FAPO** ppApo, uint32_t Flags)
222 {
223 	return FAudioCreateVolumeMeterWithCustomAllocatorEXT(
224 		ppApo,
225 		Flags,
226 		FAudio_malloc,
227 		FAudio_free,
228 		FAudio_realloc
229 	);
230 }
231 
FAudioCreateVolumeMeterWithCustomAllocatorEXT(FAPO ** ppApo,uint32_t Flags,FAudioMallocFunc customMalloc,FAudioFreeFunc customFree,FAudioReallocFunc customRealloc)232 uint32_t FAudioCreateVolumeMeterWithCustomAllocatorEXT(
233 	FAPO** ppApo,
234 	uint32_t Flags,
235 	FAudioMallocFunc customMalloc,
236 	FAudioFreeFunc customFree,
237 	FAudioReallocFunc customRealloc
238 ) {
239 	/* Allocate... */
240 	FAudioFXVolumeMeter *result = (FAudioFXVolumeMeter*) customMalloc(
241 		sizeof(FAudioFXVolumeMeter)
242 	);
243 	uint8_t *params = (uint8_t*) customMalloc(
244 		sizeof(FAudioFXVolumeMeterLevels) * 3
245 	);
246 	FAudio_zero(params, sizeof(FAudioFXVolumeMeterLevels) * 3);
247 
248 	/* Initialize... */
249 	FAudio_memcpy(
250 		&VolumeMeterProperties.clsid,
251 		&FAudioFX_CLSID_AudioVolumeMeter,
252 		sizeof(FAudioGUID)
253 	);
254 	CreateFAPOBaseWithCustomAllocatorEXT(
255 		&result->base,
256 		&VolumeMeterProperties,
257 		params,
258 		sizeof(FAudioFXVolumeMeterLevels),
259 		1,
260 		customMalloc,
261 		customFree,
262 		customRealloc
263 	);
264 
265 	/* Function table... */
266 	result->base.base.LockForProcess = (LockForProcessFunc)
267 		FAudioFXVolumeMeter_LockForProcess;
268 	result->base.base.UnlockForProcess = (UnlockForProcessFunc)
269 		FAudioFXVolumeMeter_UnlockForProcess;
270 	result->base.base.Process = (ProcessFunc)
271 		FAudioFXVolumeMeter_Process;
272 	result->base.base.GetParameters = (GetParametersFunc)
273 		FAudioFXVolumeMeter_GetParameters;
274 	result->base.Destructor = FAudioFXVolumeMeter_Free;
275 
276 	/* Finally. */
277 	*ppApo = &result->base.base;
278 	return 0;
279 }
280 
281 /* vim: set noexpandtab shiftwidth=8 tabstop=8: */
282