Application Center - Maplesoft

App Preview:

Reading and writing .WAV sound files - the WAV package

You can switch back to the summary page by clicking here.

Learn about Maple
Download Application


 

WAV.mws

Creating the WAV package in Maple 6

Paul Goossens

(c) 2000 Waterloo Maple Inc

This brings together the Maple routines outlined in "Creating Sound Sample Files using Maple 6" for reading and writing .WAV sound files.

Using the new module structure in Maple 6, it is now very easy to create a package. See "Creating Maple 6 Library Packages" at  http://iweb.maplesoft.com/M6-mirror/apps/packages/acpackages.html for more information.

> WAV:=module()                      # Define the module name
export ReadWAV, WriteWAV:          # Define what functions will be available to the user

local Integer2Bytes, Bytes2Integer:# Define what functions are internal to the package only

option package:                    # Define the type of module this is, ie "package"


# From here to the "end module" are the function definitions, described in the above worksheet.


# Maple package, WAV

# Version 1.01.

# Author: Paul Goossens, Cassidy Gentle, Douglas Harder, Waterloo Maple Inc.

# Routines for creating and importing standard Windows

# sound files (.wav).


Integer2Bytes:=proc(Integer, nBytes)

local ByteArray,ib:


# Converts an integer into an inputted number of bytes

# Intel format (LSB..MSB) assumed.


ByteArray:=Array(1..nBytes,datatype=integer[2]):

ByteArray[1]:=Integer mod 2^8:


for ib from 2 to nBytes do

   ByteArray[ib]:=(Integer-add(ByteArray[isum]*2^((isum-1)*8),isum=1..(ib-1)) mod 2^(ib*8))/2^((ib-1)*8):

end do:

ByteArray;

end:


Bytes2Integer:=proc(Bytes)


# Converts a list of byte values and converts them to an integer.

# Returns an unsigned integer.

# Intel format (LSB..MSB) assumed.


add(Bytes[i]*2^(8*(i-1)),i=1..nops(Bytes)):


end:


# WriteWAV() will create a sound file from an array of data.

# The data array can be 1 column (Mono), 2 column (Stereo),

# or multichannel. The process has the following steps:

#  1) determine the number of channels and data points (samples)

#  2) interweave multichannel data into a single data stream

#  3) quantize the data to the input Resolution (4, 8 or 16 bit)

#  4) splits the data into binary bytes (unsigned byte for 4 and

#     8 bit, signed integer for 16 bit, Intel format assumed)

#  5) create the WAV format header and attach it to the data

#  6) write the data out to a file using the writebytes() function


WriteWAV:=proc(FileName::string,

              SoundData::Array,

              SampleFreq::{float,integer},

              Resolution::{4,8,16})


local RIFF, WAVE, ckID, DATA, SigSize, nBytesPerSample, nCh, nChunkSize, wFormatTag, nChannels, nSamplesPerSec, BPS, nAvgBytesPerSec, NBA, nBlockAlign, nDataBytes, nBitsPerSample, nBytesTotal, FileSize, EndHeader, Header, Data, DataArray, ByteArray, fd,

Range, RangeMin, RangeMax, QuantRange, DeltaRange, Offset,

i, j, BAtemp, WriteArray:        



#Find out how many channels and number of points...

nCh:=ArrayNumDims(SoundData):

if nCh<1 then RETURN("No data"): end if:

print(cat(nCh, " channel(s) found."));


SigSize:=ArrayNumElems(SoundData)/nCh;

if SigSize=0 then RETURN("No data"): end if:

print(cat(SigSize, " points per channel found."));


# Define the header tags as byte values...

RIFF:=convert("RIFF",'bytes');

WAVE:=convert("WAVE",'bytes');

ckID:=convert("fmt ",'bytes');

DATA:=convert("data",'bytes');


#Set up Signal parameters...

nBytesPerSample:=ceil(Resolution/8);


#Header Tags...

nChunkSize:=convert(Integer2Bytes(16,4),list);

wFormatTag:=[1,0];

nChannels:=[nCh,0];

nSamplesPerSec:=convert(Integer2Bytes(SampleFreq, 4),list);

BPS:=SampleFreq*nCh*nBytesPerSample;

nAvgBytesPerSec:=convert(Integer2Bytes(BPS,4),list);


NBA:=nCh*nBytesPerSample;

nBlockAlign:=convert(Integer2Bytes(NBA,2),list);

nDataBytes:=convert(Integer2Bytes(nBytesPerSample*SigSize*nCh,4),list);


nBitsPerSample:=convert(Integer2Bytes(Resolution,2),list);


nBytesTotal:=36+nBytesPerSample*SigSize*nCh;

FileSize:=convert(Integer2Bytes(nBytesTotal,4),list);

EndHeader:=[1,0];


#Create the Header for the WAV file...

Header:=[op(RIFF),

        op(FileSize),

        op(WAVE),

        op(ckID),

        op(nChunkSize),

        op(wFormatTag),

        op(nChannels),

        op(nSamplesPerSec),

        op(nAvgBytesPerSec),

        op(nBlockAlign),

        op(nBitsPerSample),

        op(DATA),

        op(nDataBytes),

        op(EndHeader)];


#If more than one Channel, need to flatten out the array to one interwoven data stream...

