Lisp Sounds
Volume Number: 7
Issue Number: 3
Column Tag: Lisp Listener
The Sound Manager With Lisp 
By Michael S. Engber, Evanston, IL
Using the Sound Manager from LISP
This article develops code to play ‘snd ‘ resources asynchronously from
Macintosh Allegro Common LISP (MACL) version 1.3. The culmination of these efforts is
a macro, with-sound (Ala the standard Common LISP macro, with-open- files), which
plays a sound while its body executes. In addition, snd-p and snd-halt, allow you to
determine if the sound is still playing and to halt it.
No attempt is made here to teach LISP. The target audience is MACL users who
need parts of the ToolBox which MACL doesn’t provide a high level LISP interface to. To
this end, I will start out by exploring MACL’s trap and record definition mechanisms
and then use them to squeeze some basic functionality from the Sound Manager.
Experienced trap users can skip to the last half of the article.
Stack Trap Calls
MACL provides a pre-defined object library that lets you create and use
windows, dialog boxes, menus, and most of QuickDraw. They’ve done a pretty good job
and it’s pretty well documented in the manual. Someone who knows LISP, but not Mac
programming, can sit down, learn their object system, and create an amazingly
sophisticated user interface pretty easily. There is no need to deal with events, MACL
takes care of that for you. Menus, dialog items, etc., are defined as objects to which you
attach action functions. When your menu item gets selected or your dialog item is
clicked, its action function gets called.
Unfortunately, if your application get complicated enough, you will probably
need parts of the ToolBox MACL’s object library doesn’t include. Upon reading the
manual, you’re directed to the rather intimidating trap calls chapter for general info
and to Inside Macintosh for the details. Most users take one look and conclude that their
program didn’t really need that feature after all. But eventually, rationalization won’t
cut it anymore and it’s time to start using traps.
To start with, you need to load in traps.Lisp. This is best accomplished by putting
(require ‘traps) in your code. What traps.Lisp does, is associate ToolBox call names and
their corresponding trap words. It creates macros that allow you to call a function like
OpenResFile by using a mnemonic macro name like _OpenResFile instead of its trap
word, #xA997. traps.Lisp contains most of the ToolBox calls you’d normally use. I can
only recall running into omissions a few times. If _OpenResFile wasn’t listed in
traps.Lisp, it can still be accessed if you know its trap word. You could either add this
info to traps.Lisp, or use the general trap calling mechanism which uses trap words
rather than their mnemonic names. I’ve glossed over a few points, like the fact that
there are stack based traps and register based traps and that there are functions like
GetVol for which there are no traps. At this point its probably best to just dig right in.
Here’s an code showing a function and a procedure call, GetResource and
DetachResource.
;1
(setf my- handle (_GetResource :ostype "ICON" :word 50
:ptr))(_DetachResource :ptr my- handle)
For each argument the ToolBox call takes, you pass a pair of
arguments to the trap macro. The type is a keyword, :word, :long, :ptr, :ostype. The
value is the value you actually want passed. In addition, for functions, the last argument
of the trap call is a keyword giving the type of the return value.
You might have noticed that the aforementioned four choices for argument types
don’t seem sufficient to cover the wealth of types used in Inside Macintosh. With a bit of
creative explanation, they actually are. The first thing you have to learn is what
ToolBox calls really want passed as arguments. This can be simply expressed in Pascal:
{2}
if (the argument is a var parameter)
then pass a pointer to the argument
else if (the argument size > 4 bytes)
then pass a pointer to the argument
else pass the value of the argument
C and assembly language programmers already live by these rules. Pascal
programmers may be scratching their heads right now since the Pascal compiler take
care of all this. Trap calls are more akin to assembly language than LISP, so start
thinking about argument passing at a lower level. Basically, either you pass a pointer
to the argument or you pass its value, in which case you choose one of the 3 value types
depends mainly on the size of the argument. Some basic guidelines are summarized in a
table.
Unfortunately, the arguments to trap macros are not specified in traps.Lisp. It’s
your responsibility to look them up in, Inside Macintosh. No argument checking (type,
order, or number) is done and if you mess up MACL usually crashes. No diagnostic
error message, just that all too familiar, “Allegro Common LISP has unexpectedly
quit.”
Table 1. Stack Trap Argument Types
Notes on Table 1:
• One thing to note is that when points are passed by value they are passed as
longints. This is because point records are only four bytes in size. This is an easy
thing to forget (ask any C programmer). MACL provides some convenient
functions; make-point, point-h, and point-v, to help convert points. from and to
their longint form. They also provide the macro read character #@ to help you
write legible point literals. This means #@(10 50) reads in as a longint
corresponding to the point with the coordinates, 10 horizontal and 50 vertical.
• When boolean values are returned you should check their value by interrogating
their eighth bit instead of just checking for a non-zero value. Even though
booleans (and chars too) require only one byte, you still pass and receive a whole
two byte word. If some of the unused high bits are set, the value returned will be
non-zero regardless of whether true or false was returned. It’s easy enough to
test bit 8 with the Common LISP logbitp function. When passing boolean values to
functions, use -1 for true and 0 for false.
• When longints are passed and returned, only 31 bits are used (sign extension is
performed so the sign of the value won’t change). This is because MACL uses the
high bit to distinguish pointers from fixnums. At first this sounds like a terrible
imposition, but it only is a problem when the value represents a very large
unsigned value. So most of the time this won’t affect you, but once in a while it
will cause subtle bugs. I was recently bitten while using GetTime and
IUDateString. The seconds parameters to these routines are treated as unsigned
values and the current date (in seconds) is large enough to have the high bit set.
The work around is to treat a long as two consecutive words. Ugly, but at least you
can get the job done.
• There is a rather subtle problem with :ostype’s. They need to actually have a
string or keyword literal, not something that evaluates to a string or keyword.
For example:
;3
(defun get-rsrc (type id) (_GetResource :ostype type :word id))
This doesn’t work because of type. There is no problem with id evaluating to an