TweetFollow Us on Twitter

Lisp Sounds
Volume Number:7
Issue Number:3
Column Tag:Lisp Listener

Related Info: Sound Manager Resource Manager

The Sound Manager With Lisp

By Michael S. Engber, Evanston, IL

Using the Sound Manager from LISP

Introduction

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 <type value> 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 integer, it’s just :ostype’s that have this problem. This is something that is supposed to fixed in the next version of MACL. For now, I use macro’s to avoid this gotcha. Very few ToolBox calls use :ostype’s so just file this tidbit away somewhere.

Register Trap Calls

If you look carefully at traps.Lisp you’ll see it’s divided into two parts, stack traps and register traps. This is because some ToolBox calls expect their arguments in registers instead of on the stack. This is something that’s normally hidden from both C and Pascal programmers. You’ll need to look up in Inside Macintosh which argument goes in which register and which register contains the return value. Instead of using type keywords, use register name keywords Here’s some examples:

;4

(setf my-ptr (_NewPtr :d0 20 :a0)  ;get a 20 byte block(_DisposPtr :a0 
my-ptr :d0)        ;carelessly discard the return value

Fortunately, traps.Lisp defines stack and register traps differently enough so if you mess up and call a register trap using stack trap conventions MACL catches it. I wouldn’t exactly say it diagnoses the problem, it just gives you the cryptic error message like the one shown below, but at least it doesn’t crash on you.

> Error: :long is not a valid argument to nil> While executing: #<An 
Anonymous Compiled-function>

Passing Pointers to Trap Calls

The above discussion glossed over how you obtain pointers to LISP object. There’s know address operator like & in C or @ in MacPascal. The answer is, you don’t. “Remember this, never forget this:” never use a LISP regular object as a :ptr value. LISP objects are not of this world, they have deep underlying structure and the ToolBox can’t grok them. So what do you do? You allocate some space, stuff the desired value in, and send off this “real” pointer.

Don’t’ despair! The MACL designers, in their infinite wisdom, took pity on us mortals and provided three rather convenient mechanisms for doing this. These three special forms automatically allocate and deallocate the space for you, saving you from much pain and fragmented memory. They work much like our old friend, let Variables are defined and initialized for use within their body.

with-pstrs/with-returned-pstrs - These are for allocating and initializing strings. The with-returned-pstrs form is for used when the call will returns a value. It allocates a full 256 bytes to handle any size string that gets returned. Here’s an example that uses a pathname to open a resource file.

;5

(with-pstrs ((file-name “hd:MACL:foo.rsrc”)) (setf refnum(_OpenResFile 
:ptr file-name :word)))

The variables you define are bound to pointers to Pascal strings which are initialized to the value of the LISP string you provide. For functions that return strings, you can create a LISP string from the Pascal string using %get-string.

rlet/rref/rset - These forms let you allocate, reference, and initialize records. You’ll need to (require ‘records) to load in records.Lisp which contains record definitions. MACLs handling of Pascal records (even variants) is pretty nice. Here is an example using a rectangle:

;6

(rlet ((r :rect  :top 0 :left 0 :bottom 20 :right 50))   (format t “top=~s,left=~s~%” 
(rref r :rect.top) (rref r :rect.left));check vals (_Pt2Rect :long #@(10 
10) :long #@(50 50) :ptr r)  ;change rect (format t “top=~s,left=~s~%” 
(rref r :rect.top) (rref r :rect.left)) ;check vals (rset r :rect.top 
-90) ;change it again (format t  “top=~s,left=~s~%” (rref r :rect.top) 
(rref r :rect.left)) ;check vals)

To conserve space I’ll defer further elaboration on these to the MACL manual which does a pretty good job. You’ll end up using rlet, rref, and rset a lot, so it’s worth taking some time to learn them. Since the record definitions give MACL detail on the field structure, messed up field names get caught. making these calls reasonably foolproof.

