import * as Token from 'token-types' ; import { APEv2Parser } from '../apev2/APEv2Parser' ; import { FourCcToken } from '../common/FourCC' ; import { BasicParser } from '../common/BasicParser' ; import { IBlockHeader , IMetadataId , WavPack } from './WavPackToken' ; import * as initDebug from 'debug' ; const debug = initDebug ( 'music-metadata:parser:WavPack' ) ; /**
 * WavPack Parser
 */ export class WavPackParser extends BasicParser { private audioDataSize : number ; public async parse ( ) { this . audioDataSize = 0 ; await this . parseWavPackBlocks ( ) ; return APEv2Parser . tryParseApeHeader ( this . metadata , this . tokenizer , this . options ) ; } public async parseWavPackBlocks ( ) { do { const blockId = await this . tokenizer . peekToken < string > ( FourCcToken ) ; if ( blockId !== 'wvpk' ) break ; const header = await this . tokenizer . readToken < IBlockHeader > ( WavPack . BlockHeaderToken ) ; if ( header . BlockID !== 'wvpk' ) throw new Error ( 'Invalid WavPack Block-ID' ) ; debug ( `WavPack header blockIndex=${ header . blockIndex }, len=${ WavPack . BlockHeaderToken . len }` ) ; if ( header . blockIndex === 0 && ! this . metadata . format . container ) { this . metadata . setFormat ( 'container' , 'WavPack' ) ; this . metadata . setFormat ( 'lossless' , ! header . flags . isHybrid ) ; this . metadata . setFormat ( 'bitsPerSample' , header . flags . bitsPerSample ) ; if ( ! header . flags . isDSD ) { this . metadata . setFormat ( 'sampleRate' , header . flags . samplingRate ) ; this . metadata . setFormat ( 'duration' , header . totalSamples / header . flags . samplingRate ) ; } this . metadata . setFormat ( 'numberOfChannels' , header . flags . isMono ? 1 : 2 ) ; this . metadata . setFormat ( 'numberOfSamples' , header . totalSamples ) ; this . metadata . setFormat ( 'codec' , header . flags . isDSD ? 'DSD' : 'PCM' ) ; } const ignoreBytes = header . blockSize - ( WavPack . BlockHeaderToken . len - 8 ) ; if ( header . blockIndex === 0 ) { await this . parseMetadataSubBlock ( header , ignoreBytes ) ; } else { await this . tokenizer . ignore ( ignoreBytes ) ; } if ( header . blockSamples > 0 ) { this . audioDataSize += header . blockSize ; } } while ( ! this . tokenizer . fileInfo . size || this . tokenizer . fileInfo . size - this . tokenizer . position >= WavPack . BlockHeaderToken . len ) ; this . metadata . setFormat ( 'bitrate' , this . audioDataSize * 8 / this . metadata . format . duration ) ; } param remainingLength private async parseMetadataSubBlock ( header , remainingLength ) { while ( remainingLength > WavPack . MetadataIdToken . len ) { const id = await this . tokenizer . readToken < IMetadataId > ( WavPack . MetadataIdToken ) ; const dataSizeInWords = await this . tokenizer . readNumber ( id . largeBlock ? Token . UINT24_LE : Token . UINT8 ) ; const data = Buffer . alloc ( dataSizeInWords * 2 - ( id . isOddSize ? 1 : 0 ) ) ; await this . tokenizer . readBuffer ( data ) ; debug ( `Metadata Sub-Blocks functionId=0x${ id . functionId . toString ( 16 ) }, id.largeBlock=${ id . largeBlock },data-size=${ data . length }` ) ; switch ( id . functionId ) { case 0x0 : break ; case 0xe : debug ( 'ID_DSD_BLOCK' ) ; const mp = 1 << data . readUInt8 ( 0 ) ; const samplingRate = header . flags . samplingRate * mp * 8 ; if ( ! header . flags . isDSD ) throw new Error ( 'Only expect DSD block if DSD-flag is set' ) ; this . metadata . setFormat ( 'sampleRate' , samplingRate ) ; this . metadata . setFormat ( 'duration' , header . totalSamples / samplingRate ) ; break ; case 0x24 : debug ( 'ID_ALT_TRAILER: trailer for non-wav files' ) ; break ; case 0x26 : this . metadata . setFormat ( 'audioMD5' , data ) ; break ; case 0x2f : debug ( `ID_BLOCK_CHECKSUM: checksum=${ data . toString ( 'hex' ) }` ) ; break ; default : debug ( `Ignore unsupported meta-sub-block-id functionId=0x${ id . functionId . toString ( 16 ) }` ) ; break ; } remainingLength -= WavPack . MetadataIdToken . len + ( id . largeBlock ? Token . UINT24_LE . len : Token . UINT8 . len ) + dataSizeInWords * 2 ; debug ( `remainingLength=${ remainingLength }` ) ; if ( id . isOddSize ) this . tokenizer . ignore ( 1 ) ; } if ( remainingLength !== 0 ) throw new Error ( 'metadata-sub-block should fit it remaining length' ) ; } }