8 #define RMS_WINDOW_SIZE (1<<7)
9 #define RMS_WINDOW_MASK (RMS_WINDOW_SIZE-1)
10 #define RMS_VALUE_MAX (1<<24)
12 #define LOOKAHEAD_SIZE (1<<13)
13 #define LOOKAHEAD_MASK (LOOKAHEAD_SIZE-1)
15 static_assert(RMS_VALUE_MAX
< (UINT_MAX
/ RMS_WINDOW_SIZE
), "RMS_VALUE_MAX is too big");
17 typedef struct Compressor
{
33 ALfloat Envelope
[BUFFERSIZE
];
37 /* Multichannel compression is linked via one of two modes:
39 * Summed - Absolute sum of all channels.
40 * Maxed - Absolute maximum of any channel.
42 static void SumChannels(Compressor
*Comp
, const ALsizei NumChans
, const ALsizei SamplesToDo
,
43 ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
47 for(i
= 0;i
< SamplesToDo
;i
++)
48 Comp
->Envelope
[i
] = 0.0f
;
50 for(c
= 0;c
< NumChans
;c
++)
52 for(i
= 0;i
< SamplesToDo
;i
++)
53 Comp
->Envelope
[i
] += OutBuffer
[c
][i
];
56 for(i
= 0;i
< SamplesToDo
;i
++)
57 Comp
->Envelope
[i
] = fabsf(Comp
->Envelope
[i
]);
60 static void MaxChannels(Compressor
*Comp
, const ALsizei NumChans
, const ALsizei SamplesToDo
,
61 ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
65 for(i
= 0;i
< SamplesToDo
;i
++)
66 Comp
->Envelope
[i
] = 0.0f
;
68 for(c
= 0;c
< NumChans
;c
++)
70 for(i
= 0;i
< SamplesToDo
;i
++)
71 Comp
->Envelope
[i
] = maxf(Comp
->Envelope
[i
], fabsf(OutBuffer
[c
][i
]));
75 /* Envelope detection/sensing can be done via:
77 * RMS - Rectangular windowed root mean square of linking stage.
78 * Peak - Implicit output from linking stage.
80 static void RmsDetection(Compressor
*Comp
, const ALsizei SamplesToDo
)
82 ALuint sum
= Comp
->RmsSum
;
83 ALuint
*window
= Comp
->RmsWindow
;
84 ALsizei index
= Comp
->RmsIndex
;
87 for(i
= 0;i
< SamplesToDo
;i
++)
89 ALfloat sig
= Comp
->Envelope
[i
];
92 window
[index
] = fastf2i(minf(sig
* sig
* 65536.0f
, RMS_VALUE_MAX
));
94 index
= (index
+ 1) & RMS_WINDOW_MASK
;
96 Comp
->Envelope
[i
] = sqrtf(sum
/ 65536.0f
/ RMS_WINDOW_SIZE
);
100 Comp
->RmsIndex
= index
;
103 /* This isn't a very sophisticated envelope follower, but it gets the job
104 * done. First, it operates at logarithmic scales to keep transitions
105 * appropriate for human hearing. Second, it can apply adaptive (automated)
106 * attack/release adjustments based on the signal.
108 static void FollowEnvelope(Compressor
*Comp
, const ALsizei SamplesToDo
)
110 ALfloat attackMin
= Comp
->AttackMin
;
111 ALfloat attackMax
= Comp
->AttackMax
;
112 ALfloat releaseMin
= Comp
->ReleaseMin
;
113 ALfloat releaseMax
= Comp
->ReleaseMax
;
114 ALfloat last
= Comp
->EnvLast
;
117 for(i
= 0;i
< SamplesToDo
;i
++)
119 ALfloat env
= maxf(-6.0f
, log10f(Comp
->Envelope
[i
]));
120 ALfloat slope
= minf(1.0f
, fabsf(env
- last
) / 4.5f
);
123 last
= minf(env
, last
+ lerp(attackMin
, attackMax
, 1.0f
- (slope
* slope
)));
125 last
= maxf(env
, last
+ lerp(releaseMin
, releaseMax
, 1.0f
- (slope
* slope
)));
127 Comp
->Envelope
[i
] = last
;
130 Comp
->EnvLast
= last
;
133 /* The envelope is converted to control gain with an optional soft knee. */
134 static void EnvelopeGain(Compressor
*Comp
, const ALsizei SamplesToDo
, const ALfloat Slope
)
136 const ALfloat threshold
= Comp
->Threshold
;
137 const ALfloat knee
= Comp
->Knee
;
142 for(i
= 0;i
< SamplesToDo
;i
++)
144 ALfloat gain
= Slope
* (threshold
- Comp
->Envelope
[i
]);
145 Comp
->Envelope
[i
] = powf(10.0f
, minf(0.0f
, gain
));
150 const ALfloat lower
= threshold
- (0.5f
* knee
);
151 const ALfloat upper
= threshold
+ (0.5f
* knee
);
152 const ALfloat m
= 0.5f
* Slope
/ knee
;
154 for(i
= 0;i
< SamplesToDo
;i
++)
156 ALfloat env
= Comp
->Envelope
[i
];
159 if(env
> lower
&& env
< upper
)
160 gain
= m
* (env
- lower
) * (lower
- env
);
162 gain
= Slope
* (threshold
- env
);
164 Comp
->Envelope
[i
] = powf(10.0f
, minf(0.0f
, gain
));
170 Compressor
*CompressorInit(const ALfloat PreGainDb
, const ALfloat PostGainDb
,
171 const ALboolean SummedLink
, const ALboolean RmsSensing
,
172 const ALfloat AttackTimeMin
, const ALfloat AttackTimeMax
,
173 const ALfloat ReleaseTimeMin
, const ALfloat ReleaseTimeMax
,
174 const ALfloat Ratio
, const ALfloat ThresholdDb
,
175 const ALfloat KneeDb
, const ALuint SampleRate
)
181 size
= sizeof(*Comp
);
183 size
+= sizeof(Comp
->RmsWindow
[0]) * RMS_WINDOW_SIZE
;
184 Comp
= al_calloc(16, size
);
186 Comp
->PreGain
= powf(10.0f
, PreGainDb
/ 20.0f
);
187 Comp
->PostGain
= powf(10.0f
, PostGainDb
/ 20.0f
);
188 Comp
->SummedLink
= SummedLink
;
189 Comp
->AttackMin
= 1.0f
/ maxf(0.000001f
, AttackTimeMin
* SampleRate
* logf(10.0f
));
190 Comp
->AttackMax
= 1.0f
/ maxf(0.000001f
, AttackTimeMax
* SampleRate
* logf(10.0f
));
191 Comp
->ReleaseMin
= -1.0f
/ maxf(0.000001f
, ReleaseTimeMin
* SampleRate
* logf(10.0f
));
192 Comp
->ReleaseMax
= -1.0f
/ maxf(0.000001f
, ReleaseTimeMax
* SampleRate
* logf(10.0f
));
194 Comp
->Threshold
= ThresholdDb
/ 20.0f
;
195 Comp
->Knee
= maxf(0.0f
, KneeDb
/ 20.0f
);
196 Comp
->SampleRate
= SampleRate
;
200 Comp
->RmsWindow
= (ALuint
*)(Comp
+1);
202 Comp
->RmsWindow
= NULL
;
205 for(i
= 0;i
< BUFFERSIZE
;i
++)
206 Comp
->Envelope
[i
] = 0.0f
;
207 Comp
->EnvLast
= -6.0f
;
212 ALuint
GetCompressorSampleRate(const Compressor
*Comp
)
214 return Comp
->SampleRate
;
217 void ApplyCompression(Compressor
*Comp
, const ALsizei NumChans
, const ALsizei SamplesToDo
,
218 ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
222 if(Comp
->PreGain
!= 1.0f
)
224 for(c
= 0;c
< NumChans
;c
++)
226 for(i
= 0;i
< SamplesToDo
;i
++)
227 OutBuffer
[c
][i
] *= Comp
->PreGain
;
232 SumChannels(Comp
, NumChans
, SamplesToDo
, OutBuffer
);
234 MaxChannels(Comp
, NumChans
, SamplesToDo
, OutBuffer
);
237 RmsDetection(Comp
, SamplesToDo
);
238 FollowEnvelope(Comp
, SamplesToDo
);
240 if(Comp
->Ratio
> 0.0f
)
241 EnvelopeGain(Comp
, SamplesToDo
, 1.0f
- (1.0f
/ Comp
->Ratio
));
243 EnvelopeGain(Comp
, SamplesToDo
, 1.0f
);
245 if(Comp
->PostGain
!= 1.0f
)
247 for(i
= 0;i
< SamplesToDo
;i
++)
248 Comp
->Envelope
[i
] *= Comp
->PostGain
;
250 for(c
= 0;c
< NumChans
;c
++)
252 for(i
= 0;i
< SamplesToDo
;i
++)
253 OutBuffer
[c
][i
] *= Comp
->Envelope
[i
];