@@ -10,6 +10,10 @@ import lime.media.vorbis.Vorbis;
1010import lime .media .vorbis .VorbisFile ;
1111#end
1212
13+ #if (target.threaded)
14+ import sys .thread .Mutex ;
15+ #end
16+
1317typedef AudioAnalyzerCallback = Int -> Int -> Void ;
1418
1519/**
@@ -49,7 +53,7 @@ final class AudioAnalyzer {
4953 * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
5054 * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
5155 * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
52- * @return Output of levels/bars that ranges from 0 to 1
56+ * @return Output of levels/bars that ranges from 0 to 1.
5357 */
5458 public static function getLevelsFromFrequencies (frequencies : Array <Float >, sampleRate : Int , barCount : Int , ? levels : Array <Float >, ratio = 0.0 , minDb = - 63.0 , maxDb = - 10.0 , minFreq = 20.0 , maxFreq = 22000.0 ): Array <Float > {
5559 if (levels == null ) levels = [];
@@ -86,6 +90,138 @@ final class AudioAnalyzer {
8690 return levels ;
8791 }
8892
93+ static var __reverseIndices : Array <Array <Int >> = [];
94+ static var __windows : Array <Array <Float >> = [];
95+ static var __twiddleReals : Array <Array <Float >> = [];
96+ static var __twiddleImags : Array <Array <Float >> = [];
97+ static var __freqReals : Array <Array <Float >> = [];
98+ static var __freqImags : Array <Array <Float >> = [];
99+ #if (target.threaded)
100+ static var __mutex : Mutex = new Mutex ();
101+ static var __freqCalculating : Int = 0 ;
102+ #end
103+
104+ /**
105+ * Gets frequencies from the samples.
106+ * @param samples The samples (can be from AudioAnalyzer.getSamples).
107+ * @param fftN How much samples for the fft to get, Has to be power of two, or it won't work.
108+ * @param useWindowing Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing), Most of the time it's not worth it.
109+ * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
110+ * @return Output of frequencies.
111+ */
112+ public static function getFrequenciesFromSamples (samples : Array <Float >, fftN = 2048 , useWindowing = false , ? frequencies : Array <Float >): Array <Float > {
113+ var log = Math .floor (Math .log (fftN ) / 0.6931471805599453 );
114+ if (log == 0 ) throw " AudioAnalyzer.getFrequenciesFromSamples: Cannot insert a fftN of 1" ;
115+
116+ var i = log - 1 ;
117+ fftN = 1 << log ;
118+
119+ #if (target.threaded) __mutex .acquire (); #end
120+ var reals : Array <Float > = __freqReals [__freqCalculating ], imags : Array <Float > = __freqImags [__freqCalculating ];
121+ if (reals == null ) {
122+ __freqReals .push (reals = []);
123+ __freqImags .push (imags = []);
124+ }
125+ __freqCalculating ++ ;
126+
127+ var reverseIndices : Array <Int > = __reverseIndices [i ];
128+ var windows : Array <Float > = __windows [i ];
129+ var twiddleReals : Array <Float > = __twiddleReals [i ];
130+ var twiddleImags : Array <Float > = __twiddleImags [i ];
131+
132+ if (reverseIndices == null ) {
133+ __reverseIndices .resize (log );
134+ __windows .resize (log );
135+ __twiddleReals .resize (log );
136+ __twiddleImags .resize (log );
137+
138+ (reverseIndices = []).resize (fftN );
139+ (windows = []).resize (fftN );
140+ (twiddleReals = []).resize (fftN );
141+ (twiddleImags = []).resize (fftN );
142+
143+ var f ;
144+ for (i in 0 ... fftN ) {
145+ f = 2 * Math .PI * (i / fftN );
146+ windows [i ] = 0.42 - 0.5 * Math .cos (f ) + 0.08 * Math .cos (2 * f );
147+ reverseIndices [i ] = __bitReverse (i , log );
148+ twiddleReals [i ] = Math .cos (- f );
149+ twiddleImags [i ] = Math .sin (- f );
150+ }
151+
152+ __reverseIndices [i ] = reverseIndices ;
153+ __windows [i ] = windows ;
154+ __twiddleReals [i ] = twiddleReals ;
155+ __twiddleImags [i ] = twiddleImags ;
156+ }
157+
158+ #if (target.threaded) __mutex .release (); #end
159+
160+ if (fftN > reals .length ) {
161+ reals .resize (fftN );
162+ imags .resize (fftN );
163+ }
164+
165+ if (frequencies == null ) frequencies = [];
166+ frequencies .resize (1 << i );
167+
168+ i = samples .length ;
169+ while (i > 0 ) {
170+ i -- ;
171+ if (useWindowing ) reals [reverseIndices [i ]] = samples [i ] * windows [i ];
172+ else reals [reverseIndices [i ]] = samples [i ];
173+ imags [i ] = 0 ;
174+ }
175+
176+ var size = 1 , n = fftN , half = 1 , k , i0 , i1 , t , tr : Float , ti : Float ;
177+ while ((size << = 1 ) < fftN ) {
178+ n >> = 1 ;
179+ i = 0 ;
180+ while (i < fftN ) {
181+ k = 0 ;
182+ while (k < half ) {
183+ i1 = (i0 = i + k ) + half ;
184+ t = (k * n ) % fftN ;
185+
186+ tr = reals [i1 ] * twiddleReals [t ] - imags [i1 ] * twiddleImags [t ];
187+ ti = reals [i1 ] * twiddleImags [t ] + imags [i1 ] * twiddleReals [t ];
188+ reals [i1 ] = reals [i0 ] - tr ;
189+ imags [i1 ] = imags [i0 ] - ti ;
190+ reals [i0 ] + = tr ;
191+ imags [i0 ] + = ti ;
192+
193+ k ++ ;
194+ }
195+ i + = size ;
196+ }
197+ half = size ;
198+ }
199+
200+ tr = 1.0 / fftN ;
201+ i = 1 << (log - 1 );
202+ while (i > 1 ) {
203+ i -- ;
204+ frequencies [i ] = 2 * Math .sqrt (reals [i ] * reals [i ] + imags [i ] * imags [i ]) * tr ;
205+ }
206+ frequencies [0 ] = Math .sqrt (reals [0 ] * reals [0 ] + imags [0 ] * imags [0 ]) * tr ;
207+
208+ #if (target.threaded) __mutex .acquire (); #end
209+ __freqCalculating -- ;
210+ #if (target.threaded) __mutex .release (); #end
211+
212+ return frequencies ;
213+ }
214+
215+ static function __bitReverse (x : Int , log : Int ): Int {
216+ var y = 0 , i = log ;
217+ while (i > 0 ) {
218+ y = (y << 1 ) | (x & 1 );
219+ x >> = 1 ;
220+ i -- ;
221+ }
222+ return y ;
223+ }
224+
89225 /**
90226 * The current sound to analyze.
91227 */
@@ -97,13 +233,13 @@ final class AudioAnalyzer {
97233 *
98234 * Has to be power of two, or it won't work.
99235 */
100- public var fftN ( default , set ) : Int ;
236+ public var fftN : Int ;
101237
102238 /**
103239 * Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing).
104240 * Most of the time looks bad with this.
105241 */
106- public var useWindowingFFT : Bool = false ;
242+ public var useWindowingFFT : Bool ;
107243
108244 /**
109245 * The current buffer from sound.
@@ -137,31 +273,26 @@ final class AudioAnalyzer {
137273
138274 // samples
139275 var __sampleIndex : Int ;
276+ var __sampleChannel : Int ;
277+ var __sampleToValue : Float ;
278+ var __sampleOutputMerge : Bool ;
140279 var __sampleOutputLength : Int ;
141280 var __sampleOutput : Array <Float >;
142281
143- // fft
144- var __N2 : Int ;
145- var __logN : Int ;
282+ // frequencies
146283 var __freqSamples : Array <Float >;
147- var __reverseIndices : Array <Int > = [];
148- var __windows : Array <Float > = [];
149- var __twiddleReals : Array <Float > = [];
150- var __twiddleImags : Array <Float > = [];
151- var __freqReals : Array <Float > = [];
152- var __freqImags : Array <Float > = [];
153-
154- // levels
155284 var __frequencies : Array <Float >;
156285
157286 /**
158287 * Creates an analyzer for specified FlxSound
159288 * @param sound An FlxSound to analyze.
160289 * @param fftN How much samples for fft to get (Optional, default 2048, 4096 is recommended for highest quality).
290+ * @param useWindowingFFT Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing).
161291 */
162- public function new (sound : FlxSound , fftN = 2048 ) {
292+ public function new (sound : FlxSound , fftN = 2048 , useWindowingFFT = false ) {
163293 this .sound = sound ;
164294 this .fftN = fftN ;
295+ this .useWindowingFFT = useWindowingFFT ;
165296 __check ();
166297 }
167298
@@ -180,46 +311,6 @@ final class AudioAnalyzer {
180311 __max .resize (buffer .channels );
181312 }
182313
183- inline function set_fftN (v : Int ): Int {
184- if (fftN == (fftN = nextPow2 (v ))) return fftN ;
185-
186- __logN = Math .floor (Math .log (fftN ) / Math .log (2 ));
187- __N2 = fftN >> 1 ;
188- __freqReals .resize (fftN );
189- __freqImags .resize (fftN );
190- __reverseIndices .resize (fftN );
191- __windows .resize (fftN );
192- __twiddleReals .resize (fftN );
193- __twiddleImags .resize (fftN );
194-
195- var f , a ;
196- for (i in 0 ... fftN ) {
197- f = i / (fftN - 1 );
198- __windows [i ] = 0.42 - 0.5 * Math .cos (2 * Math .PI * f ) + 0.08 * Math .cos (4 * Math .PI * f );
199- __reverseIndices [i ] = __bitReverse (i );
200- __twiddleReals [i ] = Math .cos (a = - 2 * Math .PI * i / fftN );
201- __twiddleImags [i ] = Math .sin (a );
202- }
203-
204- return fftN ;
205- }
206-
207- inline function nextPow2 (x : Int ): Int {
208- var p = 1 ;
209- while (p < x ) p << = 1 ;
210- return p ;
211- }
212-
213- inline function __bitReverse (x : Int ): Int {
214- var y = 0 , i = __logN ;
215- while (i > 0 ) {
216- y = (y << 1 ) | (x & 1 );
217- x >> = 1 ;
218- i -- ;
219- }
220- return y ;
221- }
222-
223314 /**
224315 * Gets levels from an attached FlxSound from startPos, basically a minimized of frequencies.
225316 * @param startPos Start Position to get from sound in milliseconds.
@@ -230,7 +321,7 @@ final class AudioAnalyzer {
230321 * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
231322 * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
232323 * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
233- * @return Output of levels/bars that ranges from 0 to 1
324+ * @return Output of levels/bars that ranges from 0 to 1.
234325 */
235326 public function getLevels (startPos : Float , barCount : Int , ? levels : Array <Float >, ? ratio : Float , ? minDb : Float , ? maxDb : Float , ? minFreq : Float , ? maxFreq : Float ): Array <Float >
236327 return inline getLevelsFromFrequencies (__frequencies = getFrequencies (startPos , __frequencies ), buffer .sampleRate , barCount , levels , ratio , minDb , maxDb , minFreq , maxFreq );
@@ -239,50 +330,10 @@ final class AudioAnalyzer {
239330 * Gets frequencies from an attached FlxSound from startPos.
240331 * @param startPos Start Position to get from sound in milliseconds.
241332 * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
242- * @return Output of frequencies
333+ * @return Output of frequencies.
243334 */
244- public function getFrequencies (startPos : Float , ? frequencies : Array <Float >): Array <Float > {
245- __freqSamples = getSamples (startPos , fftN , true , __freqSamples );
246-
247- if (frequencies == null ) frequencies = [];
248- frequencies .resize (__N2 );
249-
250- var i = fftN ;
251- while (i > 0 ) {
252- i -- ;
253- __freqReals [__reverseIndices [i ]] = __freqSamples [i ] * (useWindowingFFT ? __windows [i ] : 1 );
254- __freqImags [i ] = 0 ;
255- }
256-
257- var size = 1 , n = fftN , half = 1 , k , i0 , i1 , t , tr : Float , ti : Float ;
258- while ((size << = 1 ) < fftN ) {
259- n >> = 1 ;
260- i = 0 ;
261- while (i < fftN ) {
262- k = 0 ;
263- while (k < half ) {
264- i1 = (i0 = i + k ) + half ;
265- t = (k * n ) % fftN ;
266-
267- tr = __freqReals [i1 ] * __twiddleReals [t ] - __freqImags [i1 ] * __twiddleImags [t ];
268- ti = __freqReals [i1 ] * __twiddleImags [t ] + __freqImags [i1 ] * __twiddleReals [t ];
269- __freqReals [i1 ] = __freqReals [i0 ] - tr ;
270- __freqImags [i1 ] = __freqImags [i0 ] - ti ;
271- __freqReals [i0 ] + = tr ;
272- __freqImags [i0 ] + = ti ;
273-
274- k ++ ;
275- }
276- i + = size ;
277- }
278- half = size ;
279- }
280-
281- frequencies [i = 0 ] = Math .sqrt (__freqReals [0 ] * __freqReals [0 ] + __freqImags [0 ] * __freqImags [0 ]) * (tr = 1.0 / fftN );
282- while (++ i < __N2 ) frequencies [i ] = 2 * Math .sqrt (__freqReals [i ] * __freqReals [i ] + __freqImags [i ] * __freqImags [i ]) * tr ;
283-
284- return frequencies ;
285- }
335+ public function getFrequencies (startPos : Float , ? frequencies : Array <Float >): Array <Float >
336+ return inline getFrequenciesFromSamples (__freqSamples = getSamples (startPos , fftN , true , __freqSamples ), fftN , useWindowingFFT , frequencies );
286337
287338 /**
288339 * Analyzes an attached FlxSound from startPos to endPos in milliseconds to get the amplitudes.
@@ -329,32 +380,49 @@ final class AudioAnalyzer {
329380 * @param startPos Start Position to get from sound in milliseconds.
330381 * @param length Length of Samples.
331382 * @param mono Merge all of the byte channels of samples in one channel instead (Optional).
332- * @param Output that gets passed into this function (Optional).
333- * @return Output of
383+ * @param channel What channels to get from? (-1 == All Channels, Optional, this will be ignored if mono is enabled).
384+ * @param output An Output that gets passed into this function, usually for to avoid memory leaks (Optional).
385+ * @param outputMerge Merge with previous values (Optional, default false).
386+ * @return Output of samples.
334387 */
335- public function getSamples (startPos : Float , length : Int , mono = true , ? output : Array <Float >): Array <Float > {
336- ((output == null ) ? (__sampleOutput = output = []) : (__sampleOutput = output )).resize (__sampleOutputLength = length * (mono ? 1 : buffer .channels ));
388+ public function getSamples (startPos : Float , length : Int , mono = true , channel = - 1 , ? output : Array <Float >, ? outputMerge = false ): Array <Float > {
389+ ((! mono && (__sampleChannel = channel ) == - 1 ) ? (__sampleOutputLength = length * buffer .channels ) : (__sampleOutputLength = length ));
390+ ((output == null ) ? (__sampleOutput = output = []) : (__sampleOutput = output )).resize (__sampleOutputLength );
391+ ((mono ) ? (__sampleToValue = 1.0 / (byteSize * buffer .channels )) : (__sampleToValue = 1.0 / byteSize ));
392+ __sampleOutputMerge = outputMerge ;
337393 __sampleIndex = 0 ;
338394
339395 __check ();
340- __read (startPos , startPos + (length / __toBits * buffer .channels ), mono ? __getSamplesCallbackMerge : __getSamplesCallback );
396+ __read (startPos , startPos + (length / __toBits * buffer .channels ), mono ? __getSamplesCallbackMono : ( channel == - 1 ? __getSamplesCallback : __getSamplesCallbackChannel ) );
341397
342398 __sampleOutput = null ;
343399 return output ;
344400 }
345401
346- function __getSamplesCallbackMerge (b : Int , c : Int ): Void if (__sampleIndex < __sampleOutputLength ) {
347- if (c == 0 ) __sampleOutput [__sampleIndex ] = b / buffer .channels / byteSize ;
402+ function __getSamplesCallbackMono (b : Int , c : Int ): Void if (__sampleIndex < __sampleOutputLength ) {
403+ if (c == 0 ) {
404+ if (__sampleOutputMerge ) __sampleOutput [__sampleIndex ] + = b * __sampleToValue ;
405+ else __sampleOutput [__sampleIndex ] = b * __sampleToValue ;
406+ }
348407 else if (c == buffer .channels ) {
349- __sampleOutput [__sampleIndex ] + = b / buffer . channels / byteSize ;
408+ __sampleOutput [__sampleIndex ] + = b * __sampleToValue ;
350409 __sampleIndex ++ ;
351410 }
352411 else
353- __sampleOutput [__sampleIndex ] + = b / buffer .channels / byteSize ;
412+ __sampleOutput [__sampleIndex ] + = b * __sampleToValue ;
413+ }
414+
415+ function __getSamplesCallbackChannel (b : Int , c : Int ): Void if (__sampleIndex < __sampleOutputLength ) {
416+ if (c == __sampleChannel ) {
417+ if (__sampleOutputMerge ) __sampleOutput [__sampleIndex ] + = b * __sampleToValue ;
418+ else __sampleOutput [__sampleIndex ] = b * __sampleToValue ;
419+ __sampleIndex ++ ;
420+ }
354421 }
355422
356423 function __getSamplesCallback (b : Int , c : Int ): Void if (__sampleIndex < __sampleOutputLength ) {
357- __sampleOutput [__sampleIndex ] = b / byteSize ;
424+ if (__sampleOutputMerge ) __sampleOutput [__sampleIndex ] + = b * __sampleToValue ;
425+ else __sampleOutput [__sampleIndex ] = b * __sampleToValue ;
358426 __sampleIndex ++ ;
359427 }
360428
0 commit comments