{-# LANGUAGE RankNTypes, FlexibleContexts #-}{-
This module is not meant primarily for instructive and pedagogical purposes.
As such, it is not fully featured, and sacrifices performance and generality
for clarity of code.
-}moduleData.Iteratee.Codecs.Wave(WAVEDE(..),WAVEDE_ENUM(..),WAVE_CHUNK(..),AudioFormat(..),waveReader,readRiff,waveChunk,chunkToString,dictReadFormat,dictReadFirstFormat,dictReadLastFormat,dictReadFirstData,dictReadLastData,dictReadData,dictProcessData)whereimportData.Iteratee.BaseimportqualifiedData.Iteratee.BaseasIterimportData.Iteratee.BinaryimportData.Char(chr,ord)importData.IntimportData.WordimportData.Bits(shiftL)importData.MaybeimportqualifiedData.IntMapasIM-- =====================================================-- WAVE libary code-- useful type synonymstypeL=[]-- |A WAVE directory is a list associating WAVE chunks with-- a record WAVEDEtypeWAVEDict=IM.IntMap[WAVEDE]dataWAVEDE=WAVEDE{wavede_count::Int,-- ^length of chunkwavede_type::WAVE_CHUNK,-- ^type of chunkwavede_enum::WAVEDE_ENUM-- ^enumerator to get values of chunk}dataWAVEDE_ENUM=WEN_BYTE(foralla.EnumeratorGMMLWord8LWord8IOa)|WEN_DUB(foralla.EnumeratorGMMLWord8LDoubleIOa)-- |Standard WAVE ChunksdataWAVE_CHUNK=WAVE_FMT-- ^Format|WAVE_DATA-- ^Data|WAVE_OTHERString-- ^Otherderiving(Eq,Ord,Show)instanceEnumWAVE_CHUNKwherefromEnumWAVE_FMT=1fromEnumWAVE_DATA=2fromEnum(WAVE_OTHER_)=3toEnum1=WAVE_FMTtoEnum2=WAVE_DATAtoEnum3=WAVE_OTHER""toEnum_=error"Invalid enumeration value"-- ------------------- wave chunk reading/writing functions-- |Convert a string to WAVE_CHUNK typewaveChunk::String->MaybeWAVE_CHUNKwaveChunkstr|str=="fmt "=JustWAVE_FMT|str=="data"=JustWAVE_DATA|lengthstr==4=Just$WAVE_OTHERstr|otherwise=Nothing-- |Convert a WAVE_CHUNK to the representative stringchunkToString::WAVE_CHUNK->StringchunkToStringWAVE_FMT="fmt "chunkToStringWAVE_DATA="data"chunkToString(WAVE_OTHERstr)=str-- -----------------dataAudioFormat=AudioFormat{numberOfChannels::NumChannels,-- ^Number of channels in the audio datasampleRate::SampleRate,-- ^Sample rate of the audiobitDepth::BitDepth-- ^Bit depth of the audio data}deriving(Show,Eq)typeNumChannels=IntegertypeSampleRate=IntegertypeBitDepth=Integer-- convenience function to read a 4-byte ASCII stringstringRead4::Monadm=>IterateeGLWord8mStringstringRead4=dos1<-Iter.heads2<-Iter.heads3<-Iter.heads4<-Iter.headreturn$map(chr.fromIntegral)[s1,s2,s3,s4]-- ------------------- |The library function to read the WAVE dictionarywaveReader::IterateeGLWord8IO(MaybeWAVEDict)waveReader=doreadRifftot_size<-endianRead4LSBreadRiffWavechunks_m<-findChunks$fromIntegraltot_sizeloadDict$joinMchunks_m-- |Read the RIFF header of a file.readRiff::IterateeGLWord8IO()readRiff=docnt<-heads$fmap(fromIntegral.ord)"RIFF"ifcnt==4thenreturn()elsethrowErr$Err"Bad RIFF header"-- | Read the WAVE part of the RIFF header.readRiffWave::IterateeGLWord8IO()readRiffWave=docnt<-heads$fmap(fromIntegral.ord)"WAVE"ifcnt==4thenreturn()elsethrowErr$Err"Bad RIFF/WAVE header"-- | An internal function to find all the chunks. It assumes that the-- stream is positioned to read the first chunk.findChunks::Int->IterateeGLWord8IO(Maybe[(Int,WAVE_CHUNK,Int)])findChunksn=findChunks'12[]wherefindChunks'offsetacc=dotyp<-stringRead4count<-endianRead4LSBcasewaveChunktypofNothing->(throwErr.Err$"Bad subchunk descriptor: "++showtyp)>>returnNothingJustchk->letnewpos=offset+8+countincasenewpos>=fromIntegralnofTrue->return.Just$reverse$(fromIntegraloffset,chk,fromIntegralcount):accFalse->doIter.seek$fromIntegralnewposfindChunks'newpos$(fromIntegraloffset,chk,fromIntegralcount):accloadDict::[(Int,WAVE_CHUNK,Int)]->IterateeGLWord8IO(MaybeWAVEDict)loadDict=foldlread_entry(return(JustIM.empty))whereread_entrydictM(offset,typ,count)=dictM>>=maybe(returnNothing)(\dict->doenum_m<-readValuedictoffsettypcountcase(enum_m,IM.lookup(fromEnumtyp)dict)of(Justenum,Nothing)->--insert new entryreturn.Just$IM.insert(fromEnumtyp)[WAVEDE(fromIntegralcount)typenum]dict(Justenum,Just_vals)->--existing entryreturn.Just$IM.update(\ls->Just$ls++[WAVEDE(fromIntegralcount)typenum])(fromEnumtyp)dict(Nothing,_)->return(Justdict))readValue::WAVEDict->Int->-- OffsetWAVE_CHUNK->-- Chunk typeInt->-- CountIterateeGLWord8IO(MaybeWAVEDE_ENUM)readValue_dictoffset_0=dothrowErr.Err$"Zero count in the entry of chunk at: "++showoffsetreturnNothingreadValuedictoffsetWAVE_DATAcount=dofmt_m<-dictReadLastFormatdictcasefmt_mofJustfmt->return.Just.WEN_DUB$\iter_dub->return$doIter.seek(8+fromIntegraloffset)letiter=Iter.convStream(convFuncfmt)iter_dubjoinI.joinI.takeRcount$iterNothing->dothrowErr.Err$"No valid format for data chunk at: "++showoffsetreturnNothing-- return the WaveFormat iterateereadValue_dictoffsetWAVE_FMTcount=return.Just.WEN_BYTE$\iter->return$doIter.seek(8+fromIntegraloffset)Iter.joinI$Iter.takeRcountiter-- for WAVE_OTHER, return Word8s and maybe the user can parse themreadValue_dictoffset(WAVE_OTHER_str)count=return.Just.WEN_BYTE$\iter->return$doIter.seek(8+fromIntegraloffset)Iter.joinI$Iter.takeRcountiter-- |Convert Word8s to DoublesconvFunc::AudioFormat->IterateeGLWord8IO(Maybe(LDouble))convFunc(AudioFormat_nc_sr8)=(fmap.fmap)((:[]).normalize8.(fromIntegral::Word8->Int8))(fmapeitherToMaybe(checkErrIter.head))convFunc(AudioFormat_nc_sr16)=(fmap.fmap)((:[]).normalize16.(fromIntegral::Word16->Int16))(fmapeitherToMaybe(checkErr$endianRead2LSB))convFunc(AudioFormat_nc_sr24)=(fmap.fmap)((:[]).normalize24.(fromIntegral::Word32->Int32))(fmapeitherToMaybe(checkErr$endianRead3LSB))convFunc(AudioFormat_nc_sr32)=(fmap.fmap)((:[]).normalize32.(fromIntegral::Word32->Int32))(fmapeitherToMaybe(checkErr$endianRead4LSB))convFunc_=returnNothingeitherToMaybe::Eitherab->MaybebeitherToMaybe=either(constNothing)Just-- |An Iteratee to read a wave format chunksWaveFormat::IterateeGLWord8IO(MaybeAudioFormat)sWaveFormat=dof'<-endianRead2LSB--data format, 1==PCMnc<-endianRead2LSBsr<-endianRead4LSBIter.drop6bd<-endianRead2LSBcasef'==1ofTrue->return.Just$AudioFormat(fromIntegralnc)(fromIntegralsr)(fromIntegralbd)False->returnNothing-- ----------------------- functions to assist with reading from the dictionary-- |Read the first format chunk in the WAVE dictionary.dictReadFirstFormat::WAVEDict->IterateeGLWord8IO(MaybeAudioFormat)dictReadFirstFormatdict=caseIM.lookup(fromEnumWAVE_FMT)dictofJust[]->returnNothingJust((WAVEDE_WAVE_FMT(WEN_BYTEenum)):_xs)->joinIM$enumsWaveFormat_->returnNothing-- |Read the last fromat chunk from the WAVE dictionary. This is useful-- when parsing all chunks in the dictionary.dictReadLastFormat::WAVEDict->IterateeGLWord8IO(MaybeAudioFormat)dictReadLastFormatdict=caseIM.lookup(fromEnumWAVE_FMT)dictofJust[]->returnNothingJustxs->let(WAVEDE_WAVE_FMT(WEN_BYTEenum))=lastxsinjoinIM$enumsWaveFormat_->returnNothing-- |Read the specified format chunk from the WAVE dictionarydictReadFormat::Int->--Index in the format chunk list to readWAVEDict->--DictionaryIterateeGLWord8IO(MaybeAudioFormat)dictReadFormatixdict=caseIM.lookup(fromEnumWAVE_FMT)dictofJustxs->let(WAVEDE_WAVE_FMT(WEN_BYTEenum))=(!!)xsixinjoinIM$enumsWaveFormat_->returnNothing-- |Read the first data chunk in the WAVE dictionary.dictReadFirstData::WAVEDict->IterateeGLWord8IO(Maybe[Double])dictReadFirstDatadict=caseIM.lookup(fromEnumWAVE_DATA)dictofJust[]->returnNothingJust((WAVEDE_WAVE_DATA(WEN_DUBenum)):_xs)->doe<-joinIM$enumIter.stream2listreturn$Juste_->returnNothing-- |Read the last data chunk in the WAVE dictionary.dictReadLastData::WAVEDict->IterateeGLWord8IO(Maybe[Double])dictReadLastDatadict=caseIM.lookup(fromEnumWAVE_DATA)dictofJust[]->returnNothingJustxs->let(WAVEDE_WAVE_DATA(WEN_DUBenum))=lastxsindoe<-joinIM$enumIter.stream2listreturn$Juste_->returnNothing-- |Read the specified data chunk from the WAVE dictionary.dictReadData::Int->--Index in the data chunk list to readWAVEDict->--DictionaryIterateeGLWord8IO(Maybe[Double])dictReadDataixdict=caseIM.lookup(fromEnumWAVE_DATA)dictofJustxs->let(WAVEDE_WAVE_DATA(WEN_DUBenum))=(!!)xsixindoe<-joinIM$enumIter.stream2listreturn$Juste_->returnNothing-- |Read the specified data chunk from the dictionary, applying the-- data to the specified IterateeG.dictProcessData::Int->-- Index in the data chunk list to readWAVEDict->-- DictionaryIterateeGLDoubleIOa->IterateeGLWord8IO(Maybea)dictProcessDataixdictiter=caseIM.lookup(fromEnumWAVE_DATA)dictofJustxs->let(WAVEDE_WAVE_DATA(WEN_DUBenum))=(!!)xsixindoe<-joinIM$enumiterreturn$Juste_->returnNothing-- ----------------------- convenience functions-- |Convert (Maybe []) to []. Nothing maps to an empty list.joinM::Maybe[a]->[a]joinMNothing=[]joinM(Justa)=a-- |Normalize a given value for the provided bit depth.normalize::Integrala=>BitDepth->a->Doublenormalize8a=(fromIntegrala-128)/128normalizebda=case(a>0)ofTrue->fromIntegrala/divPosFalse->fromIntegrala/divNegwheredivPos=fromIntegral(1`shiftL`fromIntegral(bd-1)::Int)-1divNeg=fromIntegral(1`shiftL`fromIntegral(bd-1)::Int)