if nCh>1 then

      DataArray:=Array(1..SigSize*nCh,datatype=float[8]);

      print(cat("Interweaving sound data..."));

      for i from 1 to SigSize do:

            for j from 1 to nCh do:

               DataArray[nCh*(i-1)+j]:=SoundData[i,j]:

            end do:

      end do:

else

      DataArray:=SoundData:


end if:


# Quantize the sound data to unsigned bytes if Resolution<=8.

# If Resolution > 8 and <=16 quantize to signed integers then convert to bytes.

print(cat("Quantizing to ",Resolution, " bits..."));


RangeMax:=max((op(convert(DataArray,list)))):

RangeMin:=min((op(convert(DataArray,list)))):

Range:=RangeMax-RangeMin:

QuantRange:=2^Resolution-1:

Offset:=(2^Resolution)/2:

DeltaRange:=Range/QuantRange:

SigSize:=nCh*SigSize:

ByteArray:=Array(1..SigSize*nBytesPerSample, datatype=integer[2]):


for i from 1 to SigSize do:


BAtemp:=Integer2Bytes(round((DataArray[i]-RangeMin)/DeltaRange)-Offset, nBytesPerSample):


   for j from 1 to nBytesPerSample do:

       ByteArray[nBytesPerSample*(i-1)+j]:=BAtemp[j]:

       

   end do:


end do:


# Create the final output byte stream...

print(cat("Saving to ", FileName));

WriteArray:=[op(Header),op(convert(ByteArray,list))]:


# ...and save it.

fd := fopen(FileName,WRITE,BINARY):       

writebytes(fd,WriteArray);                       

fclose(fd);

print("Done");

end:


# ReadWAV() will import a sound file, determine the sound

# properties and return an array of floating-point data.

# If the data is multi-channel (ie > 1), the data is returned  

# as a multi-columned array.

# This function performs pretty much the reverse of WriteWAV()


ReadWAV:=proc(FileName::string, SamplingFrequency)

local WAVarray, DataArray, SignalArray, FileSize,

nCh, nSamplesPerSec, nBitsPerSample, nDataBytes,

nAvgBytesPerSec, nBlockAlign, nBytesPerSample,

nSamples, DataStart, nPoint, qRange, i,j,k, tmp:


# Read data bytes from entered WAV file

userinfo(4,WAV,`Reading in WAV file`):

try

 WAVarray:=readbytes(FileName,infinity):

catch "file or directory does not exist":

 error;

finally

 fclose(FileName);

end;

userinfo(4,WAV,`Finished reading WAV file`):


# Test file format


if nops(WAVarray) < 1 then error "No Data Found"; end if:


if convert(WAVarray[1..4],'bytes') <> "RIFF" then error "Not a WAV file"; end if:

if convert(WAVarray[9..12],'bytes') <> "WAVE" then error "Not a WAV file"; end if:

if convert(WAVarray[13..16],'bytes')<> "fmt " then error "Format not recognized"; end if:


# Extract header info


userinfo(4,WAV,`Extracting header information`);

FileSize:=Bytes2Integer(WAVarray[5..8]);

nCh:=Bytes2Integer(WAVarray[23..24]);

nSamplesPerSec:=Bytes2Integer(WAVarray[25..28]);

nBitsPerSample:=Bytes2Integer(WAVarray[35..36]);

nDataBytes:=Bytes2Integer(WAVarray[41..44]);

nAvgBytesPerSec:=Bytes2Integer(WAVarray[29..32]);

nBlockAlign:=Bytes2Integer(WAVarray[33..34]);

nBytesPerSample:=nBlockAlign/nCh;

nSamples:=nDataBytes/nBytesPerSample;


userinfo(3,WAV,`Number of Channels found:`,nCh):

userinfo(3,WAV,`Number of samples per channel:`,nSamples/nCh):

userinfo(3,WAV,`Bit resolution:`,nBitsPerSample):

userinfo(3,WAV,`Hz sampling frequency:`,nSamplesPerSec):

userinfo(3,WAV,`Duration in seconds:`,evalf(nSamples/nCh/nSamplesPerSec)):


userinfo(4,WAV,`Finished extracting header information`):


#Starting position for data bytes...

DataStart:=FileSize-nDataBytes+9;


SignalArray:=Array(1..nSamples/nCh, 1..nCh, datatype=float);


qRange:=evalhf((2^nBitsPerSample)-1);


userinfo(4,WAV,`Converting data and normalizing between -1.0 and 1.0`);


nPoint := DataStart:

for i from 1 to nSamples do:

 k := iquo(i,nCh,'j'):

 j := j+1;

 tmp := (WAVarray[nPoint]*2.+WAVarray[nPoint+1]*512.)/qRange-1:

 SignalArray[k,j]:=tmp-CopySign(1,tmp):

 nPoint := nPoint+2:

end do:

userinfo(4,WAV,`Done`);


# optional second parameter gets assigned the sampling frequency

if nargs=2 then

 if not type(SamplingFrequency,name) then

   error "optional second argument must be a name";

 end if;

 SamplingFrequency:=nSamplesPerSec

end if;


SignalArray;

end:


end module: # Final end to define the end of the module

To create or update the package, you will need to ensure the correct libname path is set...

> libname:="c:/mylib/wav",libname;

libname :=

> libname := "c:/mylib/wav", "C:\\Program Files\\Maple 6.01/lib";

...then create a Maple archive for the package. You only need to do this to create the archive..

> march('create',libname[1],10);

...and finally save or update the package code. "WAV" refers to the module name, defined above.

> savelib(WAV);

>