├── _config.yml ├── Synoptique_AGC.pdf ├── README.md ├── LICENSE.md ├── javascript ├── utils.js ├── abstractnode.js └── peaklimiter.js └── cpp ├── peakLimiter.h └── peakLimiter.cpp /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /Synoptique_AGC.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozlima/PeakLimiter/master/Synoptique_AGC.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Radio France](https://www.radiofrance.fr), [Orange Labs](https://www.orange.fr), [IRCAM](https://www.ircam.fr) and [France Televisions](https://www.francetelevisions.fr) are happy to 2 | announce the release of their open-source Peak Limiter: 3 | 4 | This development allows the owners of web/app players to provide 5 | additional gain while preventing the output signal to clip. 6 | For the mobile listening of programmes that respect the broadcast 7 | dynamic requirements (-24 LKFS/-23 LUFS), this Peak Limiter allows an 8 | additional gain of 8 dB to offer the suitable output level in a noisy 9 | environment. 10 | 11 | This on-demand feature, controlled by the user according to his/her 12 | listening conditions, allows contents providers to deliver the same mix 13 | over broadcast and OTT networks. This Peak Limiter can be driven by 14 | metadata or keeps the signal below 0 dBTP by default. 15 | 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /javascript/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /************************************************************************************/ 29 | /*! 30 | * @brief ensure x is within range [min,max] with saturation (out of place) 31 | * 32 | */ 33 | /************************************************************************************/ 34 | export function clamp( value, min, max ) 35 | { 36 | if( max < min ) 37 | { 38 | throw new Error("Invalid"); 39 | } 40 | 41 | return Math.max( min, Math.min( value, max ) ); 42 | } 43 | 44 | /************************************************************************************/ 45 | /*! 46 | * @brief amplitude decibel to linear gain conversion 47 | * @param[in] dB : value in decibels 48 | * 49 | * @details y = 10^( x / 20 ) 50 | */ 51 | /************************************************************************************/ 52 | export function dB2lin( value ) 53 | { 54 | return Math.pow( 10 , value / 20 ); 55 | } 56 | 57 | //============================================================================== 58 | const utilities = 59 | { 60 | clamp, 61 | dB2lin, 62 | }; 63 | 64 | export default utilities; 65 | -------------------------------------------------------------------------------- /javascript/abstractnode.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | //============================================================================== 29 | /** 30 | * Template for other audio nodes: set the audioContext reference and provide connect/disconnect methods for the audio node. 31 | */ 32 | export default class AbstractNode 33 | { 34 | /** 35 | * AbstractNode constructor 36 | * @param {AudioContext} audioContext - audioContext instance. 37 | */ 38 | constructor(audioContext) 39 | { 40 | this._audioContext = audioContext; 41 | this._input = this._audioContext.createGain(); 42 | this._output = this._audioContext.createGain(); 43 | } 44 | 45 | //============================================================================== 46 | /** 47 | * Connect the audio node 48 | * @param {AudioNode} node - an AudioNode to connect to. 49 | */ 50 | connect( node ) 51 | { 52 | this._output.connect( node ); 53 | } 54 | /** 55 | * Disconnect the audio node 56 | */ 57 | disconnect() 58 | { 59 | this._output.disconnect(); 60 | } 61 | 62 | //============================================================================== 63 | /** 64 | * Returns the current sample rate of the audio context 65 | */ 66 | getCurrentSampleRate() 67 | { 68 | return this._audioContext.sampleRate; 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /cpp/peakLimiter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef __peaklimiter_h__ 34 | #define __peaklimiter_h__ 35 | 36 | enum { 37 | LIMITER_OK = 0, 38 | 39 | __error_codes_start = -100, 40 | 41 | LIMITER_INVALID_HANDLE, 42 | LIMITER_INVALID_PARAMETER, 43 | 44 | __error_codes_end 45 | }; 46 | 47 | #define PEAKLIMITER_ATTACK_DEFAULT_MS (20.0f) /* default attack time in ms */ 48 | #define PEAKLIMITER_RELEASE_DEFAULT_MS (20.0f) /* default release time in ms */ 49 | 50 | 51 | class PeakLimiter 52 | { 53 | 54 | public: 55 | int m_attack; 56 | float m_attackConst, m_releaseConst; 57 | float m_attackMs, m_releaseMs, m_maxAttackMs; 58 | float m_threshold; 59 | int m_channels, m_maxChannels; 60 | int m_sampleRate, m_maxSampleRate; 61 | float m_fadedGain; 62 | float* m_pMaxBuffer; 63 | float* m_pMaxBufferSlow; 64 | float* m_pDelayBuffer; 65 | int m_maxBufferIndex, m_maxBufferSlowIndex, m_delayBufferIndex; 66 | int m_sectionLen, m_nbrMaxBufferSection; 67 | int m_maxBufferSectionIndex, m_maxBufferSectionCounter; 68 | float m_smoothState; 69 | float m_maxMaxBufferSlow, m_maxCurrentSection; 70 | int m_indexMaxBufferSlow, *m_pIndexMaxInSection; 71 | 72 | public: 73 | 74 | /****************************************************************************** 75 | * createLimiter * 76 | * maxAttackMs: maximum attack/lookahead time in milliseconds * 77 | * releaseMs: release time in milliseconds (90% time constant) * 78 | * threshold: limiting threshold * 79 | * maxChannels: maximum number of channels * 80 | * maxSampleRate: maximum sampling rate in Hz * 81 | * returns: limiter handle * 82 | ******************************************************************************/ 83 | PeakLimiter( float maxAttackMs, 84 | float releaseMs, 85 | float threshold, 86 | int maxChannels, 87 | int maxSampleRate); 88 | ~PeakLimiter(); 89 | /****************************************************************************** 90 | * resetLimiter * 91 | * limiter: limiter handle * 92 | * returns: error code * 93 | ******************************************************************************/ 94 | int resetLimiter(); 95 | 96 | /****************************************************************************** 97 | * destroyLimiter * 98 | * limiter: limiter handle * 99 | * returns: error code * 100 | ******************************************************************************/ 101 | int destroyLimiter(); 102 | 103 | /****************************************************************************** 104 | * applyLimiter * 105 | * limiter: limiter handle * 106 | * samplesIn: input buffer containing interleaved samples * 107 | * samplesOut: output buffer containing interleaved samples * 108 | * nSamples: number of samples per channel * 109 | * returns: error code * 110 | ******************************************************************************/ 111 | int applyLimiter_E( 112 | const float* samplesIn, 113 | float* samplesOut, 114 | int nSamples); 115 | 116 | /****************************************************************************** 117 | * applyLimiter * 118 | * limiter: limiter handle * 119 | * samplesIn: input buffer containing no interleaved samples * 120 | * samplesOut: output buffer containing interleaved samples * 121 | * nSamples: number of samples per channel * 122 | * returns: error code * 123 | ******************************************************************************/ 124 | int applyLimiter( 125 | const float** samplesIn, 126 | float** samplesOut, 127 | int nSamples); 128 | 129 | /****************************************************************************** 130 | * applyLimiter * 131 | * limiter: limiter handle * 132 | * samples: input/output buffer containing no interleaved samples * 133 | * nSamples: number of samples per channel * 134 | * returns: error code * 135 | ******************************************************************************/ 136 | int applyLimiter_I( 137 | float** samples, 138 | int nSamples); 139 | 140 | /****************************************************************************** 141 | * applyLimiter * 142 | * limiter: limiter handle * 143 | * samples: input/output buffer containing interleaved samples * 144 | * nSamples: number of samples per channel * 145 | * returns: error code * 146 | ******************************************************************************/ 147 | int applyLimiter_E_I( 148 | float* samples, 149 | int nSamples); 150 | 151 | /****************************************************************************** 152 | * getLimiterDelay * 153 | * limiter: limiter handle * 154 | * returns: exact delay caused by the limiter in samples * 155 | ******************************************************************************/ 156 | int getLimiterDelay(); 157 | 158 | int getLimiterSampleRate(); 159 | 160 | float getLimiterAttack(); 161 | 162 | float getLimiterRelease(); 163 | 164 | float getLimiterThreshold(); 165 | 166 | /****************************************************************************** 167 | * getLimiterMaxGainReduction * 168 | * limiter: limiter handle * 169 | * returns: maximum gain reduction in last processed block in dB * 170 | ******************************************************************************/ 171 | float getLimiterMaxGainReduction(); 172 | 173 | /****************************************************************************** 174 | * setLimiterNChannels * 175 | * limiter: limiter handle * 176 | * nChannels: number of channels ( <= maxChannels specified on create) * 177 | * returns: error code * 178 | ******************************************************************************/ 179 | int setLimiterNChannels( int nChannels); 180 | 181 | /****************************************************************************** 182 | * setLimiterSampleRate * 183 | * limiter: limiter handle * 184 | * sampleRate: sampling rate in Hz ( <= maxSampleRate specified on create) * 185 | * returns: error code * 186 | ******************************************************************************/ 187 | int setLimiterSampleRate( int sampleRate); 188 | 189 | /****************************************************************************** 190 | * setLimiterAttack * 191 | * limiter: limiter handle * 192 | * attackMs: attack time in ms ( <= maxAttackMs specified on create) * 193 | * returns: error code * 194 | ******************************************************************************/ 195 | int setLimiterAttack( float attackMs); 196 | 197 | /****************************************************************************** 198 | * setLimiterRelease * 199 | * limiter: limiter handle * 200 | * releaseMs: release time in ms * 201 | * returns: error code * 202 | ******************************************************************************/ 203 | int setLimiterRelease( float releaseMs); 204 | 205 | /****************************************************************************** 206 | * setLimiterThreshold * 207 | * limiter: limiter handle * 208 | * threshold: limiter threshold * 209 | * returns: error code * 210 | ******************************************************************************/ 211 | int setLimiterThreshold( float threshold); 212 | }; 213 | 214 | #endif /* __peaklimiter_h__ */ 215 | -------------------------------------------------------------------------------- /cpp/peakLimiter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "peakLimiter.h" 29 | 30 | #ifndef max 31 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 32 | #endif 33 | #ifndef min 34 | #define min(a, b) (((a) < (b)) ? (a) : (b)) 35 | #endif 36 | 37 | /* create limiter */ 38 | PeakLimiter::PeakLimiter( 39 | float maxAttackMsIn, 40 | float releaseMsIn, 41 | float thresholdIn, 42 | int maxChannelsIn, 43 | int maxSampleRateIn 44 | ) 45 | { 46 | 47 | /* calc m_attack time in samples */ 48 | m_attack = (int)(maxAttackMsIn * maxSampleRateIn / 1000); 49 | 50 | if (m_attack < 1) /* m_attack time is too short */ 51 | m_attack = 1; 52 | 53 | /* length of m_pMaxBuffer sections */ 54 | m_sectionLen = (int)sqrt((float)m_attack+1); 55 | /* sqrt(m_attack+1) leads to the minimum 56 | of the number of maximum operators: 57 | nMaxOp = m_sectionLen + (m_attack+1)/m_sectionLen */ 58 | 59 | /* alloc limiter struct */ 60 | 61 | m_nbrMaxBufferSection = (m_attack+1)/m_sectionLen; 62 | if (m_nbrMaxBufferSection*m_sectionLen < (m_attack+1)) 63 | m_nbrMaxBufferSection++; /* create a full section for the last samples */ 64 | 65 | /* alloc maximum and delay buffers */ 66 | m_pMaxBuffer = new float[m_nbrMaxBufferSection * m_sectionLen]; 67 | m_pDelayBuffer = new float[m_attack * maxChannelsIn]; 68 | m_pMaxBufferSlow = new float[m_nbrMaxBufferSection]; 69 | m_pIndexMaxInSection = new int[m_nbrMaxBufferSection]; 70 | 71 | if ((m_pMaxBuffer==NULL) || (m_pDelayBuffer==NULL) || (m_pMaxBufferSlow==NULL)) { 72 | destroyLimiter(); 73 | return; 74 | } 75 | 76 | /* init parameters & states */ 77 | m_maxBufferIndex = 0; 78 | m_delayBufferIndex = 0; 79 | m_maxBufferSlowIndex = 0; 80 | m_maxBufferSectionIndex = 0; 81 | m_maxBufferSectionCounter = 0; 82 | m_maxMaxBufferSlow = 0; 83 | m_indexMaxBufferSlow = 0; 84 | m_maxCurrentSection = 0; 85 | 86 | m_attackMs = maxAttackMsIn; 87 | m_maxAttackMs = maxAttackMsIn; 88 | m_attackConst = (float)pow(0.1, 1.0 / (m_attack + 1)); 89 | m_releaseConst = (float)pow(0.1, 1.0 / (m_releaseMs * maxSampleRateIn / 1000 + 1)); 90 | m_threshold = thresholdIn; 91 | m_channels = maxChannelsIn; 92 | m_maxChannels = maxChannelsIn; 93 | m_sampleRate = maxSampleRateIn; 94 | m_maxSampleRate = maxSampleRateIn; 95 | 96 | 97 | m_fadedGain = 1.0f; 98 | m_smoothState = 1.0; 99 | 100 | memset(m_pMaxBuffer,0,sizeof(float)*m_nbrMaxBufferSection * m_sectionLen); 101 | memset(m_pDelayBuffer,0,sizeof(float)*m_attack * maxChannelsIn); 102 | memset(m_pMaxBufferSlow,0,sizeof(float)*m_nbrMaxBufferSection); 103 | memset(m_pIndexMaxInSection,0,sizeof( int)*m_nbrMaxBufferSection); 104 | } 105 | 106 | PeakLimiter::~PeakLimiter() 107 | { 108 | destroyLimiter(); 109 | } 110 | 111 | /* reset limiter */ 112 | int PeakLimiter::resetLimiter() 113 | { 114 | 115 | m_maxBufferIndex = 0; 116 | m_delayBufferIndex = 0; 117 | m_maxBufferSlowIndex = 0; 118 | m_maxBufferSectionIndex = 0; 119 | m_maxBufferSectionCounter = 0; 120 | m_fadedGain = 1.0f; 121 | m_smoothState = 1.0; 122 | m_maxMaxBufferSlow = 0; 123 | m_indexMaxBufferSlow = 0; 124 | m_maxCurrentSection = 0; 125 | 126 | 127 | memset(m_pMaxBuffer,0,sizeof(float)*m_nbrMaxBufferSection * m_sectionLen); 128 | memset(m_pDelayBuffer,0,sizeof(float)*m_attack * m_maxChannels); 129 | memset(m_pMaxBufferSlow,0,sizeof(float)*m_nbrMaxBufferSection); 130 | memset(m_pIndexMaxInSection,0,sizeof(int)*m_nbrMaxBufferSection); 131 | 132 | return LIMITER_OK; 133 | } 134 | 135 | 136 | /* destroy limiter */ 137 | int PeakLimiter::destroyLimiter() 138 | { 139 | if (m_pMaxBuffer) 140 | { 141 | delete [] m_pMaxBuffer; 142 | m_pMaxBuffer = NULL; 143 | } 144 | if (m_pDelayBuffer) 145 | { 146 | delete [] m_pDelayBuffer; 147 | m_pDelayBuffer = NULL; 148 | } 149 | if (m_pMaxBufferSlow) 150 | { 151 | delete [] m_pMaxBufferSlow; 152 | m_pMaxBufferSlow = NULL; 153 | } 154 | if (m_pIndexMaxInSection) 155 | { 156 | delete [] m_pIndexMaxInSection; 157 | m_pIndexMaxInSection = NULL; 158 | } 159 | 160 | return LIMITER_OK; 161 | } 162 | 163 | /* apply limiter */ 164 | int PeakLimiter::applyLimiter_E(const float *samplesIn,float *samplesOut, int nSamples) 165 | { 166 | memcpy(samplesOut,samplesIn,nSamples*sizeof(float)); 167 | return applyLimiter_E_I(samplesOut,nSamples); 168 | } 169 | 170 | /* apply limiter */ 171 | int PeakLimiter::applyLimiter_E_I(float *samples, int nSamples) 172 | { 173 | int i, j; 174 | float tmp, gain, maximum; 175 | 176 | for (i = 0; i < nSamples; i++) { 177 | /* get maximum absolute sample value of all channels that are greater in absoulte value to m_threshold */ 178 | m_pMaxBuffer[m_maxBufferIndex] = m_threshold; 179 | for (j = 0; j < m_channels; j++) { 180 | m_pMaxBuffer[m_maxBufferIndex] = max(m_pMaxBuffer[m_maxBufferIndex], (float)fabs(samples[i * m_channels + j])); 181 | } 182 | 183 | /* search maximum in the current section */ 184 | if (m_pIndexMaxInSection[m_maxBufferSlowIndex] == m_maxBufferIndex) // if we have just changed the sample containg the old maximum value 185 | { 186 | // need to compute the maximum on the whole section 187 | m_maxCurrentSection = m_pMaxBuffer[m_maxBufferSectionIndex]; 188 | for (j = 1; j < m_sectionLen; j++) { 189 | if (m_pMaxBuffer[m_maxBufferSectionIndex + j] > m_maxCurrentSection) 190 | { 191 | m_maxCurrentSection = m_pMaxBuffer[m_maxBufferSectionIndex + j]; 192 | m_pIndexMaxInSection[m_maxBufferSlowIndex] = m_maxBufferSectionIndex + j; 193 | } 194 | } 195 | } 196 | else // just need to compare the new value the cthe current maximum value 197 | { 198 | if (m_pMaxBuffer[m_maxBufferIndex] > m_maxCurrentSection) 199 | { 200 | m_maxCurrentSection = m_pMaxBuffer[m_maxBufferIndex]; 201 | m_pIndexMaxInSection[m_maxBufferSlowIndex] = m_maxBufferIndex; 202 | } 203 | } 204 | 205 | // find maximum of slow (downsampled) max buffer 206 | maximum = m_maxMaxBufferSlow; 207 | if (m_maxCurrentSection > maximum) 208 | { 209 | maximum = m_maxCurrentSection; 210 | } 211 | 212 | m_maxBufferIndex++; 213 | m_maxBufferSectionCounter++; 214 | 215 | /* if m_pMaxBuffer section is finished, or end of m_pMaxBuffer is reached, 216 | store the maximum of this section and open up a new one */ 217 | if ((m_maxBufferSectionCounter >= m_sectionLen) || (m_maxBufferIndex >= m_attack + 1)) { 218 | m_maxBufferSectionCounter = 0; 219 | 220 | tmp = m_pMaxBufferSlow[m_maxBufferSlowIndex] = m_maxCurrentSection; 221 | j = 0; 222 | if (m_indexMaxBufferSlow == m_maxBufferSlowIndex) 223 | { 224 | j = 1; 225 | } 226 | m_maxBufferSlowIndex++; 227 | if (m_maxBufferSlowIndex >= m_nbrMaxBufferSection) 228 | { 229 | m_maxBufferSlowIndex = 0; 230 | } 231 | if (m_indexMaxBufferSlow == m_maxBufferSlowIndex) 232 | { 233 | j = 1; 234 | } 235 | m_maxCurrentSection = m_pMaxBufferSlow[m_maxBufferSlowIndex]; 236 | m_pMaxBufferSlow[m_maxBufferSlowIndex] = 0.0f; /* zero out the value representing the new section */ 237 | 238 | /* compute the maximum over all the section */ 239 | if (j) 240 | { 241 | m_maxMaxBufferSlow = 0; 242 | for (j = 0; j < m_nbrMaxBufferSection; j++) 243 | { 244 | if (m_pMaxBufferSlow[j] > m_maxMaxBufferSlow) 245 | { 246 | m_maxMaxBufferSlow = m_pMaxBufferSlow[j]; 247 | m_indexMaxBufferSlow = j; 248 | } 249 | } 250 | } 251 | else 252 | { 253 | if (tmp > m_maxMaxBufferSlow) 254 | { 255 | m_maxMaxBufferSlow = tmp; 256 | m_indexMaxBufferSlow = m_maxBufferSlowIndex; 257 | } 258 | } 259 | 260 | m_maxBufferSectionIndex += m_sectionLen; 261 | } 262 | 263 | if (m_maxBufferIndex >= (m_attack + 1)) 264 | { 265 | m_maxBufferIndex = 0; 266 | m_maxBufferSectionIndex = 0; 267 | } 268 | 269 | /* needed current gain */ 270 | if (maximum > m_threshold) 271 | { 272 | gain = m_threshold / maximum; 273 | } 274 | else 275 | { 276 | gain = 1; 277 | } 278 | 279 | /*avoid overshoot */ 280 | 281 | if (gain < m_smoothState) { 282 | m_fadedGain = min(m_fadedGain, (gain - 0.1f * (float)m_smoothState) * 1.11111111f); 283 | } 284 | else 285 | { 286 | m_fadedGain = gain; 287 | } 288 | 289 | 290 | /* smoothing gain */ 291 | if (m_fadedGain < m_smoothState) 292 | { 293 | m_smoothState = m_attackConst * (m_smoothState - m_fadedGain) + m_fadedGain; /* m_attack */ 294 | /*avoid overshoot */ 295 | if (gain > m_smoothState) 296 | { 297 | m_smoothState = gain; 298 | } 299 | } 300 | else 301 | { 302 | m_smoothState = m_releaseConst * (m_smoothState - m_fadedGain) + m_fadedGain; /* release */ 303 | } 304 | 305 | /* fill delay line, apply gain */ 306 | for (j = 0; j < m_channels; j++) 307 | { 308 | tmp = m_pDelayBuffer[m_delayBufferIndex * m_channels + j]; 309 | m_pDelayBuffer[m_delayBufferIndex * m_channels + j] = samples[i * m_channels + j]; 310 | 311 | tmp *= m_smoothState; 312 | if (tmp > m_threshold) tmp = m_threshold; 313 | if (tmp < -m_threshold) tmp = -m_threshold; 314 | 315 | samples[i * m_channels + j] = tmp; 316 | } 317 | 318 | m_delayBufferIndex++; 319 | if (m_delayBufferIndex >= m_attack) 320 | m_delayBufferIndex = 0; 321 | 322 | } 323 | 324 | return LIMITER_OK; 325 | } 326 | 327 | /* apply limiter */ 328 | int PeakLimiter::applyLimiter(const float **samplesIn,float **samplesOut, int nSamples) 329 | { 330 | int ind; 331 | for(ind=0;ind m_maxCurrentSection) 359 | { 360 | m_maxCurrentSection = m_pMaxBuffer[m_maxBufferSectionIndex + j]; 361 | m_pIndexMaxInSection[m_maxBufferSlowIndex] = m_maxBufferSectionIndex+j; 362 | } 363 | } 364 | } 365 | else // just need to compare the new value the cthe current maximum value 366 | { 367 | if (m_pMaxBuffer[m_maxBufferIndex] > m_maxCurrentSection) 368 | { 369 | m_maxCurrentSection = m_pMaxBuffer[m_maxBufferIndex]; 370 | m_pIndexMaxInSection[m_maxBufferSlowIndex] = m_maxBufferIndex; 371 | } 372 | } 373 | 374 | // find maximum of slow (downsampled) max buffer 375 | maximum = m_maxMaxBufferSlow; 376 | if (m_maxCurrentSection > maximum) 377 | { 378 | maximum = m_maxCurrentSection; 379 | } 380 | 381 | m_maxBufferIndex++; 382 | m_maxBufferSectionCounter++; 383 | 384 | /* if m_pMaxBuffer section is finished, or end of m_pMaxBuffer is reached, 385 | store the maximum of this section and open up a new one */ 386 | if ((m_maxBufferSectionCounter >= m_sectionLen)||(m_maxBufferIndex >= m_attack+1)) { 387 | m_maxBufferSectionCounter = 0; 388 | 389 | tmp = m_pMaxBufferSlow[m_maxBufferSlowIndex] = m_maxCurrentSection; 390 | j = 0; 391 | if (m_indexMaxBufferSlow == m_maxBufferSlowIndex) 392 | { 393 | j = 1; 394 | } 395 | m_maxBufferSlowIndex++; 396 | if (m_maxBufferSlowIndex >= m_nbrMaxBufferSection) 397 | { 398 | m_maxBufferSlowIndex = 0; 399 | } 400 | if (m_indexMaxBufferSlow == m_maxBufferSlowIndex) 401 | { 402 | j = 1; 403 | } 404 | m_maxCurrentSection = m_pMaxBufferSlow[m_maxBufferSlowIndex]; 405 | m_pMaxBufferSlow[m_maxBufferSlowIndex] = 0.0f; /* zero out the value representing the new section */ 406 | 407 | /* compute the maximum over all the section */ 408 | if (j) 409 | { 410 | m_maxMaxBufferSlow = 0; 411 | for (j = 0; j < m_nbrMaxBufferSection; j++) 412 | { 413 | if (m_pMaxBufferSlow[j] > m_maxMaxBufferSlow) 414 | { 415 | m_maxMaxBufferSlow = m_pMaxBufferSlow[j]; 416 | m_indexMaxBufferSlow = j; 417 | } 418 | } 419 | } 420 | else 421 | { 422 | if (tmp > m_maxMaxBufferSlow) 423 | { 424 | m_maxMaxBufferSlow = tmp; 425 | m_indexMaxBufferSlow = m_maxBufferSlowIndex; 426 | } 427 | } 428 | 429 | m_maxBufferSectionIndex += m_sectionLen; 430 | } 431 | 432 | if (m_maxBufferIndex >= (m_attack+1)) 433 | { 434 | m_maxBufferIndex = 0; 435 | m_maxBufferSectionIndex = 0; 436 | } 437 | 438 | /* needed current gain */ 439 | if (maximum > m_threshold) 440 | { 441 | gain = m_threshold / maximum; 442 | } 443 | else 444 | { 445 | gain = 1; 446 | } 447 | 448 | /*avoid overshoot */ 449 | 450 | if (gain < m_smoothState) { 451 | m_fadedGain = min(m_fadedGain, (gain - 0.1f * (float)m_smoothState) * 1.11111111f); 452 | } 453 | else 454 | { 455 | m_fadedGain = gain; 456 | } 457 | 458 | 459 | /* smoothing gain */ 460 | if (m_fadedGain < m_smoothState) 461 | { 462 | m_smoothState = m_attackConst * (m_smoothState - m_fadedGain) + m_fadedGain; /* m_attack */ 463 | /*avoid overshoot */ 464 | if (gain > m_smoothState) 465 | { 466 | m_smoothState = gain; 467 | } 468 | } 469 | else 470 | { 471 | m_smoothState = m_releaseConst * (m_smoothState - m_fadedGain) + m_fadedGain; /* release */ 472 | } 473 | 474 | /* fill delay line, apply gain */ 475 | for (j = 0; j < m_channels; j++) 476 | { 477 | tmp = m_pDelayBuffer[m_delayBufferIndex * m_channels + j]; 478 | m_pDelayBuffer[m_delayBufferIndex * m_channels + j] = samples[j][i]; 479 | 480 | tmp *= m_smoothState; 481 | if (tmp > m_threshold) tmp = m_threshold; 482 | if (tmp < -m_threshold) tmp = -m_threshold; 483 | 484 | samples[j][i] = tmp; 485 | } 486 | 487 | m_delayBufferIndex++; 488 | if (m_delayBufferIndex >= m_attack) 489 | m_delayBufferIndex = 0; 490 | 491 | } 492 | 493 | return LIMITER_OK; 494 | 495 | } 496 | 497 | /* get delay in samples */ 498 | int PeakLimiter::getLimiterDelay() 499 | { 500 | return m_attack; 501 | } 502 | 503 | /* get m_attack in Ms */ 504 | float PeakLimiter::getLimiterAttack() 505 | { 506 | return m_attackMs; 507 | } 508 | 509 | /* get delay in samples */ 510 | int PeakLimiter::getLimiterSampleRate() 511 | { 512 | return m_sampleRate; 513 | } 514 | 515 | /* get delay in samples */ 516 | float PeakLimiter::getLimiterRelease() 517 | { 518 | return m_releaseMs; 519 | } 520 | 521 | /* get maximum gain reduction of last processed block */ 522 | float PeakLimiter::getLimiterMaxGainReduction() 523 | { 524 | return -20 * (float)log10(m_smoothState); 525 | } 526 | 527 | /* set number of channels */ 528 | int PeakLimiter::setLimiterNChannels(int nChannelsIn) 529 | { 530 | if (nChannelsIn > m_maxChannels) return LIMITER_INVALID_PARAMETER; 531 | 532 | m_channels = nChannelsIn; 533 | resetLimiter(); 534 | 535 | return LIMITER_OK; 536 | } 537 | 538 | /* set sampling rate */ 539 | int PeakLimiter::setLimiterSampleRate(int sampleRateIn) 540 | { 541 | if (sampleRateIn > m_maxSampleRate) return LIMITER_INVALID_PARAMETER; 542 | 543 | /* update m_attack/release constants */ 544 | m_attack = (int)(m_attackMs * sampleRateIn / 1000); 545 | 546 | if (m_attack < 1) /* m_attack time is too short */ 547 | return LIMITER_INVALID_PARAMETER; 548 | 549 | /* length of m_pMaxBuffer sections */ 550 | m_sectionLen = (int)sqrt((float)m_attack+1); 551 | 552 | m_nbrMaxBufferSection = (m_attack+1)/m_sectionLen; 553 | if (m_nbrMaxBufferSection*m_sectionLen < (m_attack+1)) 554 | m_nbrMaxBufferSection++; 555 | m_attackConst = (float)pow(0.1, 1.0 / (m_attack + 1)); 556 | m_releaseConst = (float)pow(0.1, 1.0 / (m_releaseMs * sampleRateIn / 1000 + 1)); 557 | m_sampleRate = sampleRateIn; 558 | 559 | /* reset */ 560 | resetLimiter(); 561 | 562 | return LIMITER_OK; 563 | } 564 | 565 | /* set m_attack time */ 566 | int PeakLimiter::setLimiterAttack(float attackMsIn) 567 | { 568 | if (attackMsIn > m_maxAttackMs) return LIMITER_INVALID_PARAMETER; 569 | 570 | /* calculate attack time in samples */ 571 | m_attack = (int)(attackMsIn * m_sampleRate / 1000); 572 | 573 | if (m_attack < 1) /* attack time is too short */ 574 | m_attack=1; 575 | 576 | /* length of m_pMaxBuffer sections */ 577 | m_sectionLen = (int)sqrt((float)m_attack+1); 578 | 579 | m_nbrMaxBufferSection = (m_attack+1)/m_sectionLen; 580 | if (m_nbrMaxBufferSection*m_sectionLen < (m_attack+1)) 581 | m_nbrMaxBufferSection++; 582 | m_attackConst = (float)pow(0.1, 1.0 / (m_attack + 1)); 583 | m_attackMs = attackMsIn; 584 | 585 | /* reset */ 586 | resetLimiter(); 587 | 588 | return LIMITER_OK; 589 | } 590 | 591 | /* set release time */ 592 | int PeakLimiter::setLimiterRelease(float releaseMsIn) 593 | { 594 | m_releaseConst = (float)pow(0.1, 1.0 / (releaseMsIn * m_sampleRate / 1000 + 1)); 595 | m_releaseMs = releaseMsIn; 596 | 597 | return LIMITER_OK; 598 | } 599 | 600 | /* set limiter threshold */ 601 | int PeakLimiter::setLimiterThreshold(float thresholdIn) 602 | { 603 | m_threshold = thresholdIn; 604 | 605 | return LIMITER_OK; 606 | } 607 | 608 | /* set limiter threshold */ 609 | float PeakLimiter::getLimiterThreshold() 610 | { 611 | return m_threshold; 612 | } 613 | 614 | 615 | -------------------------------------------------------------------------------- /javascript/peaklimiter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, UMR STMS 9912 - Ircam-Centre Pompidou / CNRS / UPMC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | import AbstractNode from './abstractnode.js'; 29 | import utilities from './utils.js'; 30 | 31 | export default class PeakLimiterNode extends AbstractNode 32 | { 33 | /************************************************************************************/ 34 | /*! 35 | * @brief Class constructor 36 | * @param[in] audioContext 37 | * @param[in] numChannels : number of channels 38 | * 39 | */ 40 | /************************************************************************************/ 41 | constructor( audioContext, 42 | numChannels ) 43 | { 44 | /// sanity checks 45 | if( numChannels <= 0 ) 46 | { 47 | throw new Error("Invalid"); 48 | } 49 | 50 | super( audioContext ); 51 | 52 | this.nMaxBufferSection = 0; 53 | this.maxBufferIndex = 0; 54 | this.delayBufferIndex = 0; 55 | this.maxBufferSlowIndex = 0; 56 | this.maxBufferSectionIndex = 0; 57 | this.maxBufferSectionCounter = 0; 58 | this.maxMaxBufSlow = 0; 59 | this.indexMaxBufferSlow = 0; 60 | this.maxCurrentSection = 0; 61 | 62 | this.attackMs = 0; 63 | this.maxAttackMs = 0; 64 | this.attackConst = 0; 65 | this.releaseConst = 0; 66 | this.threshold = 0; 67 | this.channels = 0; 68 | this.maxChannels = 0; 69 | this.sampleRate = 0; 70 | this.maxSampleRate = 0; 71 | 72 | this.fadedGain = 1.0; 73 | this.smoothState = 1.0; 74 | 75 | this.pMaxBuffer = null; 76 | this.pDelayBuffer = null; 77 | this.pMaxBufferSlow = null; 78 | this.pIndexMaxInSection = null; 79 | this.input = null; 80 | this.ouput = null; 81 | 82 | 83 | /// additions: 84 | 85 | 86 | this.maxSampleRate = 192000; 87 | 88 | this.sampleRate = utilities.clamp( audioContext.sampleRate, 22050, 192000 ); 89 | 90 | this.init( 20, 20, utilities.dB2lin( -3 ), numChannels, 192000 ); 91 | 92 | this.setSampleRate( this.sampleRate ); 93 | 94 | this.reset(); 95 | 96 | this.setNChannels( numChannels ); 97 | 98 | /// the script processor part 99 | { 100 | const bufferSize = 0; 101 | /* 102 | The buffer size in units of sample-frames. If specified, the bufferSize must be one of the following values: 103 | 256, 512, 1024, 2048, 4096, 8192, 16384. If it's not passed in, or if the value is 0, 104 | then the implementation will choose the best buffer size for the given environment, 105 | which will be a constant power of 2 throughout the lifetime of the node. 106 | */ 107 | const numberOfInputChannels = numChannels; 108 | const numberOfOutputChannels = numChannels; 109 | this._scriptNode = audioContext.createScriptProcessor( bufferSize, 110 | numberOfInputChannels, 111 | numberOfOutputChannels ); 112 | 113 | 114 | var processor = this; 115 | 116 | this._scriptNode.onaudioprocess = function( audioProcessingEvent ) 117 | { 118 | var inputBuffer = audioProcessingEvent.inputBuffer; 119 | var outputBuffer = audioProcessingEvent.outputBuffer; 120 | 121 | const numChannels = outputBuffer.numberOfChannels; 122 | 123 | if( inputBuffer.numberOfChannels != numChannels ) 124 | { 125 | throw new Error("Invalid"); 126 | } 127 | if( numChannels != processor.channels ) 128 | { 129 | throw new Error("Invalid"); 130 | } 131 | 132 | for( let j = 0; j < numChannels; j++ ) 133 | { 134 | processor.input[j] = inputBuffer.getChannelData(j); 135 | processor.output[j] = outputBuffer.getChannelData(j); 136 | } 137 | 138 | const numSamples = inputBuffer.length; 139 | 140 | var tmp, gain; 141 | var maximum, sectionMaximum; 142 | 143 | for( let i = 0; i < numSamples; i++ ) 144 | { 145 | 146 | /* get maximum absolute sample value of all channels */ 147 | processor.pMaxBuffer[processor.maxBufferIndex] = processor.threshold; 148 | for( let j = 0; j < processor.channels; j++ ) 149 | { 150 | processor.pMaxBuffer[processor.maxBufferIndex] = Math.max(processor.pMaxBuffer[processor.maxBufferIndex], Math.abs(processor.input[j][i])); 151 | } 152 | 153 | /* search maximum in the current section */ 154 | if( processor.pIndexMaxInSection[processor.maxBufferSlowIndex] == processor.maxBufferIndex ) // if we have just changed the sample containg the old maximum value 155 | { 156 | // need to compute the maximum on the whole section 157 | processor.maxCurrentSection = processor.pMaxBuffer[processor.maxBufferSectionIndex]; 158 | for( let j = 1; j < processor.sectionLength; j++ ) 159 | { 160 | if( processor.pMaxBuffer[processor.maxBufferSectionIndex + j] > processor.maxCurrentSection ) 161 | { 162 | processor.maxCurrentSection = processor.pMaxBuffer[processor.maxBufferSectionIndex + j]; 163 | processor.pIndexMaxInSection[processor.maxBufferSlowIndex] = processor.maxBufferSectionIndex + j; 164 | } 165 | } 166 | } 167 | else // just need to compare the new value the cthe current maximum value 168 | { 169 | if( processor.pMaxBuffer[processor.maxBufferIndex] > processor.maxCurrentSection ) 170 | { 171 | processor.maxCurrentSection = processor.pMaxBuffer[processor.maxBufferIndex]; 172 | processor.pIndexMaxInSection[processor.maxBufferSlowIndex] = processor.maxBufferIndex; 173 | } 174 | } 175 | 176 | // find maximum of slow (downsampled) max buffer 177 | maximum = processor.maxMaxBufferSlow; 178 | if( processor.maxCurrentSection > maximum ) 179 | { 180 | maximum = processor.maxCurrentSection; 181 | } 182 | 183 | processor.maxBufferIndex++; 184 | processor.maxBufferSectionCounter++; 185 | 186 | /* if pMaxBuffer section is finished, or end of pMaxBuffer is reached,*/ 187 | /* store the maximum of this section and open up a new one */ 188 | if( processor.maxBufferSectionCounter >= processor.sectionLength || processor.maxBufferIndex >= processor.attack + 1 ) 189 | { 190 | processor.maxBufferSectionCounter = 0; 191 | 192 | tmp = processor.pMaxBufferSlow[processor.maxBufferSlowIndex] = processor.maxCurrentSection; 193 | var j = 0; 194 | if( processor.indexMaxBufferSlow == processor.maxBufferSlowIndex ) 195 | { 196 | j = 1; 197 | } 198 | processor.maxBufferSlowIndex++; 199 | if( processor.maxBufferSlowIndex >= processor.nMaxBufferSection ) 200 | { 201 | processor.maxBufferSlowIndex = 0; 202 | } 203 | if( processor.indexMaxBufferSlow == processor.maxBufferSlowIndex ) 204 | { 205 | j = 1; 206 | } 207 | processor.maxCurrentSection = processor.pMaxBufferSlow[processor.maxBufferSlowIndex]; 208 | processor.pMaxBufferSlow[processor.maxBufferSlowIndex] = 0; /* zero out the value representing the new section */ 209 | 210 | /* compute the maximum over all the section */ 211 | if( j ) 212 | { 213 | processor.maxMaxBufferSlow = 0; 214 | for( let k = 0; k < processor.nMaxBufferSection; k++ ) 215 | { 216 | if( processor.pMaxBufferSlow[k] > processor.maxMaxBufferSlow ) 217 | { 218 | processor.maxMaxBufferSlow = processor.pMaxBufferSlow[k]; 219 | processor.indexMaxBufferSlow = k; 220 | } 221 | } 222 | } 223 | else 224 | { 225 | if( tmp > processor.maxMaxBufferSlow ) 226 | { 227 | processor.maxMaxBufferSlow = tmp; 228 | processor.indexMaxBufferSlow = processor.maxBufferSlowIndex; 229 | } 230 | } 231 | 232 | processor.maxBufferSectionIndex += processor.sectionLength; 233 | } 234 | 235 | if( processor.maxBufferIndex >= processor.attack + 1) 236 | { 237 | processor.maxBufferIndex = 0; 238 | processor.maxBufferSectionIndex = 0; 239 | } 240 | 241 | /* calc gain */ 242 | if( maximum > processor.threshold ) 243 | { 244 | gain = processor.threshold / maximum; 245 | } 246 | else 247 | { 248 | gain = 1; 249 | } 250 | 251 | /* gain smoothing */ 252 | if( gain < processor.smoothState ) 253 | { 254 | processor.fadedGain = Math.min(processor.fadedGain, (gain - 0.1 * processor.smoothState) * 1.11111111); 255 | } 256 | else 257 | { 258 | processor.fadedGain = gain; 259 | } 260 | 261 | /* smoothing filter */ 262 | if( processor.fadedGain < processor.smoothState ) 263 | { 264 | processor.smoothState = processor.attackConst * (processor.smoothState - processor.fadedGain) + processor.fadedGain; /* attack */ 265 | if( gain > processor.smoothState ) 266 | { 267 | processor.smoothState = gain; 268 | } 269 | } 270 | else 271 | { 272 | processor.smoothState = processor.releaseConst * (processor.smoothState - processor.fadedGain) + processor.fadedGain; /* release */ 273 | } 274 | 275 | /* lookahead delay, apply gain */ 276 | for( let k = 0; k < processor.channels; k++ ) 277 | { 278 | tmp = processor.pDelayBuffer[processor.delayBufferIndex * processor.channels + k]; 279 | processor.pDelayBuffer[processor.delayBufferIndex * processor.channels + k] = processor.input[k][i]; 280 | 281 | tmp *= processor.smoothState; 282 | if( tmp > processor.threshold ) 283 | { 284 | tmp = processor.threshold; 285 | } 286 | if( tmp < -processor.threshold ) 287 | { 288 | tmp = -processor.threshold; 289 | } 290 | 291 | processor.output[k][i] = tmp; 292 | } 293 | 294 | processor.delayBufferIndex++; 295 | if( processor.delayBufferIndex >= processor.attack ) 296 | { 297 | processor.delayBufferIndex = 0; 298 | } 299 | } 300 | } 301 | } 302 | 303 | 304 | this._input.connect( this._scriptNode ); 305 | this._scriptNode.connect( this._output ); 306 | 307 | } 308 | 309 | 310 | /************************************************************************************/ 311 | /*! 312 | * @brief init(maxAttackMsIn, releaseMsIn, thresholdIn, maxChannelsIn, maxSampleRateIn) : Must be called to initialized the class 313 | * @param maxAttackMsIn : maximum value for the attack time. It is the time to reach to correct attenutation to avoid clipping. 314 | * this parameter defines the amount of memory used by the class. It is a float or integer value 315 | * @param releaseMsIn : defines the time to release the gain. It is the time for the gain to go 316 | * back to its nominal value after an attenaution to avoid clipping 317 | * It is a float or integer value 318 | * @param thresholdIn : maximum value fro absolutie value. It is a float value 319 | * @param maxChannelsIn : maximum value of the number of channel that can be used. Number of channels value can be changed after class creation 320 | * but the value must be lower than maxChannelsIn. The gain between channels are inked. 321 | * @param maxSampleRateIn : maximum value of the sample rate that can be used. Sample rate value can be changed after class creation 322 | * but the value must be lower than maxSampleRateIn. 323 | * 324 | */ 325 | /************************************************************************************/ 326 | init( maxAttackMsIn, releaseMsIn, thresholdIn, maxChannelsIn, maxSampleRateIn) 327 | { 328 | 329 | this.attack = Math.floor(maxAttackMsIn * maxSampleRateIn / 1000); 330 | if( this.attack < 1 ) 331 | { 332 | /* attack time is too short */ 333 | this.attack = 1; 334 | } 335 | 336 | /* length of pMaxBuffer sections */ 337 | this.sectionLength = Math.floor(Math.sqrt(this.attack + 1)); 338 | /* sqrt(attack+1) leads to the minimum 339 | of the number of maximum operators: 340 | nMaxOp = sectionLength + (attack+1)/sectionLength */ 341 | 342 | /* alloc limiter struct */ 343 | 344 | this.nMaxBufferSection = Math.floor((this.attack + 1) / this.sectionLength); 345 | if( this.nMaxBufferSection * this.sectionLength < this.attack + 1 ) 346 | { 347 | this.nMaxBufferSection++; /* create a full section for the last samples */ 348 | } 349 | 350 | /* alloc maximum and delay Bufferfers */ 351 | this.pMaxBuffer = new Float32Array(this.nMaxBufferSection * this.sectionLength); 352 | this.pDelayBuffer = new Float32Array(this.attack * maxChannelsIn); 353 | this.pMaxBufferSlow = new Float32Array(this.nMaxBufferSection); 354 | this.pIndexMaxInSection = new Int32Array(this.nMaxBufferSection); 355 | 356 | this.input = new Array(maxChannelsIn); 357 | this.output = new Array(maxChannelsIn); 358 | for( let j = 0; j < maxChannelsIn; j++ ) 359 | { 360 | this.input[j] = null; 361 | this.output[j] = null; 362 | } 363 | 364 | if( typeof this.pMaxBuffer == 'undefined' || typeof this.pDelayBuffer == 'undefined' || typeof this.pMaxBufferSlow == 'undefined') 365 | { 366 | this.destroy(); 367 | return; 368 | } 369 | this.reset(); 370 | 371 | /* init parameters & states */ 372 | this.maxBufferIndex = 0; 373 | this.delayBufferIndex = 0; 374 | this.maxBufferSlowIndex = 0; 375 | this.maxBufferSectionIndex = 0; 376 | this.maxBufferSectionCounter = 0; 377 | this.maxMaxBufSlow = 0; 378 | this.indexMaxBufferSlow = 0; 379 | this.maxCurrentSection = 0; 380 | 381 | this.attackMs = maxAttackMsIn; 382 | this.maxAttackMs = maxAttackMsIn; 383 | this.attackConst = Math.pow(0.1, 1.0 / (this.attack + 1)); 384 | this.releaseConst = Math.pow(0.1, 1.0 / (releaseMsIn * maxSampleRateIn / 1000 + 1)); 385 | this.threshold = thresholdIn; 386 | this.channels = maxChannelsIn; 387 | this.maxChannels = maxChannelsIn; 388 | this.sampleRate = maxSampleRateIn; 389 | this.maxSampleRate = maxSampleRateIn; 390 | 391 | this.fadedGain = 1.0; 392 | this.smoothState = 1.0; 393 | } 394 | 395 | /************************************************************************************/ 396 | /*! 397 | * @brief release the memory allocated by the object. Memory is allocated when init fucntion is called 398 | * 399 | */ 400 | /************************************************************************************/ 401 | destroy() 402 | { 403 | delete this.maxBuffer; 404 | delete this.delayBuffer; 405 | delete this.maxBufferSlow; 406 | delete this.pIndexMaxInSection; 407 | } 408 | 409 | /************************************************************************************/ 410 | /*! 411 | * @brief get delay in samples 412 | * 413 | */ 414 | /************************************************************************************/ 415 | getDelay() 416 | { 417 | return this.attack; 418 | } 419 | 420 | /************************************************************************************/ 421 | /*! 422 | * @brief get attack in msec 423 | * 424 | */ 425 | /************************************************************************************/ 426 | getAttack() 427 | { 428 | return this.attackMs; 429 | } 430 | 431 | getSampleRate() 432 | { 433 | return this.sampleRate; 434 | } 435 | 436 | getRelease() 437 | { 438 | return this.releaseMs; 439 | } 440 | 441 | /************************************************************************************/ 442 | /*! 443 | * @brief get maximum gain reduction of last processed block 444 | * 445 | */ 446 | /************************************************************************************/ 447 | /* 448 | getMaxGainReduction() 449 | { 450 | return -20 * Math.log( this.minGain ) / Math.LN10; 451 | } 452 | */ 453 | 454 | setNChannels( nChannelsIn ) 455 | { 456 | if( nChannelsIn == this.maxChannels) return true; 457 | if( nChannelsIn > this.maxChannels) return false; 458 | 459 | this.channels = nChannelsIn; 460 | this.reset(); 461 | 462 | return true; 463 | } 464 | 465 | setInput( nChannelsInNum, input ) 466 | { 467 | if( nChannelsInNum >= this.channels ) 468 | { 469 | return false; 470 | } 471 | 472 | return true; 473 | } 474 | 475 | setOutput( nChannelsOutNum, output ) 476 | { 477 | if( nChannelsOutNum >= this.channels ) 478 | { 479 | return false; 480 | } 481 | 482 | return true; 483 | } 484 | 485 | setSampleRate( sampleRateIn ) 486 | { 487 | if (sampleRateIn == this.maxSampleRate) return true; 488 | if (sampleRateIn > this.maxSampleRate) return false; 489 | 490 | /* update attack/release constants */ 491 | this.attack = Math.floor(this.attackMs * sampleRateIn / 1000); 492 | 493 | if (this.attack < 1) /* attack time is too short */ 494 | this.attack = 1; 495 | 496 | /* length of pMaxBuffer sections */ 497 | this.sectionLength = Math.floor(Math.sqrt(this.attack + 1)); 498 | 499 | this.nMaxBufferSection = Math.floor((this.attack + 1) / this.sectionLength); 500 | if (this.nMaxBufferSection * this.sectionLength < this.attack + 1) this.nMaxBufferSection++; 501 | this.attackConst = Math.pow(0.1, 1.0 / (this.attack + 1)); 502 | this.releaseConst = Math.pow(0.1, 1.0 / (this.releaseMs * sampleRateIn / 1000 + 1)); 503 | this.sampleRate = sampleRateIn; 504 | 505 | /*reset */ 506 | this.reset(); 507 | 508 | return true; 509 | } 510 | 511 | setAttack( attackMsIn ) 512 | { 513 | if (attackMsIn == this.attackMs) return true; 514 | if (attackMsIn > this.maxAttackMs) return false; 515 | 516 | /* calculate attack time in samples */ 517 | this.attack = Math.floor(attackMsIn * this.sampleRate / 1000); 518 | 519 | if (this.attack < 1) /* attack time is too short */ 520 | this.attack = 1; 521 | 522 | /* length of pMaxBuffer sections */ 523 | this.sectionLength = Math.floor(Math.sqrt(this.attack + 1)); 524 | 525 | this.nMaxBufferSection = Math.floor((this.attack + 1) / this.sectionLength); 526 | if (this.nMaxBufferSection * this.sectionLength < this.attack + 1) this.nMaxBufferSection++; 527 | this.attackConst = Math.pow(0.1, 1.0 / (this.attack + 1)); 528 | this.attackMs = attackMsIn; 529 | 530 | /* reset */ 531 | this.reset(); 532 | 533 | return true; 534 | } 535 | 536 | setRelease( releaseMsIn ) 537 | { 538 | if( releaseMsIn == this.releaseMs) return true; 539 | this.releaseConst = Math.pow(0.1, 1.0 / (releaseMsIn * this.sampleRate / 1000 + 1)); 540 | this.releaseMs = releaseMsIn; 541 | 542 | return true; 543 | } 544 | 545 | setThreshold( thresholdIn ) 546 | { 547 | this.threshold = thresholdIn; 548 | 549 | return true; 550 | } 551 | 552 | getThreshold() 553 | { 554 | return this.threshold; 555 | } 556 | 557 | reset() 558 | { 559 | 560 | this.maxBufferIndex = 0; 561 | this.delayBufferIndex = 0; 562 | this.maxBufferSlowIndex = 0; 563 | this.maxBufferSectionIndex = 0; 564 | this.maxBufferSectionCounter = 0; 565 | this.fadedGain = 1.0; 566 | this.smoothState = 1.0; 567 | this.maxMaxBufSlow = 0; 568 | this.indexMaxBufferSlow = 0; 569 | this.maxCurrentSection = 0; 570 | 571 | for( let i = 0; i < this.attack + 1; i++) 572 | { 573 | this.pMaxBuffer[i] = 0; 574 | } 575 | for( let i = 0; i < this.attack * this.channels; i++) 576 | { 577 | this.pDelayBuffer[i] = 0; 578 | } 579 | for( let i = 0; i < this.nMaxBufferSection; i++) 580 | { 581 | this.pMaxBufferSlow[i] = 0; 582 | this.pIndexMaxInSection[i] = 0; 583 | } 584 | 585 | return true; 586 | } 587 | } 588 | 589 | --------------------------------------------------------------------------------