Support for sound input added. The interface is even worse than the
sound-output interface, so this needs a Python wrapper shortly.
diff --git a/Mac/Modules/snd/Sndmodule.c b/Mac/Modules/snd/Sndmodule.c
index 06031ab..accb563 100644
--- a/Mac/Modules/snd/Sndmodule.c
+++ b/Mac/Modules/snd/Sndmodule.c
@@ -76,6 +76,8 @@
 }
 
 static pascal void SndCh_UserRoutine(SndChannelPtr chan, SndCommand *cmd); /* Forward */
+static pascal void SPB_completion(SPBPtr my_spb); /* Forward */
+static pascal void SPB_interrupt(SPBPtr my_spb); /* Forward */
 
 static PyObject *Snd_Error;
 
@@ -315,6 +317,133 @@
 /* ------------------- End object type SndChannel ------------------- */
 
 
+/* ------------------------ Object type SPB ------------------------- */
+
+staticforward PyTypeObject SPB_Type;
+
+#define SPBObj_Check(x) ((x)->ob_type == &SPB_Type)
+
+typedef struct SPBObject {
+	PyObject_HEAD
+	/* Members used to implement callbacks: */
+	PyObject *ob_completion;
+	PyObject *ob_interrupt;
+	PyObject *ob_thiscallback;
+	long ob_A5;
+	SPB ob_spb;
+} SPBObject;
+
+static PyObject *SPBObj_New()
+{
+	SPBObject *it;
+	it = PyObject_NEW(SPBObject, &SPB_Type);
+	if (it == NULL) return NULL;
+	it->ob_completion = NULL;
+	it->ob_interrupt = NULL;
+	it->ob_thiscallback = NULL;
+	it->ob_A5 = SetCurrentA5();
+	memset((char *)&it->ob_spb, 0, sizeof(it->ob_spb));
+	it->ob_spb.userLong = (long)it;
+	return (PyObject *)it;
+}
+static SPBObj_Convert(v, p_itself)
+	PyObject *v;
+	SPBPtr *p_itself;
+{
+	if (!SPBObj_Check(v))
+	{
+		PyErr_SetString(PyExc_TypeError, "SPB required");
+		return 0;
+	}
+	*p_itself = &((SPBObject *)v)->ob_spb;
+	return 1;
+}
+
+static void SPBObj_dealloc(self)
+	SPBObject *self;
+{
+	/* Cleanup of self->ob_itself goes here */
+	self->ob_spb.userLong = 0;
+	self->ob_thiscallback = 0;
+	Py_XDECREF(self->ob_completion);
+	Py_XDECREF(self->ob_interrupt);
+	PyMem_DEL(self);
+}
+
+static PyMethodDef SPBObj_methods[] = {
+	{NULL, NULL, 0}
+};
+
+static PyMethodChain SPBObj_chain = { SPBObj_methods, NULL };
+
+static PyObject *SPBObj_getattr(self, name)
+	SPBObject *self;
+	char *name;
+{
+
+				if (strcmp(name, "inRefNum") == 0)
+					return Py_BuildValue("l", self->ob_spb.inRefNum);
+				else if (strcmp(name, "count") == 0)
+					return Py_BuildValue("l", self->ob_spb.count);
+				else if (strcmp(name, "milliseconds") == 0)
+					return Py_BuildValue("l", self->ob_spb.milliseconds);
+				else if (strcmp(name, "error") == 0)
+					return Py_BuildValue("h", self->ob_spb.error);
+	return Py_FindMethodInChain(&SPBObj_chain, (PyObject *)self, name);
+}
+
+static int SPBObj_setattr(self, name, value)
+	SPBObject *self;
+	char *name;
+	PyObject *value;
+{
+
+				if (strcmp(name, "inRefNum") == 0)
+					return PyArg_Parse(value, "l", &self->ob_spb.inRefNum);
+				else if (strcmp(name, "count") == 0)
+					return PyArg_Parse(value, "l", &self->ob_spb.count);
+				else if (strcmp(name, "milliseconds") == 0)
+					return PyArg_Parse(value, "l", &self->ob_spb.milliseconds);
+				else if (strcmp(name, "buffer") == 0)
+					return PyArg_Parse(value, "w#", &self->ob_spb.bufferPtr, &self->ob_spb.bufferLength);
+				else if (strcmp(name, "completionRoutine") == 0) {
+					self->ob_spb.completionRoutine = NewSICompletionProc(SPB_completion);
+					self->ob_completion = value;
+					Py_INCREF(value);
+					return 0;
+				} else if (strcmp(name, "interruptRoutine") == 0) {
+					self->ob_spb.completionRoutine = NewSIInterruptProc(SPB_interrupt);
+					self->ob_interrupt = value;
+					Py_INCREF(value);
+					return 0;
+				}
+				return -1;
+}
+
+staticforward PyTypeObject SPB_Type = {
+	PyObject_HEAD_INIT(&PyType_Type)
+	0, /*ob_size*/
+	"SPB", /*tp_name*/
+	sizeof(SPBObject), /*tp_basicsize*/
+	0, /*tp_itemsize*/
+	/* methods */
+	(destructor) SPBObj_dealloc, /*tp_dealloc*/
+	0, /*tp_print*/
+	(getattrfunc) SPBObj_getattr, /*tp_getattr*/
+	(setattrfunc) SPBObj_setattr, /*tp_setattr*/
+};
+
+/* ---------------------- End object type SPB ----------------------- */
+
+
+static PyObject *Snd_SPB(_self, _args)
+	PyObject *_self;
+	PyObject *_args;
+{
+	PyObject *_res = NULL;
+	return SPBObj_New();
+}
+
 static PyObject *Snd_SysBeep(_self, _args)
 	PyObject *_self;
 	PyObject *_args;