%stack-block - This is the catch all. You specify the size you need and it allocates raw chunks of memory on the stack. Initialization is left for you to do. A whole variety of functions like %get-word, %put-word, %get-ptr, are provide for setting and accessing.memory. Here is an example call to GetResInfo. Three of the parameters are var, so we have to allocate some space for them and pass in the pointers (and yes, I could have used with-returned-pstrs for the rsrcName). Presumably, theResource, has been set to the handle of some resource by some earlier call to GetResource. Once the trap call is returned, we extract the return values using %get-???. The pointers all become invalid once the %stack-block exits, so if we want to save the return values, we have to store their values in some LISP variables.

;7

(%stack-block (  (rsrcID 2) (rsrcType 4)(rsrcName 256)) (_GetResInfo
 :ptr theResource :ptr rsrcID :ptr rsrcType :ptr rsrcName)(setf the-id 
(%get-word rsrcID))(setf the-type (%get-ostype rsrcType))(setf the-name 
(%get-string rsrcName)))

These are the most general purpose, read dangerous, of the memory allocation/access functions. The real programmers out there might have noticed that by using field offsets this facility can can supplant the aforementioned record accessing functions. I strongly recommend against this type of thinking. For the most part, if you use record definitions you can avoid %stack-block and %get-??? and be much better off. Occasionally, they’re unavoidable. Most commonly, when you need to deal with integer or longint var parameters.

In summary, when you pass records, strings or var parameters you should allocate storage using either with-pstrs, rlet, or %stack-block, and pass in pointers to the storage. For var parameters, remember to access the returned values via one of the %get-??? functions. If you need to keep the returned values around, setf some LISP variable to the value.

Of course there are other ways to allocate/deallocate memory. There’s the NewPtr and NewHandle ToolBox calls and MACL provides make-record. The disadvantage to using these calls is that the memory you allocate is permanent, read clutters the heap, until you remember to deallocate it. But, one in a while, you need the memory you allocate to stick around a while.

Defining Pascal Record Types in LISP

When using the ToolBox another file you commonly need is records.Lisp. As with traps.Lisp, this is best accomplished by putting (require ‘records) in your code. What records.Lisp does is define various record types used by the ToolBox so that you can use them with rlet,rref, rset, and the like. Many of the common types are defined in records.Lisp, but. there are many omissions. Deficiencies can be corrected by defining additional record types using the MACL’s defrecord mechanism

To use the sound manager I had to define the Sound Manager types, SndCommand and SndChannel. Below is the definition for SndCommand.

;8

  (defrecord (SndCommand :pointer)    (cmd :integer)    (param1 :integer) 
   (param2 :longint)    )

The type after the record name, in this case :pointer, is the default storage used by rlet or rref when allocating or accessing records of this type. The choices are :pointer or:handle. The rest of the definition is just field name - field type pairs. Just to make things interesting, the types used in record definitions are different than the types used in trap calls. The choices are: :boolean, :byte, :character, :handle, :integer, :longint, :ostype, :point, and:pointer. More types were actually necessary because the four types used for trap call arguments are not specific enough for purposes of defining records. For instance, when defining a record you may need to specify a one byte :boolean field, as opposed to passing a character as an argument, when you can just use a :word (a minimum of two bytes is pushed on the stack, the extra byte is just wasted). I’m not sure why they don’t simplify things and just use the record field types for trap calls.

The defrecord mechanism also allows for variant records. The most notable use is in defining the type Rect, which can be accessed through the :top, :left, :bottom, and :right fields or through the :topleft and :bottomright fields (and yes, it should be :botright, it’s an error in records.Lisp)

Using the Sound Manager

Low Level Routines

I consider the five routines; get-snd, snd-open-channel, snd-close-channel, snd-command, and snd-command-immediate low level in that they require knowledge of the sound manager to use safely. They are not exported from sndMgr.Lisp and are not intended for use by the general MACL user.

get-snd: To play a sound, the first thing you need is a handle to the sound resource. get-snd provides a nice way to do this. It takes advantage of LISP’s weak typing to accept either a resource id or a resource name.and calls either GetResource or GetNamedResource as appropriate.

