declare var require ; declare var Editor ; declare namespace cc { export class AudioClip { } export class _Audio { } } let Fs = require ( "fs" ) ; let Path = require ( "path" ) ; let Png = require ( "./pngjs/lib/png" ) . PNG ; class FFTTextureGenerator { protected _clip : cc . AudioClip = null ; protected _analyser : any = null ; protected _freqSize : number = 32 ; protected _fftTexture : Uint8Array = null ; protected _sampleBuff : Uint8Array = null ; protected _sourceNode : AudioBufferSourceNode = null ; protected _outputPath : string = null ; public Generate ( uuid , audioPath ) { let that = this ; Editor . log ( `[VIS] start loading ${ audioPath }` ) ; cc . assetManager . loadAny ( { uuid : uuid } , ( err , clip ) => { if ( err ) Editor . log ( err ) ; that . _clip = clip ; const dir = Path . dirname ( audioPath ) ; const ext = Path . extname ( audioPath ) ; const baseName = Path . basename ( audioPath , ext ) ; that . _outputPath = Path . join ( dir , baseName + "-fft.png" ) ; that . ExtractFFTAndSave ( ) ; } ) ; } public WriteFrame ( fftTexture , frame , buff ) { let sampleLength = buff . length ; let samplePerRow = 512 / sampleLength ; let audio = this . _clip . _audio ; let elapseInSec = audio . length / audio . sampleRate ; let samples = Math . floor ( elapseInSec * 60 ) ; let sx = frame * sampleLength ; if ( sx + sampleLength > fftTexture . length ) { console . error ( "fftTexture overflow" ) ; return ; } fftTexture . set ( buff , sx ) ; } protected ExtractFFTAndSave ( ) { let audio = this . _clip . _audio ; let offlineAudioCtx = new OfflineAudioContext ( audio . numberOfChannels , audio . length , audio . sampleRate ) ; let analyser = this . _analyser = offlineAudioCtx . createAnalyser ( ) ; analyser . fftSize = this . _freqSize * 2 ; let sourceNode = this . _sourceNode = offlineAudioCtx . createBufferSource ( ) ; sourceNode . buffer = audio ; sourceNode . connect ( analyser ) ; analyser . connect ( offlineAudioCtx . destination ) ; let elapseInSec = audio . length / audio . sampleRate ; let originSamples = Math . floor ( elapseInSec * 60 ) ; let bytesPerSample = analyser . frequencyBinCount ; let sampleBuff = this . _sampleBuff = new Uint8Array ( bytesPerSample ) ; let samplePerRow = 512 / bytesPerSample ; let samples = Math . ceil ( originSamples / samplePerRow ) * samplePerRow ; let fftTexture = this . _fftTexture = new Uint8Array ( samples * bytesPerSample ) ; Editor . log ( `[VIS] texture width: ${ 512 }, height: ${ fftTexture . length / 512 }, samples: ${ samples }, samplePerRow: ${ samplePerRow }` ) ; for ( let i = 0 ; i < originSamples ; ++ i ) { offlineAudioCtx . suspend ( i / 60 ) . then ( ( ) => { analyser . getByteFrequencyData ( sampleBuff ) ; that . WriteFrame ( fftTexture , i , sampleBuff ) ; } ) . then ( ( ) => { offlineAudioCtx . resume ( ) ; } ) ; } let that = this ; offlineAudioCtx . startRendering ( ) ; offlineAudioCtx . oncomplete = ( ev ) => { sourceNode . stop ( ) ; that . SaveFFTTexture ( fftTexture , 512 , fftTexture . length / 512 ) ; that . ReleaseAudioBuffer ( ) ; Editor . log ( `[VIS] output: ${ that . _outputPath }` ) ; Editor . Ipc . sendToMain ( "music-visualizer:on-extract-finished" , that . _outputPath ) ; } ; sourceNode . start ( 0 ) ; } protected SaveFFTTexture ( texture , width , height ) { let img = new Png ( { colorType : 0 , inputColorType : 0 , width : width , height : height } ) ; img . data = texture ; img . pack ( ) . pipe ( Fs . createWriteStream ( this . _outputPath ) ) ; } protected ReleaseAudioBuffer ( ) { this . _clip = null ; this . _analyser = null ; this . _fftTexture = null ; this . _sampleBuff = null ; this . _sourceNode = null ; } } export = FFTTextureGenerator ;