@@ -839,6 +968,49 @@
 	return _res;
 }
 
+static PyObject *Snd_SPBRecord(_self, _args)
+	PyObject *_self;
+	PyObject *_args;
+{
+	PyObject *_res = NULL;
+	OSErr _err;
+	SPBPtr inParamPtr;
+	Boolean asynchFlag;
+	if (!PyArg_ParseTuple(_args, "O&b",
+	                      SPBObj_Convert, &inParamPtr,
+	                      &asynchFlag))
+		return NULL;
+	_err = SPBRecord(inParamPtr,
+	                 asynchFlag);
+	if (_err != noErr) return PyMac_Error(_err);
+	Py_INCREF(Py_None);
+	_res = Py_None;
+	return _res;
+}
+
+static PyObject *Snd_SPBRecordToFile(_self, _args)
+	PyObject *_self;
+	PyObject *_args;
+{
+	PyObject *_res = NULL;
+	OSErr _err;
+	short fRefNum;
+	SPBPtr inParamPtr;
+	Boolean asynchFlag;
+	if (!PyArg_ParseTuple(_args, "hO&b",
+	                      &fRefNum,
+	                      SPBObj_Convert, &inParamPtr,
+	                      &asynchFlag))
+		return NULL;
+	_err = SPBRecordToFile(fRefNum,
+	                       inParamPtr,
+	                       asynchFlag);
+	if (_err != noErr) return PyMac_Error(_err);
+	Py_INCREF(Py_None);
+	_res = Py_None;
+	return _res;
+}
+
 static PyObject *Snd_SPBPauseRecording(_self, _args)
 	PyObject *_self;
 	PyObject *_args;
@@ -924,6 +1096,52 @@
 	return _res;
 }
 