snd-open-channel: Since we want to play the sound asynchronously, we need to pass a SndChannelPtr to SndPlay. So we need to allocate a SndChannel using SndNewChannel (traps.Lisp has the trap name misspelled as _SndNewChan) By passing nil for the chan parameter, we get the convenience of having the SoundManager allocate storage. for the channel (SndDisposeChannel will free this storage) It is important to note that the chan is a var parameter, requiring us to allocate storage for it and pass its address. Also note that it’s initialized to nil using %put-ptr. This is a bit unusual as var parameters generally just pass information out, requiring no initialization.

The SndChannelPtr returned by SndNewChannel is stored in the global variable *snd-channel-p*. I rationalize this use of a global variable in two ways. It’s necessary to keep the around the address of the SndChannel so we can later dispose of it. Furthermore, by making sure *snd-channel-p* gets set back to nil when we close the channel, it serves as a flag indicating, by a non-nil value, that we’ve got a open sound channel. This is how the first line of snd-open-channel, (when *snd-channel_p* (snd-close-channel)), is able to protect the careless programmer who leaves an open SndChannel lying around.

The careful reader will note that there is more going here, the call to _NewPtr and _StuffHex. This has to do with the sound channel’s call back procedure, which is used to detect when the sound is done playing. I’ll defer further elaboration to the discussion of snd-open and snd-p.

snd-close-channel: Simply calls SndDisposeChannel to get rid of the sound channel, sets *snd-channel-p*, and frees up the storage used by the channel’s call back procedure.

snd-command: Provides a convenient way to queue up a command in the currently opened SndChannel, like execute your call back procedure.

snd-command-immediate: Provides a convenient way to issue a command for the currently opened SndChannel to execute immediately, like shutup.

High Level Routines

snd-open: Gets the specified sound resource, opens a sound channel, and plays the sound, asynchronously.by default. It also set the userInfo field of the channel to -1 to indicate that the sound is currently playing and then calls snd-command to queue up a callBackCmd command to be executed when the playing is over. The callBackCmd will, in turn, cause the channels call back procedure to be called which will set userInfo to 0 indicating the sound is done playing.

snd-close: It’s important to balance every call to snd-open with a call to snd-close as soon as possible after the sound plays. While you hold an open SndChannel no others can be opened. Yes, the next time you call snd-open the previous SndChannel will get closed, but in the mean time other applications are stuck, plus you won’t get system beeps. It would be nice if a SndChannel’s call back routine could close it for us, but the sound manager documentation specifically mention this is not allowed.

snd-halt: Simply sends immediate commands to quiet and flush the current SndChannel and then close it.

snd-p: This function returns whether or not a sound is still playing. Unfortunately, the sound manager doesn’t provide any calls to do this for us. We accomplish this using the userInfo field of the channel as a flag. So all snd-p has to do is examine this field.

The channel’s call back procedure is in charge of setting this flag to zero when the sound is done playing. But nothing is ever that easy. Call back procedures are called at interrupt time and thus have some strict limitations. First off, whenever you pass a pointer to the ToolBox, it expects a pointer to a Pascal function. So you can’t expect to use a regular LISP function. MACL, does provide the special form, defpascal, to let you write a LISP function with a Pascal style interface. However, it doesn’t seem to create functions strict enough to be called at interrupt time. My attempts at using it to define the call back procedure would often crash the system.

So, the solution. Write it in another language. I wrote it as a CODE resource in THINK C. It was a one line function, compiled to only 58 bytes. Since it was so small, I figured the easiest way to use it from MACL was to allocate 58 bytes of memory and just stuff the compiled machine code right into.it. So using ResEdit I copied the hex digits that comprised the CODE resource and then brought them into sndMgr.Lisp where they’re stored in the LISP string, *snd-call-back-mcode*.

Whenever a sound channel is created, 58 bytes are allocated with NewPtr, and then StuffHex is used to install the machine code of the compiled call back procedure. Sounds ugly, doesn’t it? But it worked first time I tried it, honest.

You might be asking, what if the call back procedure needed to be a big complicated procedure. Well, we could have used a resource file to hold the CODE resource and then used a derefenced handle to the resource. But this would have made sndMgr.Lisp trickier to use. Users would have to make sure to have a resource file around and open before playing a sound.

