All of Impromptu's audio output is produced using plugins - specifically AudioUnit plugins.  There is no way to produce a sound in Impromptu without using at least one AudioUnit of some kind.  There are thousands of AudioUnit's to choose from, and if you can't find one that you like you can always write your own.  There are a huge variety of free and commercial AudioUnit's to choose from so have a search around the net and see what you can find.  Here are a few suggestions to get started:
Luckily Apple ships a bunch of free AudioUnits with OSX that we can start with.
AudioUnits can do tasks as simple as adding two signals, like Apples AUMerger or as complex as Native Instruments Reaktor.  Impromptu supports both of these AudioUnits and everything in between.
AudioUnits come in a number of varieties but the two flavours that we are interested in today are instruments and effects.  At it's simplest, instruments are AudioUnits that generate an audio signal in response to midi note-on and note-off messages.  Effects are AudioUnits (AUs) that modify the audio signal in some way.  Instrument AUs will appear at the top of your signal chain and effect AUs will appear in the middle of your signal chain.  At the very end of your chain is an output AU and this is automatically provided by Impromptu.
OK, let's take a look at what Instrument AUs are on your system.  You specify an Instrument AU by using it's code "aumu"
(au:print-audiounits "aumu")
This should have printed something like this to the log.
DLSMusicDevice................type=[1635085685] 'aumu' subtype=[1684828960] 'dls ' manufacturer=[1634758764] 'appl'
The type as we specified is "aumu".  The subtype "dls " is the individual AUs name and the manufacturer is in this case "appl".  You'll notice that all of these codes are 4 characters long, even "dls " which includes a space (the space is an important part of the name !! ).  These are the codes that we use to load AudioUnits in Impromptu.
Once we have the codes we need we can load the AU into impromptu.
(define piano (au:make-node "aumu" "dls " "appl"))
You can check that the piano was loaded OK by calling print.
(print piano)
This is the piano’s node (AudioUnit) number.  A negative return value indicates that the AU initialization failed for some reason (probably the wrong 4 char code was entered).
au:make-node loads and initialises the AU but until we connect it to an output we will not be able to hear anything.  Impromptu automatically creates the output for you.  It is defined as *au:output-node*
Let's print the graph just to prove that we already have an output node ready to go.
Our log view should display the following:
AudioUnitGraph 0x1816C14:
  Member Nodes:
    node 1: desc auou ahal appl, instance 0x810000 O I
    node 2: desc aumu dls  appl, instance 0x810002 O  
  Events to be updated:
    mLastUpdateError=0, eventsToProcess=F, isRunning=T
This shows that we have instantiated 2 nodes.  One is our dls (recognise the codes) and the other is the default output (“ahal”) which is the *au:output-node* (note that the node number should match the one printed earlier).  That there are no connections and the graph is running.  If you send piano to the print function it will return it’s node number to you.  It should match the node number in the graph printout.
OK, let's go ahead and connect the piano to the output.
(au:connect-node piano 0 *au:output-node* 0)
Here we connect the piano's first output bus (buses start from 0) to the output's first input bus.  This means that signal will now flow from the piano to the output.  Most AUs only have 1 input bus and 1 output bus.  There are some exceptions, namely mixers like the multi-channel mixer.  We'll have a look at mixers later, for the moment assume only one input and one output per AU.
Whenever we make connections or disconnections we need to call au:update-graph to register the changes.
Let's print out the graph again.
AudioUnitGraph 0x1816C14:
  Member Nodes:
    node 1: desc auou ahal appl, instance 0x810000 O I
    node 2: desc aumu dls  appl, instance 0x810002 O I
    node  2 bus  0 => node  1 bus  0
  Events to be updated:
    mLastUpdateError=0, eventsToProcess=F, isRunning=T