+static PyObject *Snd_SPBGetDeviceInfo(_self, _args)
+	PyObject *_self;
+	PyObject *_args;
+{
+	PyObject *_res = NULL;
+	OSErr _err;
+	long inRefNum;
+	OSType infoType;
+	void * infoData;
+	if (!PyArg_ParseTuple(_args, "lO&w",
+	                      &inRefNum,
+	                      PyMac_GetOSType, &infoType,
+	                      &infoData))
+		return NULL;
+	_err = SPBGetDeviceInfo(inRefNum,
+	                        infoType,
+	                        infoData);
+	if (_err != noErr) return PyMac_Error(_err);
+	Py_INCREF(Py_None);
+	_res = Py_None;
+	return _res;
+}
+
+static PyObject *Snd_SPBSetDeviceInfo(_self, _args)
+	PyObject *_self;
+	PyObject *_args;
+{
+	PyObject *_res = NULL;
+	OSErr _err;
+	long inRefNum;
+	OSType infoType;
+	void * infoData;
+	if (!PyArg_ParseTuple(_args, "lO&w",
+	                      &inRefNum,
+	                      PyMac_GetOSType, &infoType,
+	                      &infoData))
+		return NULL;
+	_err = SPBSetDeviceInfo(inRefNum,
+	                        infoType,
+	                        infoData);
+	if (_err != noErr) return PyMac_Error(_err);
+	Py_INCREF(Py_None);
+	_res = Py_None;
+	return _res;
+}
+
 static PyObject *Snd_SPBMillisecondsToBytes(_self, _args)
 	PyObject *_self;
 	PyObject *_args;
@@ -963,6 +1181,8 @@
 }
 
 static PyMethodDef Snd_methods[] = {
+	{"SPB", (PyCFunction)Snd_SPB, 1,
+	 NULL},
 	{"SysBeep", (PyCFunction)Snd_SysBeep, 1,
 	 "(short duration) -> None"},
 	{"SndNewChannel", (PyCFunction)Snd_SndNewChannel, 1,
@@ -1009,6 +1229,10 @@
 	 "(Str255 deviceName, short permission) -> (long inRefNum)"},
 	{"SPBCloseDevice", (PyCFunction)Snd_SPBCloseDevice, 1,
 	 "(long inRefNum) -> None"},
+	{"SPBRecord", (PyCFunction)Snd_SPBRecord, 1,
+	 "(SPBPtr inParamPtr, Boolean asynchFlag) -> None"},
+	{"SPBRecordToFile", (PyCFunction)Snd_SPBRecordToFile, 1,
+	 "(short fRefNum, SPBPtr inParamPtr, Boolean asynchFlag) -> None"},
 	{"SPBPauseRecording", (PyCFunction)Snd_SPBPauseRecording, 1,
 	 "(long inRefNum) -> None"},
 	{"SPBResumeRecording", (PyCFunction)Snd_SPBResumeRecording, 1,
@@ -1017,6 +1241,10 @@
 	 "(long inRefNum) -> None"},
 	{"SPBGetRecordingStatus", (PyCFunction)Snd_SPBGetRecordingStatus, 1,
 	 "(long inRefNum) -> (short recordingStatus, short meterLevel, unsigned long totalSamplesToRecord, unsigned long numberOfSamplesRecorded, unsigned long totalMsecsToRecord, unsigned long numberOfMsecsRecorded)"},
+	{"SPBGetDeviceInfo", (PyCFunction)Snd_SPBGetDeviceInfo, 1,
+	 "(long inRefNum, OSType infoType, void * infoData) -> None"},
+	{"SPBSetDeviceInfo", (PyCFunction)Snd_SPBSetDeviceInfo, 1,
+	 "(long inRefNum, OSType infoType, void * infoData) -> None"},
 	{"SPBMillisecondsToBytes", (PyCFunction)Snd_SPBMillisecondsToBytes, 1,
 	 "(long inRefNum) -> (long milliseconds)"},
 	{"SPBBytesToMilliseconds", (PyCFunction)Snd_SPBBytesToMilliseconds, 1,
@@ -1057,6 +1285,52 @@
 	}
 }
 