Wrapping Up

Attached is the source to sndMgr.Lisp. To try out the code first load sndMgr.Lisp and then play around with the test code in the comment at the end of the file. It would be pretty dull to include the hex source to a ‘snd ‘ resource, so your on your own to get a more interesting sound to play. The test code shows you how to open the resource file, once you get it. Use ResEdit to find out the resource id’s of the sounds it contains.

If the formatting of the LISP source looks a bit funny to you, it’s because of the 3.5" column width of MacTutor. Sorry If it makes reading it difficult.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; sndMgr.Lisp
;;
;; Copyright © 1990 Michael S. Engber
;; All Rights Reserved
;;
;; Sound Manager access from LISP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(require ‘traps)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Sound Manager definitions (missing from Records.Lisp)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(eval-when (compile load eval)
  
  (defrecord (SndCommand :pointer)
    (cmd :integer)
    (param1 :integer)
    (param2 :longint)
    )
  
  (defrecord (SndChannel :pointer)
    (nextChan :pointer)
    (firstMode :pointer)
    (callBack :pointer)
    (userInfo :longint)
    (wait :longint) ;Time
    (cmdInProgress SndCommand)
    (flags :integer)
    (qLength :integer)
    (qHead :integer)
    (qTail :integer)
    (queue :longint) ;array [0..stdQLength-1] of SndCommand
    )
  
  ;;; sound commands
  (defconstant $nullCmd 0)
  (defconstant $initCmd 1)
  (defconstant $freeCmd 2)
  (defconstant $quietCmd 3)
  (defconstant $flushCmd 4)
  (defconstant $waitCmd 10)
  (defconstant $pauseCmd 11)
  (defconstant $resumeCmd 12)
  (defconstant $callBackCmd 13)
  (defconstant $syncCmd 14)
  (defconstant $emptyCmd 15)
  (defconstant $tickleCmd 20)
  (defconstant $requestNextCmd 21)
  (defconstant $howOftenCmd 22)
  (defconstant $wakeUpCmd 23)
  (defconstant $availableCmd 24)
  (defconstant $versionCmd 25)
  (defconstant $scaleCmd 30)
  (defconstant $tempoCmd 31)
  (defconstant $noteCmd 40)
  (defconstant $restCmd 41)
  (defconstant $freqCmd 42)
  (defconstant $ampCmd 43)
  (defconstant $timbreCmd 44)
  (defconstant $waveTableCmd 60)
  (defconstant $phaseCmd 61)
  (defconstant $soundCmd 80)
  (defconstant $bufferCmd 81)
  (defconstant $rateCmd 82)
  (defconstant $continueCmd 83)
  (defconstant $midiDataCmd 100)
  
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar *snd-channel_p* nil “pointer to currently opened sound channel”)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmacro with-sound (sndSpec &rest forms)
  “(sndSpec) -forms-
Protective ‘with’ wrapper for using sounds.”
  (unless (listp sndSpec)
    (error “bad options”))
  ‘(unwind-protect (progn (snd-open ,(first sndSpec)) ,@forms) (snd-close)))

(defun snd-halt ()
  “void
Halts any sound in progress & closes the channel.”
  (when *snd-channel_p*
    (snd-command-immediate $quietCmd 0 0)
    (snd-command-immediate $flushCmd 0 0)
    (snd-close)))

