aifc.py: added missing tell() method in AIFC write class;
	 use audioop module as backup for cl module when reading or
	 writing u-law compressed files.
sunau.py: interface with the same methods as aifc for Sun and NeXT
	  audio files
diff --git a/Lib/sunau.py b/Lib/sunau.py
new file mode 100644
index 0000000..1acebd0
--- /dev/null
+++ b/Lib/sunau.py
@@ -0,0 +1,471 @@
+# Stuff to parse Sun and NeXT audio files.
+#
+# An audio consists of a header followed by the data.  The structure
+# of the header is as follows.
+#
+#	+---------------+
+#	| magic word    |
+#	+---------------+
+#	| header size   |
+#	+---------------+
+#	| data size     |
+#	+---------------+
+#	| encoding      |
+#	+---------------+
+#	| sample rate   |
+#	+---------------+
+#	| # of channels |
+#	+---------------+
+#	| info          |
+#	|               |
+#	+---------------+
+#
+# The magic word consists of the 4 characters '.snd'.  Apart from the
+# info field, all header fields are 4 bytes in size.  They are all
+# 32-bit unsigned integers encoded in big-endian byte order.
+#
+# The header size really gives the start of the data.
+# The data size is the physical size of the data.  From the other
+# parameter the number of frames can be calculated.
+# The encoding gives the way in which audio samples are encoded.
+# Possible values are listed below.
+# The info field currently consists of an ASCII string giving a
+# human-readable description of the audio file.  The info field is
+# padded with NUL bytes to the header size.
+#
+# Usage.
+#
+# Reading audio files:
+#	f = au.open(file, 'r')
+# or
+#	f = au.openfp(filep, 'r')
+# where file is the name of a file and filep is an open file pointer.
+# The open file pointer must have methods read(), seek(), and close().
+# When the setpos() and rewind() methods are not used, the seek()
+# method is not  necessary.
+#
+# This returns an instance of a class with the following public methods:
+#	getnchannels()	-- returns number of audio channels (1 for
+#			   mono, 2 for stereo)
+#	getsampwidth()	-- returns sample width in bytes
+#	getframerate()	-- returns sampling frequency
+#	getnframes()	-- returns number of audio frames
+#	getcomptype()	-- returns compression type ('NONE' for AIFF files)
+#	getcompname()	-- returns human-readable version of
+#			   compression type ('not compressed' for AIFF files)
+#	getparams()	-- returns a tuple consisting of all of the
+#			   above in the above order
+#	getmarkers()	-- returns None (for compatibility with the
+#			   aifc module)
+#	getmark(id)	-- raises an error since the mark does not
+#			   exist (for compatibility with the aifc module)
+#	readframes(n)	-- returns at most n frames of audio
+#	rewind()	-- rewind to the beginning of the audio stream
+#	setpos(pos)	-- seek to the specified position
+#	tell()		-- return the current position
+#	close()		-- close the instance (make it unusable)
+# The position returned by tell() and the position given to setpos()
+# are compatible and have nothing to do with the actual postion in the
+# file.
+# The close() method is called automatically when the class instance
+# is destroyed.
+#
+# Writing audio files:
+#	f = au.open(file, 'w')
+# or
+#	f = au.openfp(filep, 'w')
+# where file is the name of a file and filep is an open file pointer.
+# The open file pointer must have methods write(), tell(), seek(), and
+# close().
+#
+# This returns an instance of a class with the following public methods:
+#	setnchannels(n)	-- set the number of channels
+#	setsampwidth(n)	-- set the sample width
+#	setframerate(n)	-- set the frame rate
+#	setnframes(n)	-- set the number of frames
+#	setcomptype(type, name)
+#			-- set the compression type and the
+#			   human-readable compression type
+#	setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
+#			-- set all parameters at once
+#	tell()		-- return current position in output file
+#	writeframesraw(data)
+#			-- write audio frames without pathing up the
+#			   file header
+#	writeframes(data)
+#			-- write audio frames and patch up the file header
+#	close()		-- patch up the file header and close the
+#			   output file
+# You should set the parameters before the first writeframesraw or
+# writeframes.  The total number of frames does not need to be set,
+# but when it is set to the correct value, the header does not have to
+# be patched up.
+# It is best to first set all parameters, perhaps possibly the
+# compression type, and then write audio frames using writeframesraw.
+# When all frames have been written, either call writeframes('') or
+# close() to patch up the sizes in the header.
+# The close() method is called automatically when the class instance
+# is destroyed.
+
+# from <multimedia/audio_filehdr.h>
+AUDIO_FILE_MAGIC = 0x2e736e64
+AUDIO_FILE_ENCODING_MULAW_8 = 1
+AUDIO_FILE_ENCODING_LINEAR_8 = 2
+AUDIO_FILE_ENCODING_LINEAR_16 = 3
+AUDIO_FILE_ENCODING_LINEAR_24 = 4
+AUDIO_FILE_ENCODING_LINEAR_32 = 5
+AUDIO_FILE_ENCODING_FLOAT = 6
+AUDIO_FILE_ENCODING_DOUBLE = 7
+AUDIO_FILE_ENCODING_ADPCM_G721 = 23
+AUDIO_FILE_ENCODING_ADPCM_G722 = 24
+AUDIO_FILE_ENCODING_ADPCM_G723_3 = 25
+AUDIO_FILE_ENCODING_ADPCM_G723_5 = 26
+AUDIO_FILE_ENCODING_ALAW_8 = 27
+
+# from <multimedia/audio_hdr.h>
+AUDIO_UNKNOWN_SIZE = 0xFFFFFFFFL	# ((unsigned)(~0))
+
+_simple_encodings = [AUDIO_FILE_ENCODING_MULAW_8,
+		     AUDIO_FILE_ENCODING_LINEAR_8,
+		     AUDIO_FILE_ENCODING_LINEAR_16,
+		     AUDIO_FILE_ENCODING_LINEAR_24,
+		     AUDIO_FILE_ENCODING_LINEAR_32,
+		     AUDIO_FILE_ENCODING_ALAW_8]
+
+def _read_u32(file):
+	x = 0L
+	for i in range(4):
+		byte = file.read(1)
+		if byte == '':
+			raise EOFError
+		x = x*256 + ord(byte)
+	return x
+
+def _write_u32(file, x):
+	data = []
+	for i in range(4):
+		d, m = divmod(x, 256)
+		data.insert(0, m)
+		x = d
+	for i in range(4):
+		file.write(chr(int(data[i])))
+
+class Au_read:
+	def initfp(self, file):
+		self._file = file
+		self._soundpos = 0
+		magic = int(_read_u32(file))
+		if magic != AUDIO_FILE_MAGIC:
+			raise Error, 'bad magic number'
+		self._hdr_size = int(_read_u32(file))
+		if self._hdr_size < 24:
+			raise Error, 'header size too small'
+		if self._hdr_size > 100:
+			raise Error, 'header size rediculously large'
+		self._data_size = _read_u32(file)
+		if self._data_size != AUDIO_UNKNOWN_SIZE:
+			self._data_size = int(self._data_size)
+		self._encoding = int(_read_u32(file))
+		if self._encoding not in _simple_encodings:
+			raise Error, 'encoding not (yet) supported'
+		if self._encoding in (AUDIO_FILE_ENCODING_MULAW_8,
+			  AUDIO_FILE_ENCODING_LINEAR_8,
+			  AUDIO_FILE_ENCODING_ALAW_8):
+			self._sampwidth = 2
+			self._framesize = 1
+		elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_16:
+			self._framesize = self._sampwidth = 2
+		elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_24:
+			self._framesize = self._sampwidth = 3
+		elif self._encoding == AUDIO_FILE_ENCODING_LINEAR_32:
+			self._framesize = self._sampwidth = 4
+		else:
+			raise Error, 'unknown encoding'
+		self._framerate = int(_read_u32(file))
+		self._nchannels = int(_read_u32(file))
+		self._framesize = self._framesize * self._nchannels
+		if self._hdr_size > 24:
+			self._info = file.read(self._hdr_size - 24)
+			for i in range(len(self._info)):
+				if self._info[i] == '\0':
+					self._info = self._info[:i]
+					break
+		else:
+			self._info = ''
+		return self
+
+	def init(self, filename):
+		import builtin
+		return self.initfp(builtin.open(filename, 'r'))
+
+	def __del__(self):
+		if self._file:
+			self.close()
+
+	def getfp(self):
+		return self._file
+
+	def getnchannels(self):
+		return self._nchannels
+
+	def getsampwidth(self):
+		return self._sampwidth
+
+	def getframerate(self):
+		return self._framerate
+
+	def getnframes(self):
+		if self._data_size == AUDIO_UNKNOWN_SIZE:
+			return AUDIO_UNKNOWN_SIZE
+		if self._encoding in _simple_encodings:
+			return self._data_size / self._framesize
+		return 0		# XXX--must do some arithmetic here
+
+	def getcomptype(self):
+		if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+			return 'ULAW'
+		elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
+			return 'ALAW'
+		else:
+			return 'NONE'
+
+	def getcompname(self):
+		if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+			return 'CCITT G.711 u-law'
+		elif self._encoding == AUDIO_FILE_ENCODING_ALAW_8:
+			return 'CCITT G.711 A-law'
+		else:
+			return 'not compressed'
+
+	def getparams(self):
+		return self.getnchannels(), self.getsampwidth(), \
+			  self.getframerate(), self.getnframes(), \
+			  self.getcomptype(), self.getcompname()
+
+	def getmarkers(self):
+		return None
+
+	def getmark(self, id):
+		raise Error, 'no marks'
+
+	def readframes(self, nframes):
+		if self._encoding in _simple_encodings:
+			if nframes == AUDIO_UNKNOWN_SIZE:
+				data = self._file.read()
+			else:
+				data = self._file.read(nframes * self._sampwidth * self._nchannels)
+			if self._encoding == AUDIO_FILE_ENCODING_MULAW_8:
+				import audioop
+				data = audioop.ulaw2lin(data, self._sampwidth)
+			return data
+		return None		# XXX--not implemented yet
+
+	def rewind(self):
+		self._soundpos = 0
+		self._file.seek(self._hdr_size)
+
+	def tell(self):
+		return self._soundpos
+
+	def setpos(self, pos):
+		if pos < 0 or pos > self.getnframes():
+			raise Error, 'position not in range'
+		self._file.seek(pos * self._framesize + self._hdr_size)
+		self._soundpos = pos
+
+	def close(self):
+		self._file.close()
+		self._file = None
+
+class Au_write:
+	def init(self, filename):
+		import builtin
+		return self.initfp(builtin.open(filename, 'w'))
+
+	def initfp(self, file):
+		self._file = file
+		self._framerate = 0
+		self._nchannels = 0
+		self._sampwidth = 0
+		self._framesize = 0
+		self._nframes = AUDIO_UNKNOWN_SIZE
+		self._nframeswritten = 0
+		self._datawritten = 0
+		self._datalength = 0
+		self._info = ''
+		self._comptype = 'ULAW'	# default is U-law
+		return self
+
+	def __del__(self):
+		if self._file:
+			self.close()
+
+	def setnchannels(self, nchannels):
+		if self._nframeswritten:
+			raise Error, 'cannot change parameters after starting to write'
+		if nchannels not in (1, 2, 4):
+			raise Error, 'only 1, 2, or 4 channels supported'
+		self._nchannels = nchannels
+
+	def getnchannels(self):
+		if not self._nchannels:
+			raise Error, 'number of channels not set'
+		return self._nchannels
+
+	def setsampwidth(self, sampwidth):
+		if self._nframeswritten:
+			raise Error, 'cannot change parameters after starting to write'
+		if sampwidth not in (1, 2, 4):
+			raise Error, 'bad sample width'
+		self._sampwidth = sampwidth
+
+	def getsampwidth(self):
+		if not self._framerate:
+			raise Error, 'sample width not specified'
+		return self._sampwidth
+
+	def setframerate(self, framerate):
+		if self._nframeswritten:
+			raise Error, 'cannot change parameters after starting to write'
+		self._framerate = framerate
+
+	def getframerate(self):
+		if not self._framerate:
+			raise Error, 'frame rate not set'
+		return self._framerate
+
+	def setnframes(self, nframes):
+		if self._nframeswritten:
+			raise Error, 'cannot change parameters after starting to write'
+		if nframes < 0:
+			raise Error, '# of frames cannot be negative'
+		self._nframes = nframes
+
+	def getnframes(self):
+		return self._nframeswritten
+
+	def setcomptype(self, type, name):
+		if type in ('NONE', 'ULAW'):
+			self._comptype = type
+		else:
+			raise Error, 'unknown compression type'
+
+	def getcomptype(self):
+		return self._comptype
+
+	def getcompname(self):
+		if self._comptype == 'ULAW':
+			return 'CCITT G.711 u-law'
+		elif self._comptype == 'ALAW':
+			return 'CCITT G.711 A-law'
+		else:
+			return 'not compressed'
+
+	def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
+		self.setnchannels(nchannels)
+		self.setsampwidth(sampwidth)
+		self.setframerate(framerate)
+		self.setnframes(nframes)
+		self.setcomptype(comptype, compname)
+
+	def getparams(self):
+		return self.getnchannels(), self.getsampwidth(), \
+			  self.getframerate(), self.getnframes(), \
+			  self.getcomptype(), self.getcompname()
+
+	def tell(self):
+		return self._nframeswritten
+
+	def writeframesraw(self, data):
+		self._ensure_header_written()
+		nframes = len(data) / self._framesize
+		if self._comptype == 'ULAW':
+			import audioop
+			data = audioop.lin2ulaw(data, self._sampwidth)
+		self._file.write(data)
+		self._nframeswritten = self._nframeswritten + nframes
+		self._datawritten = self._datawritten + len(data)
+
+	def writeframes(self, data):
+		self.writeframesraw(data)
+		if self._nframeswritten != self._nframes or \
+			  self._datalength != self._datawritten:
+			self._patchheader()
+
+	def close(self):
+		self._ensure_header_written()
+		if self._nframeswritten != self._nframes or \
+			  self._datalength != self._datawritten:
+			self._patchheader()
+		self._file.close()
+		self._file = None
+
+	#
+	# private methods
+	#
+	def _ensure_header_written(self):
+		if not self._nframeswritten:
+			if not self._nchannels:
+				raise Error, '# of channels not specified'
+			if not self._sampwidth:
+				raise Error, 'sample width not specified'
+			if not self._framerate:
+				raise Error, 'frame rate not specified'
+			self._write_header()
+
+	def _write_header(self):
+		if self._comptype == 'NONE':
+			if self._sampwidth == 1:
+				encoding = AUDIO_FILE_ENCODING_LINEAR_8
+				self._framesize = 1
+			elif self._sampwidth == 2:
+				encoding = AUDIO_FILE_ENCODING_LINEAR_16
+				self._framesize = 2
+			elif self._sampwidth == 4:
+				encoding = AUDIO_FILE_ENCODING_LINEAR_32
+				self._framesize = 4
+			else:
+				raise Error, 'internal error'
+		elif self._comptype == 'ULAW':
+			encoding = AUDIO_FILE_ENCODING_MULAW_8
+			self._framesize = 1
+		else:
+			raise Error, 'internal error'
+		self._framesize = self._framesize * self._nchannels
+		_write_u32(self._file, AUDIO_FILE_MAGIC)
+		header_size = 25 + len(self._info)
+		header_size = (header_size + 7) & ~7
+		_write_u32(self._file, header_size)
+		if self._nframes == AUDIO_UNKNOWN_SIZE:
+			length = AUDIO_UNKNOWN_SIZE
+		else:
+			length = self._nframes * self._framesize
+		_write_u32(self._file, length)
+		self._datalength = length
+		_write_u32(self._file, encoding)
+		_write_u32(self._file, self._framerate)
+		_write_u32(self._file, self._nchannels)
+		self._file.write(self._info)
+		self._file.write('\0'*(header_size - len(self._info) - 24))
+
+	def _patchheader(self):
+		self._file.seek(8)
+		_write_u32(self._file, self._datawritten)
+		self._datalength = self._datawritten
+		self._file.seek(0, 2)
+
+def open(filename, mode):
+	if mode == 'r':
+		return Au_read().init(filename)
+	elif mode == 'w':
+		return Au_write().init(filename)
+	else:
+		raise Error, "mode must be 'r' or 'w'"
+
+def openfp(filep, mode):
+	if mode == 'r':
+		return Au_read().initfp(filep)
+	elif mode == 'w':
+		return Au_write().initfp(filep)
+	else:
+		raise Error, "mode must be 'r' or 'w'"