+/* SPB callbacks - Schedule callbacks to Python */
+static int
+SPB_CallCallBack(arg)
+	void *arg;
+{
+	SPBObject *p = (SPBObject *)arg;
+	PyObject *args;
+	PyObject *res;
+	
+	if ( p->ob_thiscallback == 0 ) return 0;
+	args = Py_BuildValue("(O)", p);
+	res = PyEval_CallObject(p->ob_thiscallback, args);
+	p->ob_thiscallback = 0;
+	Py_DECREF(args);
+	if (res == NULL)
+		return -1;
+	Py_DECREF(res);
+	return 0;
+}
+
+static pascal void
+SPB_completion(SPBPtr my_spb)
+{
+	SPBObject *p = (SPBObject *)(my_spb->userLong);
+	
+	if (p && p->ob_completion) {
+		long A5 = SetA5(p->ob_A5);
+		p->ob_thiscallback = p->ob_completion;	/* Hope we cannot get two at the same time */
+		Py_AddPendingCall(SPB_CallCallBack, (void *)p);
+		SetA5(A5);
+	}
+}
+
+static pascal void
+SPB_interrupt(SPBPtr my_spb)
+{
+	SPBObject *p = (SPBObject *)(my_spb->userLong);
+	
+	if (p && p->ob_interrupt) {
+		long A5 = SetA5(p->ob_A5);
+		p->ob_thiscallback = p->ob_interrupt;	/* Hope we cannot get two at the same time */
+		Py_AddPendingCall(SPB_CallCallBack, (void *)p);
+		SetA5(A5);
+	}
+}
+
 
 void initSnd()
 {
@@ -1077,6 +1351,10 @@
 	Py_INCREF(&SndChannel_Type);
 	if (PyDict_SetItemString(d, "SndChannelType", (PyObject *)&SndChannel_Type) != 0)
 		Py_FatalError("can't initialize SndChannelType");
+	SPB_Type.ob_type = &PyType_Type;
+	Py_INCREF(&SPB_Type);
+	if (PyDict_SetItemString(d, "SPBType", (PyObject *)&SPB_Type) != 0)
+		Py_FatalError("can't initialize SPBType");
 }
 
 /* ========================= End module Snd ========================= */
diff --git a/Mac/Modules/snd/sndscan.py b/Mac/Modules/snd/sndscan.py
index af5ecbb..c7ca36b 100644
--- a/Mac/Modules/snd/sndscan.py
+++ b/Mac/Modules/snd/sndscan.py
@@ -43,16 +43,13 @@
 			'StartSound',
 			'StopSound',
 			'SoundDone',
-			# These are soundMgr 3.0 routines that I can't seem to find...
-			'GetSoundPreference',
-			'SetSoundPreference',
-			'GetCompressionInfo',
-			'GetCompressionName',
-			# Calls with void_ptr arguments (to be done).
+			# These do not work for cfm68k:
 			'SndGetInfo',
 			'SndSetInfo',
-			'SPBGetDeviceInfo',
-			'SPBSetDeviceInfo',
+			'GetCompressionInfo',
+			'GetCompressionName',
+			'GetSoundPreference',
+			'SetSoundPreference',
 			# And old calls that are no longer supported
 			'SetSoundVol',
 			'GetSoundVol',
@@ -72,7 +69,6 @@
 			"SoundComponentData_ptr",
 			"SoundConverter",
 			"ModalFilterUPP",
-			"SPBPtr",
 			]
 
 	def makerepairinstructions(self):
@@ -116,6 +112,9 @@
 			([("StateBlockPtr", "inState", "InMode"), ("StateBlockPtr", "outState", "InMode")],
 			 [("StateBlock", "state", "InOutMode")]),
 
+			# Catch-all for the last couple of void pointers
+			([("void", "*", "OutMode")],
+			 [("void_ptr", "*", "InMode")]),
 			]
 
 if __name__ == "__main__":