(defun snd-p ()
  “void
Returns whether a sound (played with snd-open) is currently playing.”
  (when *snd-channel_p*
    (null (zerop (rref *snd-channel_p*
                    :SndChannel.userInfo)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;snd-open & snd-close open a sound channel,
;;play a sound, and then close  the sound
;;channel. Since only one sound channel can
;;be active at any time,  it is important
;;that every call to snd-open be followed by
;;a call to  snd-close (or snd-halt) as soon
;;as possible. While a sound channel is open,
;;no other sounds (like system beeps) can
;;play.
;;
;; The with-sound macro safely takes care of
;;all this for you. When control leaves the
;;body, either normally or abnormally, the
;;sound channel is closed.

(defun snd-open (sndSpec &key (async t))
  “sndSpec &key (async t)
Plays the specified sound (asynchronously by
default) sndSpec is either a resource number
or name of a ‘snd ‘ resource.”
  (let ((snd_h (get-snd sndSpec)))
    (when snd_h
      (cond
       (async
        (snd-open-channel)
        (rset *snd-channel_p*
              :SndChannel.userInfo -1)
        (_SndPlay :ptr *snd-channel_p*
                  :ptr snd_h
                  :word -1
                  :word)
        (snd-command $callBackCmd 0 0))
       (t
        (snd-close)
        (_SndPlay :ptr nil
                  :ptr snd_h
                  :word 0 :word))))))

(defun snd-close ()
  “void
Cleans up after sound finishes.”
  (snd-close-channel))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;Determining whether a sound is still
;;playing (snd-p) is done using a  Sound
;;Manager call back routines. Before playing
;;a sound, the userInfo  field of the sound
;;channel is set to -1. When the sound
;;completes, the  call back routine is
;;called and it sets userInfo to zero. snd-p
;;simply  checks the value of userInfo.

;;Since the call back routine is called at
;;interrupt time, there are several
;;restrictions on it (see Sound Manager
;;chapter of IM) which MACL’s defpascal
;;mechanism does not obey. So it was written
;;in C. The compiled code is small enough
;;that we can just copy its machine code into
;;memory when a sound channel is created
;;(avoiding loading CODE resources or
;;external function calls)
;;
;;   #include <SoundMgr.h>
;;
;;   pascal void main (SndChannelPtr theChan,
                       SndCommand* theCmd){
;;    theChan->userInfo = 0L;
;;   }
;;
;;

(defvar *snd-call-back-mcode* “600E0000434F444501F400000000000041FAFFEE4E714E71600000024E560000206E000C42A8000C4E5E205F4FEF00084ED04D41494E20202020”
  “machine code (hex) for call back routine”)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun get-snd (sndSpec)
  “sndSpec
Returns a handle to the specified ‘snd ‘ resource. sndSpec is either 
a resource number or name of a ‘snd ‘ resource.”
  (typecase sndSpec
   (fixnum (_GetResource :ostype “snd “
                         :word sndSpec
                         :ptr))
   (string (with-pstrs ((name sndSpec))
            (_GetNamedResource :ostype “snd “
                               :ptr name
                               :ptr)))
   (otherwise
     (error “bad resource specification [~S]”
             sndSpec))))

(defun snd-open-channel ()
  “void
Opens a new channel for sound play.”
  (when *snd-channel_p* (snd-close-channel))
  (%stack-block ((channel_p 4))
    
    ;; pass nil for the channel_p so the Sound Mangager will allocate 
space
    (%put-ptr channel_p nil)
    
    ;; stuff machine code for call back routine into memory
    (let ((call-back-ptr
           (_NewPtr  :d0 (/ (length *snd-call-back-mcode*) 2)
                    :a0)))
      (with-pstrs ((p *snd-call-back-mcode*))
        (_StuffHex :ptr call-back-ptr
                   :ptr p))
    
    (if (zerop
          (_SndNewChanne :ptr channel_p
                         :word 0
                         :long 0
                         :ptr call-back-ptr
                         :word))
      (setf *snd-channel_p*
            (%get-ptr channel_p))
      (error “allocating sound channel.”)))))

(defun snd-close-channel ()
  (when *snd-channel_p*
    (_DisposPtr :a0 (rref
                      *snd-channel_p*
                      :SndChannel.callBack)
                :d0)
    (_SndDisposeChannel :ptr *snd-channel_p*
                        :word 0
                        :word)
    (setf *snd-channel_p* nil)))

(defun snd-command (cmd param1 param2)
  “cmd  param1 param2
Adds the specified command to the sound channel’s queue.”
  (when *snd-channel_p*
    (rlet ((cmd_p :SndCommand
                  :cmd cmd
                  :param1 param1
                  :param2 param2))
      (_SndDoCommand :ptr *snd-channel_p*
                     :ptr cmd_p
                     :word 0
                     :word))))

(defun snd-command-immediate (cmd param1 param2)
  “cmd param1 param2
Sends the sound channel the specified command to immediately execute.”
  (when *snd-channel_p*
    (rlet ((cmd_p :SndCommand
                  :cmd cmd
                  :param1 param1
                  :param2 param2))
      (_SndDoImmediat :ptr *snd-channel_p*
                      :ptr cmd_p
                      :word))))

#|

test code

This plays a sound asychronously. During play it checks to see if the shift key is pressed - if so it halts the sound immediatlely.It uses the ‘snd ‘ resource id = 1, the standard system beep.

;10

(with-sound (1)
    (loop (when (or
                  (shift-key-p)
                  (null (snd-p)))
            (snd-halt) (return))))

The standard system beep is so short that the above code isn’t too exciting as is. You may want to try some of the longer system beeps like Clink-Klang (id = 2) if you have them installed. Or better yet, open a sound resource file of your own with this code.

;11

(with-pstrs ((res_file “your sound file”))
  (_openresfile :ptr res_file :word))
|#

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

beaTunes 4.6.14 - Organize your music co...
beaTunes is a full-featured music player and organizational tool for music collections. How well organized is your music library? Are your artists always spelled the same way? Any R.E.M. vs REM?... Read more
iDefrag 5.1.8 - Disk defragmentation and...
iDefrag helps defragment and optimize your disk for improved performance. Features include: Supports HFS and HFS+ (Mac OS Extended). Supports case sensitive and journaled filesystems. Supports... Read more
Day One 2.1.8 - Maintain a daily journal...
Day One is the easiest and best-looking way to use a journal / diary / text-logging application for the Mac. Day One is well designed and extremely focused to encourage you to write more through... Read more
Spotify 1.0.53.758. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
VirtualBox 5.1.20 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
Arq 5.7.9 - Online backup to Google Driv...
Arq is super-easy online backup for Mac and Windows computers. Back up to your own cloud account (Amazon Cloud Drive, Google Drive, Dropbox, OneDrive, Google Cloud Storage, any S3-compatible server... Read more
Vienna 3.1.10 :d05d7a5d: - RSS and Atom...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
WhiteCap 6.7 - Visual plug-in for iTunes...
WhiteCap is a sleek and sophisticated music visualizer and screensaver that features futuristic, wireframe mesh visuals with dynamic backgrounds and colors. WhiteCap contains thousands of visual... Read more
Dropbox 24.4.16 - Cloud backup and synch...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
Amazon Chime 4.2.5645 - Amazon-based com...
Amazon Chime is a communications service that transforms online meetings with a secure, easy-to-use application that you can trust. Amazon Chime works seamlessly across your devices so that you can... Read more

Latest Forum Discussions

See All

Blizzard is looking to hire a mobile dev...
A new thread on the popular video game rumor forum, NeoGAF, uncovered an interesting job listing over at Blizzard Entertainment. It appears the studio behindStarCraft, World of WarCraft, Hearthstone,andOverwatch is looking to bring on a new hire... | Read more »
Legend of Zelda meets Cooking Mama in ne...
Dungeon Chef is what happens when you mix the RPG elements (and style) of a Legend of Zelda game, with cooking elements. Although, now that The Legend of Zelda: Breath of the Wild also has cookingelements, so maybe the gameplay is not so novel.... | Read more »
ChordFlow (Music)
ChordFlow 1.0.0 Device: iOS Universal Category: Music Price: $6.99, Version: 1.0.0 (iTunes) Description: ChordFlow is a chord sequencer with a unique 4-track polyphonic arpeggiator, extensive chord library, MIDI out and Ableton Link... | Read more »
The Walking Dead: A New Frontier is out...
The newest season of Telltale Games'The Walking Dead is well underway. After the release of the third episode, "Above the Law" about a month ago, episode four, "Thicker Than Water" is hot and ready for more zombies and gut-wrenching emotional... | Read more »
Best games we played this week
Another week, another new wave of mobile games do dive into. We've dug through the list of apps that came out this week to tell you which apps are worth your sweet time. And while there weren't too many games this week, there were some big ones.... | Read more »
Vignettes (Games)
Vignettes 1.0.1 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.1 (iTunes) Description: Vignettes is a casual but unique exploration game without text or characters, where objects shapeshift as you spin them around... | Read more »
Get Me Outta Here is an 80s retro shoote...
Are you ready to fight some aliens? Because Crescent Moon Games has released the retro shooter Get Me Outta Here on iOS devices today. [Read more] | Read more »
Get a bunch of Apple productivity apps f...
If you're an Apple Mac owner, you're probably aware of the host of Apple productivity apps the company includes in all new Mac purchases. Apps like iMovie, Keynote, and of course, GarageBand. While you used to be able to also buy these apps... | Read more »
Terra Mystica (Games)
Terra Mystica 1.03 Device: iOS Universal Category: Games Price: $9.99, Version: 1.03 (iTunes) Description: Short Summary:≈≈≈≈≈≈≈≈≈≈≈≈≈ | Read more »
Ms. Spell (Games)
Ms. Spell 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Cast spells and battle monsters in this turn based game, that has you delving into ever the changing Dreadwood to retrieve the lost... | Read more »

Price Scanner via MacPrices.net

15-inch Touch Bar MacBook Pros, Apple refurbi...
Apple is offering Certified Refurbished 2016 15″ Touch Bar MacBook Pros for $360 to $420 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 15″ 2.6GHz... Read more
13-inch MacBook Airs on sale for up to $150 o...
Overstock.com has 13″ MacBook Airs on sale for up to $150 off MSRP including free shipping: - 13″ 1.6GHz/128GB MacBook Air (sku MMGF2LL/A): $869.99 $130 off MSRP - 13″ 1.6GHz/256GB MacBook Air (sku... Read more
15-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has the new 2016 15″ Apple Touch Bar MacBook Pros in stock today and on sale for up to $200 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.7GHz Touch Bar... Read more
15-inch 2.7GHz Touch Bar MacBook Pros on sale...
Amazon has 2016 15″ 2.7GHz Apple Touch Bar MacBook Pros in stock today and on sale for $150-$200 off MSRP. Shipping is free: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray (sku MLH42LL/A): $2599 $200... Read more
Apple now offering Certified Refurbished 13-i...
Apple is now offering Certified Refurbished 2016 13″ Touch Bar MacBook Pros for $270-$300 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 13″ 2.9GHz... Read more
MyGiHealth Digestive Symptom Tracker Version...
My Total Health, Inc. has announced the release of MyGiHealth 2.1, an important update to their digestive symptom tracker developed exclusively for iPhone, iPad and iPod touch devices. MyGiHealth is... Read more
Galaxy S8 Materials Costs Highest by Far Comp...
The new Samsung Galaxy S8 equipped with 64 gigabytes (GB) of NAND flash memory carries a bill of materials (BOM) cost that comes out to US$301.60, much higher than for previous versions of the... Read more
iCarMode 4.0 Car Dashboard App For iOS Integr...
Indie developer Diego Resnik has announced the release of iCarMode 4.0, an update to his productivity app developed for iOS devices. iCarMode has positioned itself as a true car dashboard app,... Read more
How to save $150+ on Apple’s 13-inch 2.0GHz n...
Apple Authorized Reseller B&H Photo has non-Touch Bar 13″ 2.0GHz MacBook Pros on sale for $150 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more

Jobs Board

*Apple* Mac Computer Technician - GeekHampto...
…complex computer issues over the phone and in person? GeekHampton, Long Island's Apple Premium Service Provider, is looking for you! Come work with our crew Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Music Marketing Lead, iTunes & *Apple*...
# Music Marketing Lead, iTunes & Apple Music Job Number: 56868140 Culver City, California, United States Posted: Apr. 17, 2017 Weekly Hours: 40.00 **Job Summary** Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
*Apple* Mac Computer Technician - GeekHampto...
…complex computer issues over the phone and in person? GeekHampton, Long Island's Apple Premium Service Provider, is looking for you! Come work with our crew Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.