We now have one connection - node 2 is connected to node 1 exactly the way we want it.
We should now be able to produce a sound by "playing" the piano AU.  This command sends MIDI note number 60 (middle C) with a velocity of 80 and plays the sound for 10000 samples.
(play-note (now) piano 60 80 10000)
Behind the scenes impromptu is actually sending MIDI note-on and note-off messages to the piano AudioUnit.  We can also send MIDI messages to the AudioUnit explicitly.  Let's play a note by sending separate note-on and note-off messages.
(au:midi-out (now) piano *io:midi-on* 0 60 80)
(au:midi-out (+ (now) 10000) piano *io:midi-off* 0 60 0)
Of course we can send other types of messages as well.  Let's use a control change message to pan the piano hard right.
(au:midi-out (now) piano *io:midi-cc* 0 10 127)
As well as sending MIDI messages we can also send AUs parameter messages.  AU parameters allow us to control many (if not all) of an AUs UI components programatically.  First of all let's open the piano's UI so we can see what parameters are available to us.  Of course you are free to control the piano's UI manually if you like.  Have a play.
(au:open-view piano)
Let's see what parameters we can control programatically.  The au:print-params function needs to know what scope the parameters we are looking for are.  There are four scopes *au:input-scope* *au:output-scope* *au:global-scope* *au:group-scope*.  You are almost always going to want to use *au:global-scope*.
(au:print-params piano *au:global-scope*)
0 "tuning"
1 "volume"
2 "reverb volume"
You should have seen three results printed to the log.  Let's choose "tuning", parameter id 0.  We can call au:set-param to programatically change this parameter.  The first value after the AU is the parameter id (which is 0 for "tuning"), next is the scope, followed by the group id (this will almost always be 0 accept for mixes when you will use it to differentiate between input busses), finally we send the value for the parameter (400 cents in this example).
(au:set-param (now) piano 0 *au:global-scope* 0 400)
Note that changing the parameter repositions the slider.  Of course we can also retrieve the parameters current value.
(au:get-param piano 0 *au:global-scope* 0)
Let's spice things up a little, how about a filter.  Happily, Apple ship a nice AU filter for you.  We will want to position the filter between the piano and the output.  To do this we will need to disconnect the piano from the output.  Create the filter AU.  Connect the piano to the filter and then the filter to the output.   Update the graph.  Then print out the graph and open the filter's UI for good measure.
(au:disconnect-node *au:output-node* 0)
(define filter (au:make-node "aufx" "filt" "appl"))
(au:connect-node piano 0 filter 0)
(au:connect-node filter 0 *au:output-node* 0)
(au:open-view filter)
AudioUnitGraph 0x1816C14:
  Member Nodes:
    node 1: desc auou ahal appl, instance 0x810000 O I
    node 2: desc aumu dls  appl, instance 0x810002 O I
    node 3: desc aufx filt appl, instance 0x81000C O I
    node  2 bus  0 => node  3 bus  0
    node  3 bus  0 => node  1 bus  0
  Events to be updated:
    mLastUpdateError=0, eventsToProcess=F, isRunning=T
Looking good!  Notice that we disconnect from the output (i.e. we disconnected the output's first input bus - not the piano's first output bus).  You should now be able to filter the signal coming from the piano before it reaches the output.  Have a play!
Now, what about if we want to play more than one instrument at a time.  No problem.  One way to do this is to use a merger.  Again, Apple to the rescue.  Here's a complete listing that connects two instruments, through two filters into one output.  Before running this example you should restart Impromptu so that you can begin from a clean slate (or call au:clear-graph).
(define piano1 (au:make-node "aumu" "dls " "appl"))
(define filter1 (au:make-node "aufx" "filt" "appl"))
(define piano2 (au:make-node "aumu" "dls " "appl"))
(define filter2 (au:make-node "aufx" "filt" "appl"))
(define merger (au:make-node "aufc" "merg" "appl"))
(au:connect-node piano1 0 filter1 0)
(au:connect-node piano2 0 filter2 0)
(au:connect-node filter1 0 merger 0)
(au:connect-node filter2 0 merger 1)
(au:connect-node merger 0 *au:output-node* 0)
(play-note (now) piano1 60 80 10000)
(play-note (now) piano2 72 80 10000)
It is possible to create graphs of any complexity by using splitters, mergers "aufc" and mixers "aumx" along with instruments "aumu", generators "augn" and fx "aufx".  Remember AUs can be as complicated as Reaktor but they can also be simple oscillators and delays suitable for building your own synthesis instruments.
Have Fun!
Making A Noise
Impromptu as AudioUnit Host