diff --git a/Mac/Modules/snd/sndsupport.py b/Mac/Modules/snd/sndsupport.py
index b3b32f1..cabfa27 100644
--- a/Mac/Modules/snd/sndsupport.py
+++ b/Mac/Modules/snd/sndsupport.py
@@ -48,6 +48,13 @@
 SndCommand = OpaqueType('SndCommand', 'SndCmd')
 SndCommand_ptr = OpaqueType('SndCommand', 'SndCmd')
 SndListHandle = OpaqueByValueType("SndListHandle", "ResObj")
+SPBPtr = OpaqueByValueType("SPBPtr", "SPBObj")
+
+#
+# NOTE: the following is pretty dangerous. For void pointers we pass buffer addresses
+# but we have no way to check that the buffer is big enough. This is the same problem
+# as in C, though (but Pythoneers may not be suspecting this...)
+void_ptr = Type("void *", "w")
 
 class SndCallBackType(InputOnlyType):
 	def __init__(self):
@@ -118,6 +125,8 @@
 }
 
 static pascal void SndCh_UserRoutine(SndChannelPtr chan, SndCommand *cmd); /* Forward */
+static pascal void SPB_completion(SPBPtr my_spb); /* Forward */
+static pascal void SPB_interrupt(SPBPtr my_spb); /* Forward */
 """
 
 
@@ -152,6 +161,52 @@
 		SetA5(A5);
 	}
 }
+
+/* SPB callbacks - Schedule callbacks to Python */
+static int
+SPB_CallCallBack(arg)
+	void *arg;
+{
+	SPBObject *p = (SPBObject *)arg;
+	PyObject *args;
+	PyObject *res;
+	
+	if ( p->ob_thiscallback == 0 ) return 0;
+	args = Py_BuildValue("(O)", p);
+	res = PyEval_CallObject(p->ob_thiscallback, args);
+	p->ob_thiscallback = 0;
+	Py_DECREF(args);
+	if (res == NULL)
+		return -1;
+	Py_DECREF(res);
+	return 0;
+}
+
+static pascal void
+SPB_completion(SPBPtr my_spb)
+{
+	SPBObject *p = (SPBObject *)(my_spb->userLong);
+	
+	if (p && p->ob_completion) {
+		long A5 = SetA5(p->ob_A5);
+		p->ob_thiscallback = p->ob_completion;	/* Hope we cannot get two at the same time */
+		Py_AddPendingCall(SPB_CallCallBack, (void *)p);
+		SetA5(A5);
+	}
+}
+
+static pascal void
+SPB_interrupt(SPBPtr my_spb)
+{
+	SPBObject *p = (SPBObject *)(my_spb->userLong);
+	
+	if (p && p->ob_interrupt) {
+		long A5 = SetA5(p->ob_A5);
+		p->ob_thiscallback = p->ob_interrupt;	/* Hope we cannot get two at the same time */
+		Py_AddPendingCall(SPB_CallCallBack, (void *)p);
+		SetA5(A5);
+	}
+}
 """
 
 
@@ -177,11 +232,118 @@
 	
 	def outputFreeIt(self, itselfname):
 		Output("SndDisposeChannel(%s, 1);", itselfname)
+		
+#
 
+class SpbObjectDefinition(ObjectDefinition):
+
+	def outputStructMembers(self):
+		Output("/* Members used to implement callbacks: */")
+		Output("PyObject *ob_completion;")
+		Output("PyObject *ob_interrupt;")
+		Output("PyObject *ob_thiscallback;");
+		Output("long ob_A5;")
+		Output("SPB ob_spb;")
+
+	def outputNew(self):
+		Output()
+		Output("%sPyObject *%s_New()", self.static, self.prefix)
+		OutLbrace()
+		Output("%s *it;", self.objecttype)
+		self.outputCheckNewArg()
+		Output("it = PyObject_NEW(%s, &%s);", self.objecttype, self.typename)
+		Output("if (it == NULL) return NULL;")
+		self.outputInitStructMembers()
+		Output("return (PyObject *)it;")
+		OutRbrace()
+
+	def outputInitStructMembers(self):
+		Output("it->ob_completion = NULL;")
+		Output("it->ob_interrupt = NULL;")
+		Output("it->ob_thiscallback = NULL;")
+		Output("it->ob_A5 = SetCurrentA5();")
+		Output("memset((char *)&it->ob_spb, 0, sizeof(it->ob_spb));")
+		Output("it->ob_spb.userLong = (long)it;")
+
+	def outputCleanupStructMembers(self):
+		ObjectDefinition.outputCleanupStructMembers(self)
+		Output("self->ob_spb.userLong = 0;")
+		Output("self->ob_thiscallback = 0;")
+		Output("Py_XDECREF(self->ob_completion);")
+		Output("Py_XDECREF(self->ob_interrupt);")
+	
+	def outputConvert(self):
+		Output("%s%s_Convert(v, p_itself)", self.static, self.prefix)
+		IndentLevel()
+		Output("PyObject *v;")
+		Output("%s *p_itself;", self.itselftype)
+		DedentLevel()
+		OutLbrace()
+		self.outputCheckConvertArg()
+		Output("if (!%s_Check(v))", self.prefix)
+		OutLbrace()
+		Output('PyErr_SetString(PyExc_TypeError, "%s required");', self.name)
+		Output("return 0;")
+		OutRbrace()
+		Output("*p_itself = &((%s *)v)->ob_spb;", self.objecttype)
+		Output("return 1;")
+		OutRbrace()
+
+	def outputSetattr(self):
+		Output()
+		Output("static int %s_setattr(self, name, value)", self.prefix)
+		IndentLevel()
+		Output("%s *self;", self.objecttype)
+		Output("char *name;")
+		Output("PyObject *value;")
+		DedentLevel()
+		OutLbrace()
+		self.outputSetattrBody()
+		OutRbrace()
+
+	def outputSetattrBody(self):
+		Output("""
+			if (strcmp(name, "inRefNum") == 0)
+				return PyArg_Parse(value, "l", &self->ob_spb.inRefNum);
+			else if (strcmp(name, "count") == 0)
+				return PyArg_Parse(value, "l", &self->ob_spb.count);
+			else if (strcmp(name, "milliseconds") == 0)
+				return PyArg_Parse(value, "l", &self->ob_spb.milliseconds);
+			else if (strcmp(name, "buffer") == 0)
+				return PyArg_Parse(value, "w#", &self->ob_spb.bufferPtr, &self->ob_spb.bufferLength);
+			else if (strcmp(name, "completionRoutine") == 0) {
+				self->ob_spb.completionRoutine = NewSICompletionProc(SPB_completion);
+				self->ob_completion = value;
+				Py_INCREF(value);
+				return 0;
+			} else if (strcmp(name, "interruptRoutine") == 0) {
+				self->ob_spb.completionRoutine = NewSIInterruptProc(SPB_interrupt);
+				self->ob_interrupt = value;
+				Py_INCREF(value);
+				return 0;
+			}
+			return -1;""")
+			
+	def outputGetattrHook(self):
+		Output("""
+			if (strcmp(name, "inRefNum") == 0)
+				return Py_BuildValue("l", self->ob_spb.inRefNum);
+			else if (strcmp(name, "count") == 0)
+				return Py_BuildValue("l", self->ob_spb.count);
+			else if (strcmp(name, "milliseconds") == 0)
+				return Py_BuildValue("l", self->ob_spb.milliseconds);
+			else if (strcmp(name, "error") == 0)
+				return Py_BuildValue("h", self->ob_spb.error);""")
+		
+					
 
 sndobject = SndObjectDefinition('SndChannel', 'SndCh', 'SndChannelPtr')
+spbobject = SpbObjectDefinition('SPB', 'SPBObj', 'SPBPtr')
+spbgenerator = ManualGenerator("SPB", "return SPBObj_New();")
 module = MacModule('Snd', 'Snd', includestuff, finalstuff, initstuff)
 module.addobject(sndobject)
+module.addobject(spbobject)
+module.add(spbgenerator)
 
 
 # create lists of functions and object methods